--- name: hunt-aspnet description: Hunt ASP.NET-specific surface — ViewState deserialization (signed-only vs encrypted), machineKey recovery, dual-parser MAC-bypass anti-pattern, request-validator bypass, trace.axd/elmah.axd disclosure, load-balanced ViewState cross-node failures, SafeControl enumeration via reflection, customErrors mode=Off stack-trace leaks, classic Webforms .aspx/.asmx/.svc surface. Built for ASP.NET Webforms + WCF + SharePoint farms. sources: github, authorized-engagement report_count: 1 --- ## Crown Jewel Targets ASP.NET deserialization bugs pay among the highest amounts in bug bounty when they reach RCE. Even when patched, the disclosure-tier findings (signed-only ViewState, dual-parser differential, request-validator quirks) reliably pay Low-Medium. **Highest-value targets:** - **SharePoint farms** (any version — 2013/2016/2019/SE) — sign-only ViewState + permissive ToolPane.aspx + anonymous FormDigest creates the CVE-2025-53770 ToolShell precondition chain - **Telerik UI for ASP.NET AJAX** — `Telerik.Web.UI.WebResource.axd` is a documented RCE sink when keys leak (CVE-2017-11317, CVE-2017-11357, CVE-2019-18935) - **Classic ASP.NET Webforms enterprise apps** — banking portals, dealer portals, HR systems left on .NET Framework 4.x - **WCF services** (`*.svc?WSDL`) — often forgotten admin endpoints with looser auth than the main app - **Sitecore CMS** — ViewState + Sitecore-specific deserialization chains (CVE-2021-42237) - **DotNetNuke (DNN)** — historic ViewState RCE chains - **Umbraco CMS** — ViewState + custom deserialization sinks **Asset types that pay most:** internet-reachable ASP.NET Webforms apps > WCF admin services > Telerik-integrated sites > Classic ASP.NET MVC with VSF (very rare) --- ## Attack Surface Signals **Response headers indicating ASP.NET:** ``` X-AspNet-Version: 4.0.30319 (classic — disclosure on its own) X-Powered-By: ASP.NET X-AspNetMvc-Version: 5.2 Server: Microsoft-IIS/10.0 Set-Cookie: ASP.NET_SessionId=... Set-Cookie: .ASPXAUTH=... (Forms auth cookie) Set-Cookie: .ASPXFORMSAUTH=... Set-Cookie: ASP.NET_SessionId=...; SameSite=None (suggests cross-origin embedding) ``` **Body signals (in form HTML):** ``` ↑ EMPTY = signed-only, not encrypted = exploitable if key leaks ↑ SharePoint CSRF token; if anon-issued, see hunt-sharepoint ``` **URL patterns to probe:** ``` /trace.axd (per-app trace viewer; sometimes anon-accessible) /elmah.axd (ELMAH error log viewer) /elmah.axd/?id=... (ELMAH RCE / stack-trace leak) /*.svc (WCF services) /*.svc?wsdl (WCF WSDL) /*.svc/mex (Metadata Exchange) /*.asmx (legacy SOAP) /*.asmx?WSDL (legacy SOAP description) /*.asmx?disco (legacy discovery) /Telerik.Web.UI.WebResource.axd (Telerik AJAX components) /ChartImg.axd (DataVisualization controls; historic deserialization) /ScriptResource.axd (script resource handler; sometimes leaks paths) /WebResource.axd (web resource handler) /_vti_bin/* (SharePoint Web Service Forwarder) /api/ (Web API 2.x is ASP.NET on classic framework) /signin (often FedAuth / WS-Federation) ``` **Tech-stack signals:** - `Server: Microsoft-IIS/10.0` (or `/8.5`, `/7.5`) — confirmed Windows + IIS - `X-AspNet-Version` header — classic .NET Framework (4.x); .NET Core/5+ does NOT emit this - Cookies with `ASP.NET_SessionId`, `.ASPXAUTH`, `FedAuth` — Forms or claims auth - `__VIEWSTATE` in form bodies — Webforms (NOT MVC, NOT Razor Pages, NOT Blazor) - `MicrosoftSharePointTeamServices` header (sometimes stripped by ELB but leaks in `start.aspx` body) — SharePoint --- ## Step-by-Step Hunting Methodology 1. **Fingerprint the framework version.** Trigger any 500 error (stale ViewState POST is a reliable way) and look for `Version Information: Microsoft .NET Framework Version:X.X.XXXXX; ASP.NET Version:X.X.XXXX.X` in the error body. This banner discloses both the runtime and ASP.NET-version-specific patch level. .NET 4.0.30319 + ASP.NET 4.8.x is the most common modern combination. 2. **Locate every form with `__VIEWSTATE`.** Spider the target and grep for `name="__VIEWSTATE"`. Each is a candidate sink for deserialization attacks if MAC / encryption is bypassable. 3. **Check `__VIEWSTATEENCRYPTED` value.** Empty (`value=""`) means ViewState is signed-only via `` but NOT encrypted. Recovery of the validation key → arbitrary deserialization. Non-empty (`value="something"`) means ViewState is BOTH signed and encrypted; both keys needed to forge. 4. **Test the ViewState parser-error differential** (the dual-parser anti-pattern). Send 7+ ViewState shapes and classify responses: - Trivial garbage (`AAAA`) → `"Validation of viewstate MAC failed"` - Real prefix from current page → `"Validation of viewstate MAC failed"` - Flipped-bit real ViewState → `"Validation of viewstate MAC failed"` - Oversize (`A * 100000`) → `"Validation of viewstate MAC failed"` - XML-shaped (``) → **"The state information is invalid for this page and might be corrupted"** ← different parser path - LosFormatter-style prefix (`/wEPDwUKMTcxNzgyOTQwMmRkkz9p4lzA...`) → **"The state information is invalid for this page and might be corrupted"** The differential proves there are **two distinct deserialization entry points**, one of which dispatches BEFORE the MAC check on some payload shapes. Historically this enables MAC-before-parse-bypass exploits. 5. **Look for load-balanced cross-node ViewState MAC failures.** If POST gets a 500 with `"Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that configuration specifies the same validationKey..."`, the farm has multiple WFEs WITHOUT machineKey sync, or without sticky-session affinity. Operationally this breaks legit users; security-wise it confirms farm topology. 6. **Probe `trace.axd` and `elmah.axd`.** If either returns 200 anonymously, it's a Critical finding (trace leaks every request + headers + form data; ELMAH leaks every server error including stack traces). 7. **Enumerate WCF services (`.svc`).** For each, fetch `?wsdl` and `?mex` (metadata exchange). MEX endpoints sometimes return full service contracts including admin operations. 8. **Test request-validator bypass.** ASP.NET's request validator blocks `<` in query strings by default. Bypass categories that may still get through: - HTML-entity-encoded payloads (`<script>` — but these don't execute) - Encoded inside JSON / XML POST bodies (different content-type ≠ same validator) - In path segments (not query) — validator scope depends on framework version - In Cookie / Referer headers (varies) - Inside `<%@ ... %>` ASP directives if reached via WebDAV PUT (rare) 9. **Check `customErrors` mode.** If 500s expose full stack traces, framework versions, file paths, internal method names → `customErrors mode="Off"` is set. Should be `RemoteOnly` for production. 10. **Look for Telerik components.** `Telerik.Web.UI.WebResource.axd?type=rau` is the historic upload-to-RCE chain (CVE-2017-11317). The `dialogParametersHolder` parameter chain (CVE-2019-18935) requires the encryption key but is otherwise RCE. 11. **SharePoint-specific deserialization paths** — see `hunt-sharepoint` skill for the ToolPane.aspx + anonymous FormDigest + unencrypted ViewState chain. 12. **SafeControl enumeration via reflection.** SharePoint's `Picker.aspx?PickerDialogType=` (and DNN-equivalent endpoints) accept class names and return DIFFERENT error messages for "type exists but not whitelisted" vs "type does not exist." Feed a wordlist of `Microsoft.SharePoint.*.WebControls.*` types to enumerate the SafeControl list — useful for CVE-2019-0604-family hunting. --- ## Payload & Detection Patterns **Stack-trace fingerprint (trigger via stale ViewState POST):** ```bash curl -sk -X POST "https://target.example/page.aspx" \ --data "__VIEWSTATE=AAAA&__VIEWSTATEGENERATOR=AAAA" # Inspect body for: # - "Validation of viewstate MAC failed" → confirms signed ViewState # - "The state information is invalid for this page" → confirms ALTERNATE parser path # - "Version Information: Microsoft .NET Framework Version:X.X.XXXXX" → exact patch level # - "Microsoft.SharePoint.Client.ServerStub..." → SharePoint farm ``` **ViewState parser-error differential probe (Python):** ```python import requests, re, json S = requests.Session(); S.verify = False # Get fresh form r = S.get("https://target.example/path/page.aspx") real_vs = re.search(r'__VIEWSTATE" id="__VIEWSTATE" value="([^"]+)', r.text).group(1) real_vsg = re.search(r'__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="([^"]+)', r.text).group(1) # Test 7 payload shapes for label, vs in [ ("trivial", "AAAA"), ("real", real_vs), ("flipped-bit", real_vs[:50] + "X" + real_vs[51:]), ("oversize", "A" * 100000), ("base64", "VGVzdE1hcmtlcjY3OFhZWg=="), ("xml-shaped", ""), ("losformatter", "/wEPDwUKMTcxNzgyOTQwMmRkkz9p4lzA" + "A"*50), ]: r = S.post("https://target.example/path/page.aspx", data={"__VIEWSTATE": vs, "__VIEWSTATEGENERATOR": real_vsg}) title = re.search(r'([^<]+)', r.text) title = title.group(1)[:100] if title else "—" print(f" [{label:14s}] {r.status_code} {title}") ``` **`trace.axd` anonymous check:** ```bash curl -sk -o /dev/null -w "%{http_code}\n" "https://target.example/trace.axd" # 200 = full trace dump exposed → Critical # 403 = mod set to localhost-only → check via X-Forwarded-For: 127.0.0.1 ``` **WCF service enumeration:** ```bash # Find all .svc files curl -sk "https://target.example/" -o body.html grep -oE '/[a-zA-Z0-9/_-]+\.svc' body.html | sort -u # For each found: curl -sk "https://target.example/Service.svc?wsdl" | xmllint --format - | head -60 ``` **Request-validator bypass categories:** ``` # Default: in ?q= → "Potentially dangerous Request.QueryString value detected" # Bypasses that sometimes work: ?q=%3cscript%3e (URL-encoded — depends on validator config) ?q= (depends on validator version) ?q=<%00script> (NUL-byte; older validators) ?q=javascript:alert(1) (no < at all — passes validator) Cookie: foo=