Introduction: The Silent Threat Lurking in Your Web Applications
Imagine a vulnerability that allows attackers to reach into your internal network, access sensitive data, or even delete critical resources—all by simply manipulating a URL. This isn’t science fiction; it’s Server-Side Request Forgery (SSRF), one of the most underrated yet dangerous vulnerabilities in modern web applications.
SSRF attacks exploit trust relationships between servers, turning benign features like stock checkers or screenshot tools into weapons for attackers. In this blog, we’ll dissect SSRF through hands-on labs and a real-world challenge, uncovering techniques like DNS rebinding, blacklist bypasses, and open redirect hijacking. Whether you’re a bug bounty hunter, developer, or security enthusiast, you’ll walk away with actionable insights to exploit—and defend against—these stealthy attacks.
Blackbox Testing
Lab 1: Basic SSRF against the local server
🔗 Lab URL - https://portswigger.net/web-security/ssrf/lab-basic-ssrf-against-localhost
After accessing the labs, after few clicks, we see an option called check stocks
. Now unlike other requests in the web-site, this seems to be interesting request as it’s a POST
request. Looking at the request we see stockApi
parameter, that accepts an URL. Now what if we can change the URL to fetch some internal resource Well in that case this website is vulnerable to SSRF
. Let’s try it out
Click on any URL like this and click on check stock
and you see POST
request sent to the server. Take a look on the burpsuite.
https://0a2500c90385725e849b7c4a00ad000c.web-security-academy.net/product?productId=1
If we change the stock API to the following options we get the access to admin panel.
http://localhost
http://127.0.0.1
Now let’s go ahead an access /admin
and we see if we check on pretty
view option in the response, then we see exact path for deleting the carlos user.
http://localhost/admin
http://127.0.0.1/admin
Visiting the endpoint we delete the user.
http://localhost/admin/delete?username=carlos
http://127.0.0.1/admin/delete?username=carlos
Now click on follow redirection
and once the request has been sent then we would have successfully solved the lab.
Lab 2: Basic SSRF against another back-end system
🔗 Lab URL - https://portswigger.net/web-security/ssrf/lab-basic-ssrf-against-backend-system
Now we are given an internal IP address of 192.168.0.1
and port number 8080
. Now we can either fuzz for other ports, but here in the lab description we are asked to check for the last octet so grab a number list from 1 to 255 and fuzz the last octet on the intruder.
Once we have started fuzzing, we see that last octet with value 82
returns 404 response, but despite the error, we are able to access /admin
pannel.
Now let’s fire our repeater and access http://192.168.0.82:8080/admin
. We are able to access the admin panel.
Now let’s repeat steps from the previous labs, click on pretty, grab the link to delete the carlos user, and with this we solve the lab.
http://192.168.0.82:8080/admin/delete?username=carlos
If we access the above endpoint, we solve the lab. Just like the previous lab we get an redirect and on following the redirection we solve the lab.
Lab 3: SSRF with blacklist-based input filter
🔗 Lab URL - https://portswigger.net/web-security/ssrf/lab-ssrf-with-blacklist-filter
In this lab we start off with similar note, just like previous ones, and we see the post request with stock options
and we see stock API
. We see that localhost
and 127.0.0.1
are black listed. Now we can evade these basic filters with addresses like:
http://127.1/
http://2130706433/
The last one is the decimal representation of the IPv4
address. You can visit websites like https://www.ipaddressguide.com/ip to convert an IP address to decimial. Notice that in CTFS the second one should work but in our lab it does not work.
So we go ahead with http://127.1
and we get access to admin panel. If we try and access http://127.1/admin
we see an option to delete the carlos user, just click on pretty and grab the href
link.
If things were that simple then we there would not be fun right? we see that /admin
is an blacklisted character, so in the word admin
if we can double URL encode the character we get %25%36%31
and we use this character to bypass the restriction. We grab the link to delete the carlos user and now the final payload for accessing internal resource would look something like the following.
http://127.1/%25%36%31dmin/delete?username=carlos
On changing the stockapi to the above URL we get an redirection and with this we are able to solve the lab.
Lab 4: SSRF with filter bypass via open redirection vulnerability
🔗 Lab URL - https://portswigger.net/web-security/ssrf/lab-ssrf-filter-bypass-via-open-redirection
In this lab we see an option to view the next product at the end of each page, and once we click we see the following GET request made.
/product/nextProduct?currentProductId=2&path=/product?productId=3
Notice the &path
and we see that if we replace it with any URL this end point is vulnerable to open redirect vulnerability. Now the above endpoint makes an 302 request or redirect and if we follow along we reach this endpoint.
/product?productId=3
Let’s take this parameter and then join this parameter inside the stock API
parameter and let’s try and access the admin dashboard. Notice the /product?productID
from redirect and the &path
from above request? let’s fuse both requests together and make an new parameter in stockAPI.
stockApi=/product?productId=3&path=http://192.168.0.12:8080/admin/
Visiting this endpoint, from the response section if we look at the source code then we see link for deleting the carlos user which is as follows.
/http://192.168.0.12:8080/admin/delete?username=carlos
Now the final payload for the stockApi
would look something like the below following.
stockApi=/product?productId=3&path=http://192.168.0.12:8080/admin/delete?username=carlos
With this we should solve the lab. This was really an out of box situation, which forced me to understand how we can tamper the view next page
option to get what we want. This lab opened door to crafting new http parameters
and coming up with innovative ways to approach solving this lab.
Whitebox Testing
🔗 Lab URL - https://app.hackthebox.com/challenges/baby%2520CachedView
Baby Cached Web
Below is a revised version of your “Baby Cached Web” section with updated code snippets and explanations that reflect the easy challenge’s source code. This version also includes an engaging introduction and conclusion to improve the overall flow.
Overview of the Challenge
In this challenge, we examine a Flask web application with two main endpoints:
/cache
: Accepts a URL via a JSON POST request, loads the page using a headless browser (Selenium with Firefox), takes a screenshot, and caches the image.
/flag
: Returns a secret flag image but is strictly accessible only from localhost.
At first glance, the application seems secure because it enforces the following checks (as seen in CachedWeb/web_cached_web/challenge/application/util.py
)
URL Scheme Check
The code ensures that only URLs with http
or https
schemes are allowed.
if scheme not in ['http', 'https']:
return flash('Invalid scheme', 'danger')
Internal IP Check
When processing the request the application first uses socket.gethostbyname
to resolve the domain then confirms the obtained IP address does not belong to internal ranges starting with 127.0.0.0/8
and 10.0.0.0/8
.
def ip2long(ip_addr):
return struct.unpack('!L', socket.inet_aton(ip_addr))[0]
def is_inner_ipaddress(ip):
ip = ip2long(ip)
return ip2long('127.0.0.0') >> 24 == ip >> 24 or \
ip2long('10.0.0.0') >> 24 == ip >> 24 or \
ip2long('172.16.0.0') >> 20 == ip >> 20 or \
ip2long('192.168.0.0') >> 16 == ip >> 16 or \
ip2long('0.0.0.0') >> 24 == ip >> 24
if is_inner_ipaddress(socket.gethostbyname(domain)):
return flash('IP not allowed', 'danger')
Localhost Protection for /flag
A decorator in util.py
ensures that only requests originating from 127.0.0.1
and without a referrer can access the /flag
endpoint.
def is_from_localhost(func):
@functools.wraps(func)
def check_ip(*args, **kwargs):
if request.remote_addr != '127.0.0.1' or request.referrer:
return abort(403)
return func(*args, **kwargs)
return check_ip
The route then uses this decorator. This can be found inside CachedWeb/web_cached_web/challenge/application/blueprints/routes.py
@web.route('/flag')
@is_from_localhost
def flag():
return send_file('flag.png')
The Vulnerability: DNS Rebinding + TOCTOU
The image from the source code reveals us a major hints on what this application might be vulnerable to.
The application suffers from a DNS rebinding attack and a acrid Time-of-Check to Time-of-Use (TOCTOU) race condition despite its security safeguards. Here’s why:
Initial DNS Resolution Check
The cache_web
function employs socket.gethostbyname(domain)
to find the domain resolution while ensuring no internal IP addresses remain pointed to it. The DNS check happens once when the URL is still in possession of the system prior to Selenium receiving it.
Separate DNS Resolution by Selenium
The browser runs separate DNS queries when the instruction driver.get(url)
activates in the serve_screenshot_from
function. Attackers who exploit DNS rebinding can fool the first IP address check of Selenium even though they redirect domain resolution later to 127.0.0.1 and internal network addresses.
TOCTOU Window
The period between the DNS check at time-of-check and the actual browser request at time-of-use allows attackers to create a race condition. The window between discussions allows hackers to modify DNS records and send traffic to a server at 127.0.0.1
or internal endpoint /flag
.
DNS Rebinding Explained (in Simple Terms):
DNS Rebinding:
A website obtains deception through altering its IP address after initial computer acceptance.
Runtime security platforms recognize DNS rebinding as a technique explained by Wikipedia to circumvent the same-origin policy through unexpected domain IP address resolution by browsers.
TOCTOU Race Condition Explained:
TOCTOU (Time-of-Check to Time-of-Use):
Such condition arises when testing a status requires a noticeable duration before applying its results. The system operates on outdated or manipulated data since a change occurs within the defined interval such as a DNS response.
When checking door locks to leave a space remains it would be helpful to confirm security status first. Your assumption about security becomes invalid when the locking condition changes between your check and someone else accessing the lock. DNS changes between when the browser conducts checks and makes its request provide the attacker with a potential period of access.
Exploitation Steps:
Craft a Malicious URL:
The attacker conducts the IP check with a DNS rebinding service that points to an initially safe IP address but this service later redirects to 127.0.0.1
just before Selenium executes the URL.
Bypass the IP Check:
The DNS lookup operated from cache_web
takes place at a time when it fails to detect the IP address change because of which the IP check successfully finishes.
Trigger the TOCTOU Vulnerability:
An internal target becomes the domain resolution point after Selenium completes its domain retrieval process. Through this method the attacker achieves access to the protected endpoint that resides at /flag
.
Getting the flag
- We will first find the google’s IP address to rebind the local host address. We can use the
nslookup
command to find google’s IP address
❯ nslookup google.com
Server: 127.0.0.53
Address: 127.0.0.53#53
Non-authoritative answer:
Name: google.com
Address: 142.250.195.110
Name: google.com
Address: 2404:6800:4007:81b::200e
- Now we will use a website like
https://lock.cmpxchg8b.com/rebinder.html
to perform rebinding attack.
- Copy the address and paste it in the input box several times till we trigger the race condition and get the flag. Ideally 3 times should be enough but keep doing until we get the flag
http://7f000001.8efac36e.rbndr.us/flag
The Baby Cached Web challenge illustrates how even seemingly robust SSRF defenses can be undermined through DNS rebinding and TOCTOU race conditions. By exploiting the gap between the initial DNS check and the subsequent browser request, an attacker can manipulate the DNS resolution to access internal endpoints intended to be protected—like retrieving the secret flag image. This challenge serves as a powerful reminder that security measures must consider dynamic network behaviors, not just static input validation
Final Words: Turning Knowledge Into Defense
SSRF isn’t just a vulnerability—it’s a gateway to your internal infrastructure. From bypassing blacklists with 127.1
to weaponizing DNS rebinding, we’ve seen how attackers pivot from simple URL parameters to full-scale breaches.
Key Takeaways:
Always validate and sanitize user-supplied URLs.
Assume localhost restrictions can be bypassed; enforce strict allowlists.
Monitor DNS resolution gaps in TOCTOU-prone workflows.
To mitigate against these attacks feel free to refer these reference links to understand and to defend better against SSRF
attacks
# How to prevent SSRF Attacks in Node.js
[](https://www.youtube.com/@Snyksec)
# What functionalities are vulnerable to SSRFs? Case study of 124 bug bounty reports
[](https://www.youtube.com/@BugBountyReportsExplained)
https://medium.com/@ajay.monga73/defending-against-ssrf-understanding-detecting-and-mitigating-server-side-request-forgery-f2d1fd62413d [Has some good java code snippets]
To sum up here are some of the defences you can start implementing in your web application to stay protected against attacks like SSRF
.
- Input Validation & Sanitization
Users must submit valid URLs through the schema validation library Zod to maintain proper check parameters. The validation schema features one requirement for strings that need to adopt valid URL properties and restrict access to HTTPS protocol usage. The system should block all inputs that do not conform to valid URL structures or permitted schemas including file://
and ftp://
.
Implementation:
const urlSchema = z.string().url().startsWith("https://");
try { urlSchema.parse(userInput); }
catch (e) { blockRequest(); }
The parsing system enables Zod to detect invalid input through structured error reporting so that it prevents SSRF attacks by authorizing sanitized URLs with valid protocols.
- Enforce URL Schemas
The Zod function startsWith("https://")
enables HTTPS protocol protection to block all domains lacking HTTPS prefixes. The measure prevents security risks from internal protocol handlers that could enable them to access local files through commands (such as file://).
The system should reject URLs containing encoded characters like %0D%0A
for CRLF injection through regulation patterns and normalization techniques.
- Domain Allowlisting
The implementation phase should use a fixed list of trusted domains which includes the entry "api.unsplash.com"
. A security check involving URL.hostname
extraction with strict comparison should validate all URLs against the list of trusted domains.
Code Example:
const userHost = new URL(userInput).hostname;
if (!trustedDomains.includes(userHost)) throw Error("Untrusted domain");
Such security controls require connectivity to configuration management systems including Consul or databases to maintain optimal lookups for dynamic address registration.
- Web Application Firewalls (WAF)
Cloud-based WAFs including AWS WAF or Cloudflare should monitor and block requests directed to internal IP ranges (RFC 1918) and loopback addresses (127.0.0.1
and AWS/GCP metadata endpoints (169.254.169.254
).
Rulesets:
A. The WAF system should block all requests that have a Host
header which resolves to any internal IP address.
B. WAF systems should detect unauthorized SSRF patterns that appear as localhost
or admin.internal
.
C. Can limit the number of URL requests to prevent scanning activities through endpoint rates.
- Dependency Management
The implementation of Snyk or Dependabot as tools for automating vulnerability scans should be integrated. The system must detect outdated is-url
versions that contain ReDoS vulnerabilities and automatically force upgrades to new versions.
Pipeline Integration:
# CI/CD step
snyk test && snyk monitor
Audit libraries that manage network operations need evaluation to prevent default settings such as following redirected connections which pose risks to third-parties.
- TOCTOU Mitigation
The DNS Rebinding defense resolves the domain to an IP during validation after which the same IP can be reused for subsequent requests. The server should store obtained IP addresses in temporary cache to defend against attackers who could change DNS records after validation completion.
Code Snippet:
const resolvedIP = dns.resolve(userHost);
if (isInternalIP(resolvedIP)) blockRequest();
// Use resolvedIP for the actual fetch, not the user-provided URL
fetch(`https://${resolvedIP}/path`)
- Network Hardening
The application server should not reach backend services such as databases or metadata APIs which must reside within separate private subnets.
Egress Controls: Restrict outbound traffic from the Node.js process to only necessary domains/IPs via firewall rules (e.g., iptables, cloud security groups).
Ready to test your skills? Test the laboratories by hand and do not underestimate the defensive power of paranoia during your SSRF defenses. You should combine curiosity with ethical breakage of systems while building multiple security layers to protect yourself.