[HackerNotes Ep. 163] PortSwigger Top 10 Web Hacking Techniques of 2025

We are covering all the techniques from the top 10 by PortSwigger in 2025

Hacker TL;DR

  • Parser differentials: duplicate JSON keys or double Authorization headers parsed differently by frontend vs backend → auth bypass depending on which value each component reads.

  • HTTP/2 CONNECT tunnels: if the server supports the CONNECT verb over HTTP/2, you get a potential SSRF and internal port scanning primitive via binary-framed stream multiplexing.

  • XSS-Leak via Chrome connection pool: saturate Chrome's 256-connection cap, trigger a cross-origin redirect, and use lexicographic host resolution ordering to binary-search leaked subdomains.

  • Next.js internal cache poisoning: __nextDataReq converts responses to JSON but isn't part of the cache key; combine with x-now-route-matches to poison the internal cache with a stored XSS payload.

  • Cross-site ETag length leak: ETag reflected into If-None-Match can overflow Express's 16KB header limit → HTTP 431 → Chrome History API length oracle leaks secret content.

  • SOAPwn (.NET WSDL proxy): file:// URI supplied to HttpWebClientProtocol causes a silent cast failure → arbitrary file write to disk as .aspx/.cshtml → instant webshell and RCE.

  • Unicode normalization attacks: five classes of bypass (overlong encoding, byte truncation, confusables, casing, combining diacritics) all exploit the same root cause, security check runs before normalization.

  • Novel SSRF via redirect loops: escalating redirect chains (301→302→...→310) with a final hop to AWS metadata can flip blind SSRF to full-read through error handling mismatches.

  • ORM injection as server-side XS-leaks: filtering endpoints exposing field + operator syntax (resetToken[not]=E) allow binary search on sensitive field values, the new SQLi.

  • Error-based SSTI: borrowing SQLi methodology for template injection, verbose errors leak data, division-by-zero gives a boolean oracle, and (1/0).zxy.zxy is a universal detection polyglot.

Hackernotes

Parser Differentials: When Interpretation Becomes a Vulnerability

This talk from joernchen at OffensiveCon 25 is a clean survey of what happens when two components in the same stack parse the same data and reach different conclusions. The first example is a JSON body with duplicate roles keys. Erlang's JSON parser reads the first occurrence and sees admin. JavaScript's parser reads the last and sees an empty array. If the auth decision happens in Erlang but the session is managed by a Node service, you get a straight auth bypass with one duplicated key.

The second example targets the Authorization header. Send two of them: the first carries an alg:none JWT with crafted admin claims, the second is a legitimate low-privilege bearer token. The frontend validates the second header (valid signature, low-priv), the backend reads the first (unsigned, admin claims). Same request, two interpretations, full privilege escalation.

YAML parsing opens even more surface. The !!binary tag silently base64-encodes values, and the << merge operator merges keys from anchors, both can create differential behavior between YAML parsers that handle these tags differently. Even YAML booleans are unstable: yes, no, true, false may resolve as strings or booleans depending on the parser version. CI/CD pipelines that consume YAML (GitHub Actions, GitLab CI) are high-value targets here because the parsing stack is rarely audited end-to-end.

Playing with HTTP/2 CONNECT

HTTP/1.1 CONNECT creates a raw bidirectional tunnel. HTTP/2 CONNECT is a completely different mechanism: binary framing, stream multiplexing, individual stream identifiers. The author built a Go-based port scanner that operates inside HTTP/2 CONNECT tunnels. A successful connection returns a status 200 frame; a failure returns 503. That distinction is enough to map open internal ports behind a proxy or load balancer that supports the CONNECT verb.

Beyond port scanning, raw HTTP/1.1 requests can be sent inside the tunnel and responses read back from internal HTTP servers. The hunting approach is straightforward: scan your targets for HTTP/2 CONNECT support. If the verb is accepted, you likely have a free SSRF and port scan primitive without needing any other vulnerability.

But this blogpost was quite hard to understand, especially the real impact and surface of this vulnerability.

We do subs at $25, $10, and $5, premium subscribers get access to:

Hackalongs: live bug bounty hacking on real programs, VODs available
Live data streams, exploits, tools, scripts & un-redacted bug reports

XSS-Leak: Leaking Cross-Origin Redirects

