Use of Less Trusted Source

Description

Use of Less Trusted Source is a vulnerability that occurs when a product selects between multiple data sources but chooses the one that offers reduced verification capability, diminished trustworthiness, or increased susceptibility to compromise. This commonly manifests when applications trust client-supplied data over server-verified information, such as trusting the X-Forwarded-For HTTP header instead of the actual connection IP address, or trusting client-provided user identity over server-side authentication data. The vulnerability arises from prioritizing convenience or compatibility over security when multiple information sources of varying trustworthiness are available. The chosen less-trusted source can be manipulated by attackers to bypass security controls, spoof identities, or inject malicious data.

Risk

Trusting less reliable data sources creates security bypasses that attackers can exploit to circumvent authentication, authorization, and auditing controls. When applications trust X-Forwarded-For headers for IP-based access control, attackers can spoof internal IP addresses to bypass firewall restrictions and access internal-only resources. Client-supplied identity information can be manipulated for session hijacking or privilege escalation. Audit logs based on spoofable data become unreliable for forensic analysis and incident response. IP-based rate limiting using forged headers allows attackers to bypass abuse protections. Geographic restrictions based on spoofed location data can be circumvented. The risk is particularly severe when the less-trusted source is used for security-critical decisions like access control, authentication, or audit logging, as attackers can both perform malicious actions and hide their true identity.

Solution

Always prefer the most trustworthy data source when multiple sources are available. For IP addresses, use the direct connection IP (REMOTE_ADDR) rather than headers like X-Forwarded-For which can be spoofed. If proxy headers must be used, only trust them from known proxy servers by validating the direct connection IP first. Implement a chain of trust where each hop in a proxy chain is verified. For user identity, always use server-side session data rather than client-supplied values. Document the trust level of each data source and ensure security decisions use appropriately trusted sources. When client data must be used, validate it against server-side records. Implement logging that captures both trusted and untrusted values to support forensic analysis.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Attackers can bypass IP-based access restrictions, authentication checks, and authorization controls by supplying forged values through the less-trusted source.
IntegrityScope: Integrity

Audit logs and security records become unreliable when they contain attacker-controlled data from less trusted sources, compromising incident response and forensics.
Non-RepudiationScope: Non-Repudiation

Attackers can hide their true identity by spoofing data in less trusted sources, making it difficult to attribute malicious actions to their actual source.

Example Code

Vulnerable Code (PHP/Python)

The following examples demonstrate use of less trusted sources:

<?php
// Vulnerable: Using less trusted X-Forwarded-For header

// Vulnerable: Trusting X-Forwarded-For for access control
function vulnerable_check_internal_access() {
    // Vulnerable: Prefers X-Forwarded-For over REMOTE_ADDR
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $client_ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    } else {
        $client_ip = $_SERVER['REMOTE_ADDR'];
    }

    // Vulnerable: IP-based access control on spoofable header
    return strpos($client_ip, '10.') === 0 ||
           strpos($client_ip, '192.168.') === 0;
}

// Vulnerable: Rate limiting with spoofable IP
function vulnerable_rate_limit() {
    // Vulnerable: Trusting client-supplied IP
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];

    $key = "rate_limit_" . $ip;
    $count = cache_get($key) ?? 0;

    if ($count > 100) {
        http_response_code(429);
        exit("Rate limit exceeded");
    }

    cache_set($key, $count + 1, 60);
}

// Vulnerable: Logging with spoofable data
function vulnerable_log_action($action) {
    // Vulnerable: Logging attacker-controlled IP
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];

    // Audit log unreliable for forensics
    log_to_file("Action: $action from IP: $ip");
}

// Vulnerable: Trusting client-supplied user identity
function vulnerable_get_user_id() {
    // Vulnerable: Trusting client cookie over session
    if (isset($_COOKIE['user_id'])) {
        return $_COOKIE['user_id'];
    }
    return $_SESSION['user_id'] ?? null;
}

// Vulnerable: Geographic restriction bypass
function vulnerable_geo_restriction() {
    // Vulnerable: Trusting CF-IPCountry header
    $country = $_SERVER['HTTP_CF_IPCOUNTRY'] ?? 'XX';

    // Attacker can spoof this header
    if (in_array($country, ['US', 'CA', 'UK'])) {
        return true;
    }
    return false;
}
?>
# Vulnerable: Using less trusted sources in Python
from flask import Flask, request

app = Flask(__name__)

# Vulnerable: Trusting X-Forwarded-For for IP
def vulnerable_get_client_ip():
    # Vulnerable: Prefers spoofable header over actual connection IP
    forwarded_for = request.headers.get('X-Forwarded-For')
    if forwarded_for:
        # Vulnerable: Taking first IP from header (attacker-controlled)
        return forwarded_for.split(',')[0].strip()
    return request.remote_addr

