Feb 3, 2026·6 min read·44 visits
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.
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.
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."
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.
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 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.
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.
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:P| 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) |
An arbitrary file read and write vulnerability exists in the Model Context Protocol (MCP) server endpoints of sublinear-time-solver and consciousness-explorer. By providing unvalidated file paths to the export_state, import_state, saveVectorToFile, and loadVectorFromFile tools, local attackers can read or overwrite sensitive host files.
An Authorization Bypass Through User-Controlled Key (CWE-639 / Insecure Direct Object Reference) vulnerability exists in @arikusi/deepseek-mcp-server starting in version 1.4.2 and fixed in 1.7.0. In Streamable HTTP transport mode, a process-global SessionStore singleton allows any remote client to retrieve or modify active conversation contexts belonging to other clients.
CVE-2026-45018 is a critical command injection vulnerability in Chainlit's Model Context Protocol (MCP) stdio transport backend. By submitting a crafted JSON payload containing dangerous argument options to an unauthenticated HTTP endpoint, a remote attacker can bypass executable validation rules and run arbitrary shell commands with the privileges of the active Python process.
An unauthenticated server-side request forgery (SSRF) vulnerability exists in Chainlit versions >= 2.4.0rc0 and < 2.12.0 when the Model Context Protocol (MCP) features are enabled. This vulnerability allows remote, unauthenticated attackers to force the backend application server to initiate arbitrary HTTP/HTTPS connections to internal subnets, localhost endpoints, or cloud metadata infrastructure.
An algorithmic complexity denial of service vulnerability exists in the Python icalendar library's component equality evaluation. Due to recursive nested comparisons inside list membership operations, parsing and validating calendar components with deep nesting triggers exponential execution time, blocking application threads and consuming 100% of the available CPU core.
JupyterHub is vulnerable to an unauthenticated Denial of Service (DoS) vulnerability. Prior to version 5.5.0, form-based authenticators failed to restrict the size of the username input field on failed logins, allowing remote attackers to exhaust host storage and memory resources.