Despite the name, this has nothing to do with XSS. It's a cross-site leak that abuses Chrome's connection pool mechanics. Chrome enforces a hard cap of 256 concurrent requests total and 6 per origin. When the pool is full and a conflict needs resolution, Chrome prioritizes by a specific order: priority table, then port, then scheme, then host, lexicographically.

The attack works by filling the connection pool, then triggering a cross-origin redirect (for example via a hash change). Simultaneously, the attacker triggers a request to a guessed hostname with the same priority, port, and scheme. One slot is released, and timing is measured. If the request took longer to resolve, the cross-origin host was lexicographically smaller than the guess. This gives a binary search oracle that can leak full subdomains character by character.

The technique also works for user-type detection. If an application redirects to admin.example.com for admins and user.example.com for regular users, the lexicographic ordering difference becomes a role oracle. It's a narrow primitive but a reusable one anywhere cross-origin redirects carry state.

Next.js, cache, and chains: the stale elixir

This is a research from zhero targeting the Next.js internal cache, not a CDN-vs-backend mismatch, but the framework's own caching layer. Two components make it work. The __nextDataReq query parameter converts a standard page request into a "data route" that returns JSON instead of HTML. The x-now-route-matches header makes the response cacheable by the internal cache.

The critical detail: __nextDataReq is not part of the cache key. There's no query-parameter-based cache buster available. But Accept-Encoding is part of the cache key, and browsers always send it. So the attacker sends the malicious request without Accept-Encoding, poisoning that specific cache variant. Normal users always include the header, so they never hit the poisoned entry, unless the attacker targets the variant they'll actually receive.

The impact is stored XSS. The JSON blob includes the User-Agent value and gets served with a text/html content type, so it executes on page load. Multiple reports using this chain netted a six-figure bounty total. The takeaway here is internal caching mechanisms in frameworks are high-value audit targets. Look for internal headers and query params that alter response format without being part of the cache key.

Cross-Site ETag Length Leak

This one started as an unintended CTF solution and generalized into a reusable primitive. The chain begins with CSRF to control the length of note content. The ETag header reflects the response size. On a second request, the browser automatically sends If-None-Match with the ETag value, which increases the request size. The attacker buffers additional data up to Express's 16KB header limit using a query parameter. If the ETag is 4 bytes instead of 3, the total request size crosses the 16KB boundary and triggers an HTTP 431 (Request Header Fields Too Large).

Here's the Chrome gadget that makes it exploitable: a 431 response replaces the current history entry instead of adding a new one. That means history.length becomes a binary oracle. Did the 431 happen? Then the ETag was 4 bytes. Did the content match the flag condition? Binary search loop, secret extracted.

Real-world applicability is constrained: you need CSRF and granular control over content length. But the Chrome 431 History API gadget is broadly reusable as an oracle wherever you can influence response header size. The key primitive is a response header (ETag) reflected into a request header (If-None-Match), creating overflow-based attack surface.

SOAPwn: Pwning .NET Framework Apps Through HTTP Client Proxies and WSDL

WatchTowr dropped a 93-page whitepaper on this one. The core vulnerability lives in HttpWebClientProtocol, the .NET class used to send SOAP requests over HTTP. The code uses the as operator to cast a URI to an HttpWebRequest. When an attacker supplies a file:// URI, the cast produces a FileWebRequest instead, and the as operator returns null rather than throwing. The code checks for null, skips all HTTP-specific setup, and returns the original FileWebRequest untouched. No error is thrown.

The first impact is arbitrary file write. The XML SOAP body gets written to disk. Name the file .cshtml or .aspx, inject C# code into the SOAP arguments, and you have an instant webshell. The second impact comes from UNC paths: supply a UNC path as the URI, and the server attempts to write to an attacker-controlled share, leaking its NTLM hash for offline cracking.

Microsoft classified this as an application-level issue rather than a framework vulnerability, so there's no universal patch. Hunting for this means looking at .NET applications that handle HTTP client proxies and WSDL ingestion, specifically for cast/URI-type confusion where file:// or UNC schemes aren't filtered before the cast.

Lost in Translation: Exploiting Unicode Normalization

Ryan and Isabella Barnett presented this at Black Hat USA 2025, and Ryan's position at a major WAF vendor gives the research real-world grounding. They document five distinct attack classes, all rooted in the same fundamental problem: the security check executes before Unicode normalization.