# Vulnerable: Access control with spoofed IP
@app.route('/admin')
def vulnerable_admin_access():
    client_ip = vulnerable_get_client_ip()

    # Vulnerable: IP-based access control on untrusted data
    if not client_ip.startswith('10.') and not client_ip.startswith('192.168.'):
        return "Access denied", 403

    return render_admin_panel()

# Vulnerable: Trusting client-supplied role
@app.route('/api/action')
def vulnerable_role_check():
    # Vulnerable: Trusting role from request instead of session
    user_role = request.headers.get('X-User-Role', 'user')

    if user_role == 'admin':
        return perform_admin_action()
    return perform_user_action()

# Vulnerable: Authentication with less trusted source
@app.route('/api/data')
def vulnerable_auth():
    # Vulnerable: Trusting JWT from cookie over session
    jwt_token = request.cookies.get('auth_token')
    session_user = session.get('user_id')

    # Vulnerable: Prefers client-controlled cookie
    if jwt_token:
        user = decode_jwt_without_verification(jwt_token)
        return get_user_data(user['id'])
    elif session_user:
        return get_user_data(session_user)
    return "Not authenticated", 401

# Vulnerable: Rate limiting with spoofed identity
from functools import wraps

def vulnerable_rate_limit(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        # Vulnerable: Using spoofable header for rate limit key
        client_id = request.headers.get('X-Client-ID',
                                         vulnerable_get_client_ip())

        key = f"rate:{client_id}"
        count = redis.incr(key)
        if count == 1:
            redis.expire(key, 60)

        if count > 100:
            return "Rate limit exceeded", 429

        return f(*args, **kwargs)
    return decorated
// Vulnerable: Using less trusted sources in Java
import javax.servlet.http.*;

public class VulnerableTrustSource extends HttpServlet {

    // Vulnerable: IP from header instead of connection
    private String vulnerableGetClientIP(HttpServletRequest request) {
        // Vulnerable: Prefers spoofable header
        String forwardedFor = request.getHeader("X-Forwarded-For");
        if (forwardedFor != null && !forwardedFor.isEmpty()) {
            return forwardedFor.split(",")[0].trim();
        }
        return request.getRemoteAddr();
    }

    // Vulnerable: Access control with spoofed IP
    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) {
        String clientIP = vulnerableGetClientIP(request);

        // Vulnerable: Security decision on untrusted data
        if (!clientIP.startsWith("10.") && !clientIP.startsWith("192.168.")) {
            response.setStatus(403);
            return;
        }

        serveAdminContent(response);
    }

    // Vulnerable: User identity from header
    private String vulnerableGetUserId(HttpServletRequest request) {
        // Vulnerable: Trusting client-supplied header over session
        String headerUserId = request.getHeader("X-User-Id");
        if (headerUserId != null) {
            return headerUserId;  // Attacker-controlled!
        }

        HttpSession session = request.getSession(false);
        return session != null ? (String) session.getAttribute("userId") : null;
    }

    // Vulnerable: Logging with spoofed data
    private void vulnerableAuditLog(HttpServletRequest request, String action) {
        String clientIP = vulnerableGetClientIP(request);
        String userId = vulnerableGetUserId(request);

        // Vulnerable: Log contains attacker-controlled data
        logger.info("Action: {} by user {} from IP {}", action, userId, clientIP);
    }

    // Vulnerable: Geographic restriction
    private boolean vulnerableCheckGeo(HttpServletRequest request) {
        // Vulnerable: Trusting CloudFlare header without verification
        String country = request.getHeader("CF-IPCountry");
        if (country == null) {
            country = "XX";
        }

        // Attacker can add this header
        return Arrays.asList("US", "CA", "UK").contains(country);
    }
}

Fixed Code (PHP/Python)

<?php
// Fixed: Using trusted data sources

// Fixed: Only trust proxy headers from known proxies
function secure_get_client_ip() {
    // Fixed: List of trusted proxy IPs
    $trusted_proxies = ['10.0.0.1', '10.0.0.2'];  // Load balancer IPs

    // Fixed: Always start with REMOTE_ADDR (cannot be spoofed)
    $remote_addr = $_SERVER['REMOTE_ADDR'];

    // Fixed: Only trust X-Forwarded-For if request is from trusted proxy
    if (in_array($remote_addr, $trusted_proxies) &&
        isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {

        // Fixed: Parse the chain properly
        $chain = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));

        // Fixed: Return the rightmost untrusted IP
        // (last IP before our trusted proxy chain)
        return $chain[0];
    }

    // Fixed: Default to actual connection IP
    return $remote_addr;
}

