July 14, 2026
OWASP Top 10 Labs Series #10 - SSRF: Two Labs, Two Ways a Server Fetches What It Shouldn’t
Series: I’m working through all ten OWASP Top 10 vulnerability categories hands-on using Juice Shop and PortSwigger, and documenting each…

By Om Fulsundar
6 min read
Series: I'm working through all ten OWASP Top 10 vulnerability categories hands-on using Juice Shop and PortSwigger, and documenting each one. SSRF falls under A10:2021 (Server-Side Request Forgery). This entry covers two PortSwigger labs -one basic, one with a blacklist filter to bypass. Follow along for the full A01 through A10 series.
The Bug -What Is SSRF?
Server-Side Request Forgery is when an attacker makes the server send HTTP requests on their behalf. The application has a feature that fetches a URL — and that URL is user-controlled. Instead of pointing it at the intended destination, you point it at something internal. The server makes the request from its own network, with its own trust level, and returns whatever it gets back.
The impact is significant because internal services often have zero authentication. They trust requests from localhost or the internal network implicitly. From the outside you'd be blocked. But if you can make the server ask on your behalf, you're inside.
Lab 1 -Basic SSRF Against the Local Server
Platform: PortSwigger Web Security Academy
Finding the Attack Surface
Opened a product page and clicked Check stock. Burp Intercept caught the request before it left the browser.
The stockApi parameter contained a full URL the server would fetch server-side:
stockApi=http%3A%2F%2Fstock.weliketoshop.net%3A8080%2Fproduct%2Fstock%2Fcheck...stockApi=http%3A%2F%2Fstock.weliketoshop.net%3A8080%2Fproduct%2Fstock%2Fcheck...User-controlled URL passed to a server-side fetch. That's the entry point. Sent it to Repeater.
Redirecting the Server to Localhost
Changed stockApi to:
stockApi=http://localhost/adminstockApi=http://localhost/admin
The server fetched http://localhost/admin from its own loopback interface and returned the HTML response directly. The admin panel - which would have blocked an external visitor - was wide open to requests coming from localhost. I was reading internal admin HTML through a stock check endpoint.
Reading the Admin Panel Response
Scrolled through the returned HTML.
The response included the full admin panel markup — user list, navigation links, and the delete endpoints. The application handed me the internal API structure through its own response.
Executing the Delete via SSRF
Modified stockApi to target the delete endpoint directly:
stockApi=http://localhost/admin/delete?username=carlosstockApi=http://localhost/admin/delete?username=carlos
302 Found. The server executed the delete action - not me, but the server itself acting on my behalf against its own internal admin endpoint.
Lab solved. The message on the page makes the vulnerability design explicit: the admin panel trusts loopback requests. SSRF weaponizes that trust assumption.
Lab 2 — SSRF With Blacklist-Based Input Filter
Platform: PortSwigger Web Security Academy
The second lab adds a server-side filter that's supposed to block SSRF. The approach shifts from straightforward exploitation to filter analysis and bypass.
Hitting the Blacklist
Same setup — intercepted the stock check, sent to Repeater, replaced stockApi with [http://localhost/admin](http://localhost/admin.).
Blocked. The filter detected localhost in the URL and rejected it. Now the question was: what exactly is the filter checking, and can I represent the same address in a way it doesn't recognize?
Finding What Gets Through
Tested alternate representations of 127.0.0.1:
http://127.1/returned 200 OK - the shortened IP bypassed the localhost checkhttp://127.1/adminwas still blocked - so the filter also matched the wordadminin the path
Two things on the blacklist: the string localhost and the string admin. Both needed bypassing independently.
The Double Encoding Bypass
The path filter checks for admin literally. But what if admin isn't written as admin when the filter reads it?
URL encoding turns a into %61, making admin become %61dmin. Submit %2561dmin - where %25 is the encoded form of % - and the filter sees %61dmin (not admin), passes it, and the server decodes it a second time to admin before making the request.
Payload:
stockApi=http://127.1/%2561dminstockApi=http://127.1/%2561dmin
Admin panel returned. 127.1 bypassed the localhost check, %2561dmin bypassed the path check. The server decoded the double-encoded path and fetched the internal admin page.
Deleting the User Through the Bypass
Applied the same encoding to the delete endpoint:
stockApi=http://127.1/%2561dmin/delete?username=carlosstockApi=http://127.1/%2561dmin/delete?username=carlos
302 Found. Delete executed through the bypass payload.
Both labs solved. The blacklist provided a speed bump, not protection.
The Full Attack Chains
Lab 1: Intercept stock check → identify user-controlled stockApi URL → replace with http://localhost/admin → server fetches internal admin page → read HTML for delete endpoints → send http://localhost/admin/delete?username=carlos → 302 confirms deletion → lab solved.
Lab 2: Same starting point → http://localhost/admin blocked → test alternate IP representations → http://127.1/ passes → /admin path blocked → double URL-encode the path (%2561dmin) → http://127.1/%2561dmin returns admin HTML → apply same encoding to delete endpoint → 302 confirms deletion → lab solved.
What I Didn't Test But Exists
AWS Metadata Service (IMDS) via SSRF
The most impactful real-world SSRF target is http://169.254.169.254/latest/meta-data/. On AWS EC2 instances, this endpoint returns IAM credentials, instance metadata, and security tokens - all unauthenticated, accessible only from the instance itself. An SSRF vulnerability in an application running on EC2 hands an attacker the cloud credentials for the underlying instance. This is exactly how the Capital One breach worked in 2019.
Internal Network Port Scanning
With a controllable stockApi parameter you can point it at internal IP ranges and observe response times and status codes. A 200 means the port is open, a connection refused or timeout means it isn't. This turns SSRF into an internal network scanner - you can map what services are running internally without ever touching the network directly.
Protocol Smuggling
SSRF isn't limited to HTTP. Depending on the fetch library, you can sometimes use other schemes: file:///etc/passwd to read local files, gopher:// to send raw TCP data to internal services, or dict:// to interact with dictionary servers. These expand SSRF from internal HTTP access to arbitrary internal communication.
Whitelist Bypass via Open Redirect
Some SSRF filters use allowlists — only specific domains permitted. If any page on the allowed domain has an open redirect, you can chain them: point stockApi at the allowed domain's redirect endpoint, which forwards to an internal IP. The filter sees an approved domain, the server follows the redirect, and you land at the internal target.
Blind SSRF
In blind SSRF the server makes the request but doesn't return the response. Detection relies on out-of-band channels — Burp Collaborator or a webhook to confirm the server made an outbound request to your controlled domain. Even without reading the response, blind SSRF confirms the vulnerability and can be used for internal port scanning via timing side-channels.
Real Breaches This Maps To
Capital One, 2019 — A misconfigured WAF combined with SSRF allowed an attacker to hit the AWS metadata endpoint at 169.254.169.254 and retrieve IAM role credentials. Those credentials were used to access over 100 million customer records in S3. SSRF to cloud credential theft to mass data exfiltration, all through one parameter.
GitLab, 2021 — CVE-2021–22214 was an SSRF vulnerability in GitLab's Prometheus integration. An unauthenticated attacker could make the GitLab server send requests to internal services, exposing internal infrastructure details.
Shopify, 2015 — An SSRF bug in Shopify's image import feature allowed scanning internal network services by supplying internal IPs as image URLs. The server would attempt to fetch the image and error responses revealed open ports.
Remediation
Three Takeaways
The stock check feature had no reason to accept arbitrary URLs. It should only accept requests to its own known stock service. The moment user input controls the destination of a server-side fetch, SSRF is possible. Functionality scope matters as much as input validation.
Blacklists fail the moment you think of a representation the developer didn't. localhost, 127.0.0.1, 127.1, 2130706433, 0177.0.0.1, ::1 - all resolve to loopback. A blacklist has to enumerate every representation. An allowlist only defines what's permitted. One approach scales, the other doesn't.
The double encoding bypass is the most transferable lesson from Lab 2. %2561dmin works because the filter decodes once and the server decodes again. Anytime a security control checks a value before the application processes it, there's a potential mismatch in how many decode passes each side performs. That gap is a bypass.
Lab: PortSwigger Web Security Academy — SSRF Labs | Tools: Burp Suite Community, Browser DevTools | Category: OWASP A10:2021 — Server-Side Request Forgery (SSRF)
Part of my OWASP Top 10 Labs Series — documenting all ten categories hands-on. More writeups coming.