logo

Database

Server-side request forgery (SSRF)

Need

Restriction of server-side outbound requests to trusted destinations

Context

• Usage of Rust 1.75+ for building application services

• Usage of the actix-web crate for handling HTTP requests

• Usage of the reqwest crate for outbound HTTP requests

• Usage of the url crate for URL parsing

Description

1. Non compliant code

use actix_web::{get, web, HttpResponse};
use serde::Deserialize;

#[derive(Deserialize)]
struct StatusQuery {
    target: String,
}
...

The `check_status` handler below takes the `target` query parameter, builds `http://{target}/status` and fetches it with `reqwest::get`. The server makes the request from its own network position, so the caller can point it at hosts that are not reachable from the internet. Typical targets are the cloud metadata service (`169.254.169.254`, which can return temporary credentials), internal admin panels, databases that speak HTTP, and `localhost` ports. Because the response body is returned to the caller, the attacker can also read what those internal services answer. The same weakness appears when request data selects the host of a database or cache connection. Building a `sqlx` `PgConnectOptions` with a caller-supplied `host`, or a `redis://user:password@{host}` or `mongodb://` connection string from request data, makes the server send the application's own credentials to a machine the attacker controls.

2. Steps

• Do not build outbound URLs for `reqwest` or `awc` from request data; when a caller must choose a destination, accept an identifier and map it to a URL on the server.

• If a host must come from the request, compare it with an explicit allowlist before building the URL, and parse the result with the `url` crate to confirm the scheme and host.

• Disable automatic redirects with `reqwest::redirect::Policy::none()`, or validate every redirect target against the same allowlist.

• Read database and cache hosts (`sqlx` `PgConnectOptions::host`, `redis://` and `mongodb://` connection strings) from server configuration, never from request parameters.

• Block egress from the service to link-local, loopback and private address ranges at the network layer, including the cloud metadata endpoint.

• Do not return the raw body of an outbound response to the caller unless the endpoint requires it.

3. Secure code example

use actix_web::{get, web, HttpResponse};
use reqwest::redirect::Policy;
use serde::Deserialize;
use url::Url;

const ALLOWED_HOSTS: &[&str] = &["api.example.com", "status.example.com"];

#[derive(Deserialize)]...

The corrected handler only contacts hosts from a fixed allowlist. `build_status_url` compares the raw `target` value against `ALLOWED_HOSTS` before any URL is built, so values such as `169.254.169.254`, `localhost:8080` or `api.example.com@evil.example` are rejected outright. The URL is then parsed with the `url` crate, forced to `https`, and its parsed host is compared again with the allowed value to make sure the parser and the check agree. The `reqwest` client is built with `redirect::Policy::none()`. Without it, an allowed host that answers with a redirect could still send the server to an internal address. For database and cache connections, the equivalent fix is to read the host from server configuration, never from the request.