// Fixed: Access control with trusted IP
function secure_check_internal_access() {
    $client_ip = secure_get_client_ip();

    // Fixed: Using trusted IP source
    return strpos($client_ip, '10.') === 0 ||
           strpos($client_ip, '192.168.') === 0;
}

// Fixed: Rate limiting with trusted identity
function secure_rate_limit() {
    // Fixed: Using trusted IP source
    $ip = secure_get_client_ip();

    // Fixed: Additionally use authenticated user ID if available
    $user_id = $_SESSION['user_id'] ?? null;
    $key = $user_id ? "rate_limit_user_$user_id" : "rate_limit_ip_$ip";

    $count = cache_get($key) ?? 0;

    if ($count > 100) {
        http_response_code(429);
        exit("Rate limit exceeded");
    }

    cache_set($key, $count + 1, 60);
}

// Fixed: Logging with both trusted and untrusted data
function secure_log_action($action) {
    // Fixed: Log trusted IP
    $trusted_ip = secure_get_client_ip();

    // Fixed: Also log untrusted for forensics (clearly marked)
    $forwarded_for = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? 'none';

    // Fixed: Clear distinction between trusted and untrusted
    log_to_file(sprintf(
        "Action: %s | Trusted IP: %s | X-Forwarded-For (untrusted): %s",
        $action, $trusted_ip, $forwarded_for
    ));
}

// Fixed: User identity from session only
function secure_get_user_id() {
    // Fixed: Only trust server-side session
    if (!isset($_SESSION['user_id'])) {
        return null;
    }
    return $_SESSION['user_id'];
    // Fixed: Never trust client-supplied user ID
}

// Fixed: Geographic restriction with verification
function secure_geo_restriction() {
    // Fixed: Get trusted client IP
    $client_ip = secure_get_client_ip();

    // Fixed: Perform server-side geo lookup
    $country = geoip_country_code($client_ip);  // Server-side lookup

    if (in_array($country, ['US', 'CA', 'UK'])) {
        return true;
    }
    return false;
}
?>
# Fixed: Using trusted data sources in Python
from flask import Flask, request, session
import ipaddress

app = Flask(__name__)

# Fixed: Configuration for trusted proxies
TRUSTED_PROXIES = {'10.0.0.1', '10.0.0.2'}  # Load balancer IPs
INTERNAL_NETWORKS = [
    ipaddress.ip_network('10.0.0.0/8'),
    ipaddress.ip_network('192.168.0.0/16'),
    ipaddress.ip_network('172.16.0.0/12')
]

# Fixed: Trusted IP extraction
def secure_get_client_ip():
    remote_addr = request.remote_addr

    # Fixed: Only trust proxy headers from known proxies
    if remote_addr in TRUSTED_PROXIES:
        forwarded_for = request.headers.get('X-Forwarded-For')
        if forwarded_for:
            # Fixed: Get the first (client) IP from chain
            client_ip = forwarded_for.split(',')[0].strip()
            try:
                # Fixed: Validate it's a valid IP
                ipaddress.ip_address(client_ip)
                return client_ip
            except ValueError:
                pass  # Invalid IP, fall through to remote_addr

    # Fixed: Default to direct connection IP
    return remote_addr

# Fixed: Access control with trusted IP
@app.route('/admin')
def secure_admin_access():
    client_ip = secure_get_client_ip()

    try:
        ip_obj = ipaddress.ip_address(client_ip)
        # Fixed: Check against internal networks using trusted IP
        is_internal = any(ip_obj in net for net in INTERNAL_NETWORKS)

        if not is_internal:
            return "Access denied", 403

        return render_admin_panel()
    except ValueError:
        return "Invalid request", 400

# Fixed: Role from session only
@app.route('/api/action')
def secure_role_check():
    # Fixed: Only trust session data for role
    if 'user' not in session:
        return "Not authenticated", 401

    user_role = session['user']['role']  # From server-side session

    if user_role == 'admin':
        return perform_admin_action()
    return perform_user_action()

# Fixed: Authentication with trusted source
@app.route('/api/data')
def secure_auth():
    # Fixed: Only trust server-side session
    if 'user_id' not in session:
        return "Not authenticated", 401

    user_id = session['user_id']  # Server-verified
    return get_user_data(user_id)

# Fixed: Rate limiting with trusted identity
from functools import wraps

