-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Potential security issue (X-Real-IP spoofing) #634
Description
I've been reviewing the RustDesk server source code and noticed that the WebSocket implementation seemingly trusts the X-Real-IP and X-Forwarded-For HTTP headers without validation. I'm trying to determine if this is an intentional design decision or a potential security vulnerability. I'm guessing it's to do with handling if the RustDesk server is behind a reverse-proxy or not, but I don't see any validation performed in that case. This would be similar to CVE-2025-6504, CVE-2025-69203, CVE-2024-0970, and many others.
I'd appreciate guidance on whether this constitutes a valid security finding since I lack some of the context of the development team, and this could totally be a nothing-burger.
The code in question
The source in question:
https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L1160-L1177
https://github.com/rustdesk/rustdesk-server/blob/master/src/relay_server.rs#L400-L416
When handling WebSocket connections, the server extracts the client IP from HTTP headers. The server then uses this addr throughout the codebase for various operations. I don't see any validation to check if the header value matches the actual TCP peer address.
Where does this spoofed addr get used?
I traced it through the codebase to see what actually relies on it:
Rate limiting - When processing RegisterPk messages https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L350-L352
The check_ip_blocker function (lines 891-919) maintains per-IP counters for rate limiting. When processing RegisterPk messages, this uses the IP from addr.
Potential Impact: An attacker could bypass rate limiting by changing the X-Real-IP header on each request.
Limitation: Only applies to RegisterPk messages, not basic WebSocket connections.
2. Internal Tracking Structures
Location: https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L719-L733 (Punch_REQ)
Location: https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L892-L917 (IP_BLOCKER)
Location: https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L1017-L1048 (IP_CHANGES)
These data structures store the IP for monitoring and security purposes.
Potential Impact: An attacker could poison these internal records with false IPs, affecting monitoring and security decisions.
Limitation: These structures are only viewable via localhost admin interface or management command port (not sure what this is called).
3. Authentication Failure Logging
Location: https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L691
Potential Impact: Logs would show the spoofed IP rather than the real attacker's IP, potentially causing false attribution.
Limitation: Only logs on authentication failures; most operations don't log IPs at all. (Pro-only feature, since OSS doesn't use auth as far as I can tell?)
4. IP Change Detection
Location: https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L605
Potential Impact: IP change detection could be triggered or evaded using spoofed headers.
Limitation: Only triggers when a peer's IP actually changes.
5. Blocklist/Blacklist Checks
Location: https://github.com/rustdesk/rustdesk-server/blob/master/src/relay_server.rs#L382-L385
Potential Impact: A blocked IP could potentially evade the block by spoofing a different IP.
Limitation: Unclear how effective this would be in practice.
What Does NOT Use X-Real-IP?
Through testing and code review, I found that some security features correctly use the actual TCP peer address:
1. Admin Commands (Secure)
Location: https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L1104-L1117
This checks the actual TCP connection source, not WebSocket headers. Admin commands are not vulnerable to X-Real-IP spoofing.
2. ConfigUpdate Messages (Secure)
Location: https://github.com/rustdesk/rustdesk-server/blob/master/src/rendezvous_server.rs#L445-L465
While this checks is_loopback(), the ConfigureUpdate message is only processed via UDP (in handle_udp), not via WebSocket. The addr here comes from the UDP packet source, not HTTP headers. This is not vulnerable to X-Real-IP spoofing.
Questions for Maintainers
-
Is X-Real-IP trust intentional?
Is the server designed to be deployed behind a reverse proxy that validates these headers? If so, should this be documented as a deployment requirement? Or should this functionality have an opt-in switch to allow users to specify if these headers should be trusted in the case of a reverse proxy? -
What is the intended deployment model?
- Direct internet exposure?
- Behind nginx/reverse proxy?
- Behind cloud load balancer?
-
Are the security implications understood and correct?
Specifically:- Rate limiting bypass via RegisterPk messages
- IP-based access control evasion
- Audit log integrity concerns
Proposed Fix (If Considered a Vulnerability)
Option 1: Remove X-Real-IP Trust (Most Secure)
// REMOVE the callback that reads headers
let ws_stream = tokio_tungstenite::accept_async(stream).await?;
// Use actual TCP peer address
let addr = stream.peer_addr()?;This ensures IP-based security decisions use the actual source.
Option 2: Add Configuration Option
// Only trust X-Real-IP if explicitly enabled
if config.trust_proxy_headers {
// Read X-Real-IP
} else {
// Use peer_addr()
}With clear documentation about the security implications.
Option 3: Validate Header Against Peer Address
let real_ip = headers.get("X-Real-IP");
let peer_ip = stream.peer_addr()?.ip();
if let Some(ip) = real_ip {
// Only accept if from trusted proxy IP
if is_trusted_proxy(peer_ip) {
addr = format!("{ip}:0").parse().unwrap_or(addr);
}
}Option 4: Document as Deployment Requirement
If the current behavior is intentional, clearly document:
- Server MUST be deployed behind reverse proxy
- Proxy MUST strip/validate X-Real-IP headers
- Direct internet exposure is NOT supported