Unintended Proxy or Intermediary ('Confused Deputy')
Description
Unintended Proxy or Intermediary, also known as the 'Confused Deputy' problem, is a vulnerability where a product receives a request, message, or directive from an upstream component but does not sufficiently preserve the original source of the request before forwarding it to another entity. The intermediary component acts with its own privileges rather than the privileges of the original requester, allowing attackers to perform actions they couldn't do directly. This enables privilege escalation, access control bypass, and concealment of malicious activities by exploiting trust relationships between components.
Risk
Confused deputy vulnerabilities enable attackers to leverage the privileges and access of intermediary systems. Server-Side Request Forgery (SSRF) allows attackers to access internal resources through server requests. FTP bounce attacks use FTP servers to scan or attack systems the attacker can't reach directly. Mail servers can be exploited to probe internal networks. Web applications acting as proxies may inadvertently provide access to administrative interfaces. The risk is amplified when the intermediary has elevated privileges or access to sensitive internal networks. Attackers can also use confused deputies to hide the source of their attacks, making attribution and defense more difficult.
Solution
Enforce strong mutual authentication between all parties in a transaction chain. Preserve and forward the identity of the original transaction initiator throughout the processing chain - this identity must be immutable and verifiable by the final target. Implement the principle of least privilege so intermediaries only have access they actually need. Validate that requests are appropriate for the declared source identity. For web applications, restrict outbound requests to allowlisted destinations. Implement network segmentation to limit what internal resources intermediaries can access. Log the full chain of request origination for audit purposes.
Common Consequences
| Impact | Details |
|---|---|
| Non-Repudiation | Scope: Non-Repudiation Hide Activities - Attackers can obscure the source of requests by routing them through trusted intermediaries, making attribution difficult. |
| Access Control | Scope: Access Control Gain Privileges or Assume Identity - The intermediary's privileges and identity are used instead of the original requester's, enabling unauthorized access. |
| Integrity | Scope: Integrity Execute Unauthorized Code or Commands - Attackers can cause the intermediary to perform actions they couldn't do directly, including executing code or modifying data. |
Example Code
Vulnerable Code
# Vulnerable: Web application with SSRF vulnerability
from flask import Flask, request
import requests
app = Flask(__name__)
@app.route('/fetch')
def vulnerable_fetch():
# Vulnerable: Fetches any URL provided by user
url = request.args.get('url')
# Vulnerable: Server acts as proxy with server's privileges
# Can access internal resources user cannot reach directly
response = requests.get(url)
# Attacker provides: ?url=http://169.254.169.254/latest/meta-data/
# Server fetches AWS metadata (internal), returns to attacker
# Attacker provides: ?url=http://internal-admin.local/delete-users
# Server performs admin action with server's access
return response.text
@app.route('/proxy')
def vulnerable_proxy():
host = request.args.get('host')
port = int(request.args.get('port', 80))
path = request.args.get('path', '/')
# Vulnerable: Acts as open proxy
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
# Server's identity/IP used, not original requester's
sock.send(f"GET {path} HTTP/1.0\r\nHost: {host}\r\n\r\n".encode())
return sock.recv(4096).decode()
// Vulnerable: FTP server with bounce attack vulnerability
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
void vulnerable_ftp_port(int client_fd, char *port_command) {
// Vulnerable: Accepts any IP:port from client
// FTP PORT command: PORT h1,h2,h3,h4,p1,p2
int h1, h2, h3, h4, p1, p2;
sscanf(port_command, "PORT %d,%d,%d,%d,%d,%d",
&h1, &h2, &h3, &h4, &p1, &p2);
char target_ip[16];
sprintf(target_ip, "%d.%d.%d.%d", h1, h2, h3, h4);
int target_port = (p1 * 256) + p2;
// Vulnerable: Will connect to ANY address specified
// Attacker can use this to:
// 1. Scan internal networks (FTP server can reach them)
// 2. Attack internal services
// 3. Relay data through FTP server
data_connection = connect_to(target_ip, target_port);
// Server identity used, not client's
// Internal systems see connection from trusted FTP server
}
// Vulnerable: DMA controller confused deputy
void vulnerable_dma_transfer(uint32_t src, uint32_t dst, size_t len) {
// Vulnerable: DMA operates with DMA controller's privileges
// Not the requesting CPU's privileges
// Microcontroller with restricted access requests transfer
// DMA has unrestricted memory access
// Transfer proceeds with DMA's permissions
dma_copy(src, dst, len); // Bypasses CPU's access restrictions
}
// Vulnerable: Mail server used as proxy
public class VulnerableMailServer {
public void processVerifyCommand(String address) {
// Vulnerable: VRFY/EXPN commands used for reconnaissance
// Server checks if addresses exist, revealing info
// Attacker: VRFY [email protected]
// Server: 250 Admin User <[email protected]>
// Leaks internal user information
if (userExists(address)) {
sendResponse("250 " + getUserInfo(address));
} else {
sendResponse("550 User not found");
}
}
public void processRelayMessage(String from, String to, String message) {
// Vulnerable: Open relay
// Server acts as proxy to deliver mail anywhere
// Attacker uses server to:
// 1. Send spam (hides attacker's identity)
// 2. Probe internal mail servers
// 3. Deliver malware from "trusted" source
deliverMessage(to, message); // No validation of relay permission
}
}
// Vulnerable: API gateway confused deputy
class VulnerableAPIGateway {
async forwardRequest(userRequest) {
const targetService = userRequest.headers['x-target-service'];
const targetPath = userRequest.headers['x-target-path'];
// Vulnerable: Forwards request with gateway's identity
const response = await fetch(`http://${targetService}${targetPath}`, {
method: userRequest.method,
headers: {
// Vulnerable: Uses gateway's service account token
'Authorization': `Bearer ${this.gatewayToken}`,
// User's original identity lost
},
body: userRequest.body
});
// Attacker sets: x-target-service: admin-api.internal
// Gateway has access to admin-api, user doesn't
// Request succeeds with gateway's elevated privileges
return response;
}
}
Fixed Code
# Fixed: Web application with SSRF protection
from flask import Flask, request
import requests
from urllib.parse import urlparse
import ipaddress
app = Flask(__name__)
# Fixed: Allowlist of permitted destinations
ALLOWED_HOSTS = {'api.trusted.com', 'cdn.example.com'}
BLOCKED_IP_RANGES = [
ipaddress.ip_network('10.0.0.0/8'),
ipaddress.ip_network('172.16.0.0/12'),
ipaddress.ip_network('192.168.0.0/16'),
ipaddress.ip_network('169.254.0.0/16'), # Link-local/metadata
ipaddress.ip_network('127.0.0.0/8'), # Localhost
]
def is_safe_url(url):
try:
parsed = urlparse(url)
# Fixed: Only allow HTTPS
if parsed.scheme != 'https':
return False
# Fixed: Check against allowlist
if parsed.hostname not in ALLOWED_HOSTS:
return False
# Fixed: Resolve and check IP
import socket
ip = socket.gethostbyname(parsed.hostname)
ip_obj = ipaddress.ip_address(ip)
for blocked_range in BLOCKED_IP_RANGES:
if ip_obj in blocked_range:
return False
return True
except Exception:
return False
@app.route('/fetch')
def secure_fetch():
url = request.args.get('url')
# Fixed: Validate URL before fetching
if not is_safe_url(url):
return "Invalid or blocked URL", 403
# Fixed: Add timeout and size limits
response = requests.get(url, timeout=5, stream=True)
# Fixed: Limit response size
content = response.raw.read(1024 * 1024) # 1MB max
return content
@app.route('/proxy')
def secure_proxy():
# Fixed: Proxy functionality removed or heavily restricted
return "Proxy functionality not available", 403
// Fixed: FTP server with bounce attack prevention
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void secure_ftp_port(int client_fd, char *port_command,
struct sockaddr_in *client_addr) {
int h1, h2, h3, h4, p1, p2;
sscanf(port_command, "PORT %d,%d,%d,%d,%d,%d",
&h1, &h2, &h3, &h4, &p1, &p2);
char target_ip[16];
sprintf(target_ip, "%d.%d.%d.%d", h1, h2, h3, h4);
int target_port = (p1 * 256) + p2;
// Fixed: Verify target IP matches client IP
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr->sin_addr, client_ip, sizeof(client_ip));
if (strcmp(target_ip, client_ip) != 0) {
// Fixed: Reject bounce attacks
send_response(client_fd, "500 PORT address must match client IP\r\n");
return;
}
// Fixed: Restrict port range (non-privileged ports only)
if (target_port < 1024) {
send_response(client_fd, "500 PORT number must be >= 1024\r\n");
return;
}
// Fixed: Only connect to verified client address
data_connection = connect_to(target_ip, target_port);
}
// Fixed: DMA controller with identity preservation
typedef struct {
uint32_t src;
uint32_t dst;
size_t len;
uint32_t requester_id; // Fixed: Track originator
uint32_t access_level; // Fixed: Preserve access rights
} DMARequest;
int secure_dma_transfer(DMARequest *req) {
// Fixed: Check access using original requester's privileges
if (!check_access(req->requester_id, req->src, READ)) {
return -EACCES;
}
if (!check_access(req->requester_id, req->dst, WRITE)) {
return -EACCES;
}
// Fixed: Transfer only proceeds if requester has access
dma_copy(req->src, req->dst, req->len);
return 0;
}
// Fixed: Mail server with relay protection
public class SecureMailServer {
private Set<String> localDomains = Set.of("example.com", "mail.example.com");
public void processVerifyCommand(String address) {
// Fixed: Disable VRFY/EXPN or restrict
sendResponse("252 Cannot VRFY user");
// Or: Only allow for authenticated users
// if (!isAuthenticated()) {
// sendResponse("530 Authentication required");
// return;
// }
}
public void processRelayMessage(Session session, String from,
String to, String message) {
// Fixed: Check relay permissions
String recipientDomain = extractDomain(to);
// Fixed: Allow relay only for local domains
if (!localDomains.contains(recipientDomain)) {
// External recipient - check authentication
if (!session.isAuthenticated()) {
sendResponse("550 Relay denied");
return;
}
// Fixed: Check sender's relay permissions
if (!hasRelayPermission(session.getUser(), recipientDomain)) {
sendResponse("550 Not authorized to relay to this domain");
return;
}
}
// Fixed: Log original sender for non-repudiation
logMessage(session.getClientIP(), session.getUser(), from, to);
deliverMessage(to, message);
}
}
// Fixed: API gateway with identity preservation
class SecureAPIGateway {
// Fixed: Allowlist of internal services
static ALLOWED_SERVICES = new Set(['user-api', 'product-api', 'order-api']);
async forwardRequest(userRequest, userIdentity) {
const targetService = userRequest.headers['x-target-service'];
const targetPath = userRequest.headers['x-target-path'];
// Fixed: Validate target service is allowed
if (!SecureAPIGateway.ALLOWED_SERVICES.has(targetService)) {
throw new ForbiddenError(`Service not allowed: ${targetService}`);
}
// Fixed: Check user's authorization for this service
if (!await this.checkAuthorization(userIdentity, targetService, targetPath)) {
throw new ForbiddenError('User not authorized for this resource');
}
// Fixed: Forward with user's identity preserved
const response = await fetch(`http://${targetService}${targetPath}`, {
method: userRequest.method,
headers: {
// Fixed: Include user's identity
'X-Original-User': userIdentity.userId,
'X-Original-Roles': userIdentity.roles.join(','),
'X-Request-ID': userRequest.requestId,
// Fixed: Sign the identity assertion
'X-Identity-Signature': this.signIdentity(userIdentity),
},
body: userRequest.body
});
return response;
}
async checkAuthorization(userIdentity, service, path) {
// Fixed: Verify user has access to requested resource
const permissions = await this.permissionService.getPermissions(
userIdentity.userId
);
return permissions.some(p =>
p.service === service && this.pathMatches(p.pathPattern, path)
);
}
}
CVE Examples
- CVE-1999-0017 - Classic FTP bounce attack using PORT command to scan or attack systems through FTP server.
- CVE-2005-0315 - FTP server allowed port scanning by not verifying PORT command IP matched client.
- CVE-2002-1484 - Web server proxied requests to arbitrary ports, enabling attacks on internal services.
- CVE-2010-1637 - Mail program allowed internal network scanning through email processing.
References
- MITRE Corporation. "CWE-441: Unintended Proxy or Intermediary ('Confused Deputy')." https://cwe.mitre.org/data/definitions/441.html
- Norm Hardy. "The Confused Deputy: (or why capabilities might have been invented)." ACM SIGOPS Operating Systems Review, 1988.
- OWASP. "Server Side Request Forgery." https://owasp.org/www-community/attacks/Server_Side_Request_Forgery