Trust Issues: The RustFS IP Spoofing Bypass (CVE-2026-21862)
Feb 3, 2026·6 min read·6 visits
Executive Summary (TL;DR)
RustFS allowed attackers to bypass IP allowlists by spoofing the 'X-Forwarded-For' header. Fixed in alpha.78 by validating against the actual TCP socket address.
RustFS, a distributed object storage system designed for safety and performance, fell victim to one of the oldest tricks in the book: trusting client input. Prior to version alpha.78, the system determined a user's source IP address by blindly believing the 'X-Forwarded-For' header. This allowed attackers to bypass IP-based Access Control Lists (ACLs) by simply asking nicely—or rather, by spoofing a trusted IP address in their HTTP headers.
The Hook: The False Promise of 'Safe' Languages
We love Rust. It gives us memory safety, concurrency without data races, and a compiler that yells at us like a disappointed parent. But here is the hard truth: Rust prevents memory corruption, not logic corruption. You can write the most memory-safe code in the world, but if your logic decides to hand the keys to the castle to anyone who asks, you are still going to get owned.
RustFS is a modern distributed object storage system—think S3, but written in Rust. It supports AWS-style policies, including the aws:SourceIp condition. This is a critical feature for enterprises. You might have a bucket named corporate-secrets and a policy that says, "Only allow access if the request comes from the VPN (10.0.0.0/24)." It is a standard defense-in-depth measure. Or at least, it is supposed to be.
CVE-2026-21862 is a stark reminder that while we have moved past buffer overflows on the stack, we haven't moved past developers trusting user input. This vulnerability isn't about smashing the heap; it is about a fundamental misunderstanding of how the web works. The application assumed that the HTTP headers it received were the gospel truth, ignoring the fact that the client controls the headers entirely.
The Flaw: When 127.0.0.1 is Just a Suggestion
To enforce an IP allowlist, the server needs to know who is calling. In the pre-patch versions of RustFS, the function get_condition_values in src/auth.rs was tasked with this job. It needed to extract the SourceIp to compare it against the bucket policy.
Here is where the logic went off the rails. The developers prioritized convenience over security. In modern infrastructure, services often sit behind load balancers (like Nginx or AWS ELB). These balancers terminate the TCP connection and forward the request to the backend, adding the original client's IP to the X-Forwarded-For (XFF) header. The backend sees the load balancer's IP on the TCP socket, so it looks at the header to find the "real" user.
However, RustFS implemented this blindly. It did not check who sent the header. It didn't verify if the request came from a trusted proxy. It just grabbed the value. This creates a classic CWE-290: Authentication Bypass by Spoofing. If I connect directly to the RustFS server (bypassing the load balancer, or if the server is exposed directly) and send X-Forwarded-For: 127.0.0.1, the code goes, "Ah, the administrator is logging in from localhost! Welcome, sir."
The Code: The Smoking Gun
Let's look at the vulnerable code in src/auth.rs. It is almost painful in its innocence. The goal is to get the remote address, but look at the order of operations:
// The Vulnerable Logic
let remote_addr = header
.get("x-forwarded-for")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.split(',').next()) // Take the first IP in the list
.or_else(|| header.get("x-real-ip").and_then(|v| v.to_str().ok()))
.unwrap_or("127.0.0.1"); // Default to localhost if nothing found? Ouch.Do you see the problem? It checks the headers first. If X-Forwarded-For exists, it uses it. No questions asked. There is no validation that the connection actually came from a trusted ingress point.
The fix, applied in commit b4ba62fa33, completely inverts this relationship. Instead of trusting headers, the patch changes the architecture to pass the physical TCP socket address up through the middleware stack.
// The Fixed Logic (Simplified)
// 1. In the HTTP server layer, capture the real peer address from the socket
let remote_addr = match socket.peer_addr() {
Ok(addr) => Some(RemoteAddr(addr)),
Err(_) => None,
};
// 2. In auth.rs, use that socket address, NOT the header
let remote_addr_s = remote_addr
.map(|a| a.ip().to_string())
.unwrap_or_default();
// Only use XFF if you have a strict trusted proxy configuration (not shown here),
// otherwise default to the physical wire address.By anchoring the identity to the TCP socket (socket.peer_addr()), the application no longer cares what lies the client puts in the HTTP headers.
The Exploit: Knocking on the Back Door
Exploiting this is trivially easy. You do not need a debugger, you do not need to craft shellcode, and you do not need to spray the heap. You just need curl.
Scenario: A sysadmin has set up a RustFS bucket named backup-2025 containing sensitive database dumps. They applied a policy: "Condition": {"IpAddress": {"aws:SourceIp": "192.168.10.50"}}. This IP corresponds to the internal admin console.
If you try to access it normally from the public internet:
curl -v http://rustfs.target.com/backup-2025/dump.sql
> 403 ForbiddenThe server sees your public IP, checks the list, and denies access. Working as intended.
The Attack: Now, we lie. We simply tell the server we are the admin.
curl -v -H "X-Forwarded-For: 192.168.10.50" http://rustfs.target.com/backup-2025/dump.sql
> 200 OKWhy it works: The vulnerable get_condition_values function sees the header, parses 192.168.10.50, matches it against the policy, and unlocks the door. It is like walking into a bank vault and telling the guard, "I am the manager," and him believing you because you wore a fake mustache.
The Impact: Why Should We Panic?
The impact here depends entirely on how heavily an organization relies on IP allowlisting. In many cloud-native environments, IP restrictions are a secondary control (Defense in Depth). If you still need a valid Access Key and Secret Key to sign the request, this spoofing vulnerability might just let you bypass a "VPN Required" check, but not the authentication itself.
However, IP-based policies are often used as the primary authentication mechanism for legacy systems or internal service-to-service communication. For example, a policy might allow PutObject only from the CI/CD server's IP. If an attacker can spoof that IP, they can overwrite artifacts, inject malicious code into build pipelines, or delete backups.
Furthermore, this affects aws:SourceIp conditions in all policy evaluations. If an admin has a "Deny all unless IP is X" policy, that safety net is now gone. The CVSS score of 7.7 reflects the High Integrity impact—the system is doing exactly what it is told not to do.
The Fix: Closing the Window
If you are running RustFS, you need to update to version alpha.78 or later immediately. The patch changes the source of truth for IP addresses to the underlying transport layer, which cannot be spoofed (without complex TCP sequence number prediction attacks that are out of scope for a simple HTTP request).
If you cannot patch immediately, you have one architectural workaround: The Strict Proxy.
Place the RustFS service behind a trusted reverse proxy (like Nginx) that is configured to strip all incoming X-Forwarded-For and X-Real-IP headers from the client, and then insert its own. If the proxy blindly passes existing headers through (which is the default in many configs), you are still vulnerable. You must ensure your ingress edge sanitizes these headers before they reach the vulnerable RustFS instance.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:PAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
RustFS rustfs | < alpha.78 | alpha.78 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-290 |
| Attack Vector | Network (Layer 7) |
| CVSS v4.0 | 7.7 (High) |
| Impact | Authorization Bypass |
| Exploit Status | Proof of Concept (PoC) Available |
| Components | get_condition_values (auth.rs) |
MITRE ATT&CK Mapping
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.