CodeQL documentation

Server-side request forgery

ID: rust/request-forgery
Kind: path-problem
Security severity: 9.1
Severity: error
Precision: high
Tags:
   - security
   - external/cwe/cwe-918
Query suites:
   - rust-code-scanning.qls
   - rust-security-extended.qls
   - rust-security-and-quality.qls

Click to see the query in the CodeQL repository

Directly incorporating user input into an HTTP request without validating the input can facilitate server-side request forgery (SSRF) attacks. In these attacks, the server may be tricked into making a request to an unintended API endpoint or resource. If the server is connected to an internal network, attackers can bypass security boundaries to target internal services. Forged requests can execute unintended actions, leak data if redirected to an external server, or compromise the server if responses are handled insecurely.

Recommendation

To guard against SSRF attacks, you should avoid putting user-provided input directly into a request URL. Instead, maintain a list of authorized URLs on the server; then choose from that list based on the input provided. Alternatively, ensure requests constructed from user input are limited to a particular host or a more restrictive URL prefix.

Example

The following example shows an HTTP request parameter being used directly to form a new request without validating the input, which facilitates SSRF attacks. It also shows how to remedy the problem by validating the user input against a known fixed string.

// BAD: Endpoint handler that makes requests based on user input
async fn vulnerable_endpoint_handler(req: Request) -> Result<Response> {
    // This request is vulnerable to SSRF attacks as the user controls the
    // entire URL
    let response = reqwest::get(&req.user_url).await;
    
    match response {
        Ok(resp) => {
            let body = resp.text().await.unwrap_or_default();
            Ok(Response {
                message: "Success".to_string(),
                data: body,
            })
        }
        Err(_) => Err("Request failed")
    }
}

// GOOD: Validate user input against an allowlist
async fn secure_endpoint_handler(req: Request) -> Result<Response> {
    // Allow list of specific, known-safe URLs
    let allowed_hosts = ["api.example.com", "trusted-service.com"];
    
    if !allowed_hosts.contains(&req.user_url) {
        return Err("Untrusted domain");
    }
    // This request is safe as the user input has been validated
    let response = reqwest::get(&req.user_url).await;
    match response {
        Ok(resp) => {
            let body = resp.text().await.unwrap_or_default();
            Ok(Response {
                message: "Success".to_string(),
                data: body,
            })
        }
        Err(_) => Err("Request failed")
    }
}

References

  • © GitHub, Inc.
  • Terms
  • Privacy