Overlong encodings are the first class: a standard ASCII character encoded as 2 or 3 bytes bypasses regex checks that run before the application normalizes back to the single-byte representation. Byte truncation is the second: a 3-byte Chinese character truncated to its last two bytes can produce CRLF or LF sequences, enabling header injection through what appears to be an endianness issue. Confusables and best-fit mappings are the third class, extending Orange Tsai's "worst-fit" research: visually similar characters get normalized to dangerous equivalents. Casing attacks are the fourth: the dotless ı (U+0131) normalizes to I after .upper(), and the Kelvin sign K (U+212A) normalizes to k after .lower(), both bypassing WAF checks that run before case normalization. The fifth class uses combining diacritics: > combined with a combining slash produces , which some parsers re-normalize back to >, bypassing HTML injection filters.

The updated ActiveScan++ extension includes detection for these patterns. For code review, the signal is simple: if the security check runs before normalization, it's vulnerable.

Novel SSRF Technique Involving HTTP Redirect Loops

Shubs research started with a blind SSRF and wanted full-read. The experiments showed distinct error behaviors: a single redirect returned "Exception: Invalid JSON" many redirects (up to 30) returned "Network Exception," and a 500 status in the response chain returned the full response body. That error state transition was the key.

The working technique uses an escalating redirect chain: 301 → 302 → ... → 310, with the final redirect pointing to AWS metadata. Full-read SSRF works. Only responses from redirect number 305 onward are visible, which suggests a mismatch between libcurl's max-redirect setting and the application's error handling. The application catches a different error class once the redirect limit is exceeded and exposes the response body instead of a generic error.

This technique has worked across multiple targets and tech stacks since its discovery. The hunting approach: whenever you have blind SSRF, throw redirect chains at it with escalating status codes. Error state transitions between "exception type A" and "exception type B" can flip a blind SSRF to full-read.

ORM Leaking More Than You Joined For

Alex Brown frames ORM leaks as server-side XS-leaks, and calls it the new SQLi. The attack surface is any application that exposes filtering with field and operator syntax on search, sort, or query endpoints. Key signals in the wild include patterns like email[starts_with]=, resetToken[not]=E, or double underscores in query parameters.

The URL-encoded format resetToken[not]=E maps to the JSON representation {"resetToken":{"not":"E"}}, which can bypass middleware JSON validation that doesn't account for bracket-encoded operators. Even without a starts_with operator, logical operators like gt and lt enable binary search on field values. Database collation matters: case sensitivity and sort order vary by database engine, so the binary search has to be calibrated accordingly.

Justin demonstrated a combination attack: ORM injection paired with boolean error-based blind SSTI on Microsoft Graph OData. The payload startswith(ExternalId,'59d') and ((UserName eq '[email protected]') and (FirstName eq 'FalseValueHere' or Id div 0 eq 1)) creates a blind boolean oracle via OData, exfiltrating data character by character even after direct JSON responses were blocked. The pattern generalizes: any ORM or query language that exposes operators to user input is a candidate.

Successful Errors: New Code Injection and SSTI Techniques

Vladislav Korchagin took SQL injection methodology and applied it systematically to server-side template injection, documenting payloads across every major template engine. The first technique is error-based SSTI: trigger a verbose template engine error that leaks data in the error message itself. In Python, getattr(obj, payload) produces an error like "str has no attribute <output>" where the output contains file contents. Unlike integer conversion tricks, this approach has no size truncation.

The second technique is boolean error-based blind SSTI. The expression (1/(expression_evaluating_to_0)) triggers a division by zero, which returns HTTP 500. That's a binary oracle: true conditions evaluate to a non-zero value and return normally, false conditions hit division by zero and return 500. The universal detection polyglot (1/0).zxy.zxy triggers an error in every tested language and template engine, making it a reliable fingerprinting payload.

Coverage includes Python, PHP, Java, Ruby, Node.js, and Elixir with both language-specific and generic payloads. Everything is integrated into the SSTImap tool, with improvements pushed the same day this episode recorded. For hunters, this research turns SSTI from a "find the magic string" exercise into a systematic extraction methodology with blind, error-based, and boolean-blind variants, exactly mirroring the evolution SQLi went through years ago.

And that's it for today, keep hacking !

Resources