def secure_rate_limit(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        # Fixed: Use trusted identity for rate limiting
        if 'user_id' in session:
            # Authenticated users: rate limit by user ID
            key = f"rate:user:{session['user_id']}"
        else:
            # Anonymous: rate limit by trusted IP
            key = f"rate:ip:{secure_get_client_ip()}"

        count = redis.incr(key)
        if count == 1:
            redis.expire(key, 60)

        if count > 100:
            return "Rate limit exceeded", 429

        return f(*args, **kwargs)
    return decorated

# Fixed: Logging with trust levels
import logging

def secure_audit_log(action):
    trusted_ip = secure_get_client_ip()
    user_id = session.get('user_id', 'anonymous')

    # Fixed: Clearly mark trust level of data
    untrusted_xff = request.headers.get('X-Forwarded-For', 'none')

    logging.info(
        f"AUDIT | action={action} | "
        f"trusted_ip={trusted_ip} | "
        f"user_id={user_id} | "
        f"xff_untrusted={untrusted_xff}"
    )
// Fixed: Using trusted data sources in Java
import javax.servlet.http.*;
import java.net.InetAddress;
import java.util.*;

public class SecureTrustSource extends HttpServlet {

    private static final Set<String> TRUSTED_PROXIES = Set.of(
        "10.0.0.1", "10.0.0.2"
    );

    // Fixed: IP from trusted source only
    private String secureGetClientIP(HttpServletRequest request) {
        String remoteAddr = request.getRemoteAddr();

        // Fixed: Only trust headers from known proxies
        if (TRUSTED_PROXIES.contains(remoteAddr)) {
            String forwardedFor = request.getHeader("X-Forwarded-For");
            if (forwardedFor != null && !forwardedFor.isEmpty()) {
                String clientIp = forwardedFor.split(",")[0].trim();
                // Fixed: Validate IP format
                if (isValidIpAddress(clientIp)) {
                    return clientIp;
                }
            }
        }

        // Fixed: Default to direct connection
        return remoteAddr;
    }

    // Fixed: Access control with trusted IP
    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) {
        String clientIP = secureGetClientIP(request);

        // Fixed: Security decision on trusted data
        if (!isInternalIP(clientIP)) {
            response.setStatus(403);
            return;
        }

        serveAdminContent(response);
    }

    // Fixed: User identity from session only
    private String secureGetUserId(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return null;
        }

        // Fixed: Only trust server-side session
        return (String) session.getAttribute("userId");
        // Fixed: Never use client-supplied headers for user ID
    }

    // Fixed: Logging with trust levels
    private void secureAuditLog(HttpServletRequest request, String action) {
        String trustedIP = secureGetClientIP(request);
        String userId = secureGetUserId(request);

        // Fixed: Log trusted data for security decisions
        // Also log untrusted data (marked) for forensics
        String untrustedXFF = request.getHeader("X-Forwarded-For");

        logger.info("AUDIT | action={} | user={} | trusted_ip={} | xff_untrusted={}",
                    action,
                    userId != null ? userId : "anonymous",
                    trustedIP,
                    untrustedXFF != null ? untrustedXFF : "none");
    }

    // Fixed: Server-side geo lookup
    private boolean secureCheckGeo(HttpServletRequest request) {
        String clientIP = secureGetClientIP(request);

        // Fixed: Server-side geo lookup, not from headers
        String country = geoIpService.lookupCountry(clientIP);

        return Arrays.asList("US", "CA", "UK").contains(country);
    }

    private boolean isValidIpAddress(String ip) {
        try {
            InetAddress.getByName(ip);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private boolean isInternalIP(String ip) {
        return ip.startsWith("10.") ||
               ip.startsWith("192.168.") ||
               ip.startsWith("172.16.");
    }
}

The fix ensures security decisions use the most trusted available data source, with proper validation of proxy chains.


Exploited in the Wild

X-Forwarded-For Spoofing (CVE-2001-0860)

Application relied on client-supplied IP instead of packet headers, allowing attackers to bypass IP-based access controls.

PHP Application Header Trust (CVE-2006-1126)

PHP application trusted X-Forwarded-For instead of REMOTE_ADDR, enabling access control bypass.


Tools to Test/Exploit

  • Burp Suite — Modify HTTP headers to test X-Forwarded-For spoofing.

  • curl — Send requests with custom headers for testing.

  • OWASP ZAP — Automated scanning for header injection vulnerabilities.


CVE Examples

  • CVE-2001-0860 — Client-supplied IP trusted over packet headers.

  • CVE-2004-1950 — X-Forwarded-For prioritized over server variables.

  • CVE-2006-1126 — PHP trusted X-Forwarded-For for access control.


References

  1. MITRE Corporation. "CWE-348: Use of Less Trusted Source." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/348.html

  2. OWASP Foundation. "HTTP Request Smuggling." https://owasp.org/www-community/attacks/HTTP_Request_Smuggling

  3. Mozilla Developer Network. "X-Forwarded-For." https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For