diff --git a/rust/ql/lib/codeql/rust/security/XssExtensions.qll b/rust/ql/lib/codeql/rust/security/XssExtensions.qll index 74ed161acb09..bcb560cf2f0e 100644 --- a/rust/ql/lib/codeql/rust/security/XssExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/XssExtensions.qll @@ -38,6 +38,23 @@ module Xss { */ private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } + /** + * A host or URL field read from a configuration type. + */ + private class ConfigHostFieldBarrier extends Barrier { + ConfigHostFieldBarrier() { + exists(FieldExpr field, Struct configType, string fieldName | + this.asExpr() = field and + field.getStructField().isStructField(configType, fieldName) and + configType.getName().getText().regexpMatch(".*(Config|Configuration|Options|Opts|Settings).*") and + fieldName = + [ + "external_domain", "hostname", "host_name", "base_url", "server_url", "public_url" + ] + ) + } + } + /** * A sink for XSS from model data. */ diff --git a/rust/ql/test/query-tests/security/CWE-079/axum/XSS.expected b/rust/ql/test/query-tests/security/CWE-079/axum/XSS.expected index 385ce0f58c48..7f5c92cce581 100644 --- a/rust/ql/test/query-tests/security/CWE-079/axum/XSS.expected +++ b/rust/ql/test/query-tests/security/CWE-079/axum/XSS.expected @@ -1,24 +1,58 @@ #select -| main.rs:10:10:10:21 | html_content | main.rs:15:51:15:53 | get | main.rs:10:10:10:21 | html_content | Cross-site scripting vulnerability due to a $@. | main.rs:15:51:15:53 | get | user-provided value | +| main.rs:15:10:15:21 | html_content | main.rs:37:26:37:28 | get | main.rs:15:10:15:21 | html_content | Cross-site scripting vulnerability due to a $@. | main.rs:37:26:37:28 | get | user-provided value | +| main.rs:19:10:19:60 | MacroExpr | main.rs:38:33:38:35 | get | main.rs:19:10:19:60 | MacroExpr | Cross-site scripting vulnerability due to a $@. | main.rs:38:33:38:35 | get | user-provided value | +| main.rs:31:10:31:34 | MacroExpr | main.rs:30:17:30:29 | ...::var | main.rs:31:10:31:34 | MacroExpr | Cross-site scripting vulnerability due to a $@. | main.rs:30:17:30:29 | ...::var | user-provided value | edges -| main.rs:8:24:8:59 | ...: Query::<...> | main.rs:9:32:9:63 | MacroExpr | provenance | | -| main.rs:9:9:9:20 | html_content | main.rs:10:10:10:21 | html_content | provenance | | -| main.rs:9:32:9:63 | ...::format(...) | main.rs:9:32:9:63 | { ... } | provenance | | -| main.rs:9:32:9:63 | ...::must_use(...) | main.rs:9:9:9:20 | html_content | provenance | | -| main.rs:9:32:9:63 | MacroExpr | main.rs:9:32:9:63 | ...::format(...) | provenance | MaD:2 | -| main.rs:9:32:9:63 | { ... } | main.rs:9:32:9:63 | ...::must_use(...) | provenance | MaD:3 | -| main.rs:15:51:15:53 | get | main.rs:8:24:8:59 | ...: Query::<...> | provenance | Src:MaD:1 | +| main.rs:13:24:13:59 | ...: Query::<...> | main.rs:14:32:14:63 | MacroExpr | provenance | | +| main.rs:14:9:14:20 | html_content | main.rs:15:10:15:21 | html_content | provenance | | +| main.rs:14:32:14:63 | ...::format(...) | main.rs:14:32:14:63 | { ... } | provenance | | +| main.rs:14:32:14:63 | ...::must_use(...) | main.rs:14:9:14:20 | html_content | provenance | | +| main.rs:14:32:14:63 | MacroExpr | main.rs:14:32:14:63 | ...::format(...) | provenance | MaD:4 | +| main.rs:14:32:14:63 | { ... } | main.rs:14:32:14:63 | ...::must_use(...) | provenance | MaD:5 | +| main.rs:18:31:18:66 | ...: Query::<...> | main.rs:19:18:19:59 | MacroExpr | provenance | | +| main.rs:19:18:19:59 | ...::format(...) | main.rs:19:18:19:59 | { ... } | provenance | | +| main.rs:19:18:19:59 | ...::must_use(...) | main.rs:19:10:19:60 | MacroExpr | provenance | | +| main.rs:19:18:19:59 | MacroExpr | main.rs:19:18:19:59 | ...::format(...) | provenance | MaD:4 | +| main.rs:19:18:19:59 | { ... } | main.rs:19:18:19:59 | ...::must_use(...) | provenance | MaD:5 | +| main.rs:30:9:30:13 | value | main.rs:31:18:31:33 | MacroExpr | provenance | | +| main.rs:30:17:30:29 | ...::var | main.rs:30:17:30:37 | ...::var(...) [Ok] | provenance | Src:MaD:2 | +| main.rs:30:17:30:37 | ...::var(...) [Ok] | main.rs:30:17:30:46 | ... .unwrap() | provenance | MaD:3 | +| main.rs:30:17:30:46 | ... .unwrap() | main.rs:30:9:30:13 | value | provenance | | +| main.rs:31:18:31:33 | ...::format(...) | main.rs:31:18:31:33 | { ... } | provenance | | +| main.rs:31:18:31:33 | ...::must_use(...) | main.rs:31:10:31:34 | MacroExpr | provenance | | +| main.rs:31:18:31:33 | MacroExpr | main.rs:31:18:31:33 | ...::format(...) | provenance | MaD:4 | +| main.rs:31:18:31:33 | { ... } | main.rs:31:18:31:33 | ...::must_use(...) | provenance | MaD:5 | +| main.rs:37:26:37:28 | get | main.rs:13:24:13:59 | ...: Query::<...> | provenance | Src:MaD:1 | +| main.rs:38:33:38:35 | get | main.rs:18:31:18:66 | ...: Query::<...> | provenance | Src:MaD:1 | models | 1 | Source: axum::routing::method_routing::get; Argument[0].Parameter[0..7]; remote | -| 2 | Summary: alloc::fmt::format; Argument[0]; ReturnValue; taint | -| 3 | Summary: core::hint::must_use; Argument[0]; ReturnValue; value | +| 2 | Source: std::env::var; ReturnValue.Field[core::result::Result::Ok(0)]; environment | +| 3 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | +| 4 | Summary: alloc::fmt::format; Argument[0]; ReturnValue; taint | +| 5 | Summary: core::hint::must_use; Argument[0]; ReturnValue; value | nodes -| main.rs:8:24:8:59 | ...: Query::<...> | semmle.label | ...: Query::<...> | -| main.rs:9:9:9:20 | html_content | semmle.label | html_content | -| main.rs:9:32:9:63 | ...::format(...) | semmle.label | ...::format(...) | -| main.rs:9:32:9:63 | ...::must_use(...) | semmle.label | ...::must_use(...) | -| main.rs:9:32:9:63 | MacroExpr | semmle.label | MacroExpr | -| main.rs:9:32:9:63 | { ... } | semmle.label | { ... } | -| main.rs:10:10:10:21 | html_content | semmle.label | html_content | -| main.rs:15:51:15:53 | get | semmle.label | get | +| main.rs:13:24:13:59 | ...: Query::<...> | semmle.label | ...: Query::<...> | +| main.rs:14:9:14:20 | html_content | semmle.label | html_content | +| main.rs:14:32:14:63 | ...::format(...) | semmle.label | ...::format(...) | +| main.rs:14:32:14:63 | ...::must_use(...) | semmle.label | ...::must_use(...) | +| main.rs:14:32:14:63 | MacroExpr | semmle.label | MacroExpr | +| main.rs:14:32:14:63 | { ... } | semmle.label | { ... } | +| main.rs:15:10:15:21 | html_content | semmle.label | html_content | +| main.rs:18:31:18:66 | ...: Query::<...> | semmle.label | ...: Query::<...> | +| main.rs:19:10:19:60 | MacroExpr | semmle.label | MacroExpr | +| main.rs:19:18:19:59 | ...::format(...) | semmle.label | ...::format(...) | +| main.rs:19:18:19:59 | ...::must_use(...) | semmle.label | ...::must_use(...) | +| main.rs:19:18:19:59 | MacroExpr | semmle.label | MacroExpr | +| main.rs:19:18:19:59 | { ... } | semmle.label | { ... } | +| main.rs:30:9:30:13 | value | semmle.label | value | +| main.rs:30:17:30:29 | ...::var | semmle.label | ...::var | +| main.rs:30:17:30:37 | ...::var(...) [Ok] | semmle.label | ...::var(...) [Ok] | +| main.rs:30:17:30:46 | ... .unwrap() | semmle.label | ... .unwrap() | +| main.rs:31:10:31:34 | MacroExpr | semmle.label | MacroExpr | +| main.rs:31:18:31:33 | ...::format(...) | semmle.label | ...::format(...) | +| main.rs:31:18:31:33 | ...::must_use(...) | semmle.label | ...::must_use(...) | +| main.rs:31:18:31:33 | MacroExpr | semmle.label | MacroExpr | +| main.rs:31:18:31:33 | { ... } | semmle.label | { ... } | +| main.rs:37:26:37:28 | get | semmle.label | get | +| main.rs:38:33:38:35 | get | semmle.label | get | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-079/axum/main.rs b/rust/ql/test/query-tests/security/CWE-079/axum/main.rs index 27807c5883d9..add4a648f336 100644 --- a/rust/ql/test/query-tests/security/CWE-079/axum/main.rs +++ b/rust/ql/test/query-tests/security/CWE-079/axum/main.rs @@ -3,6 +3,11 @@ use axum::{extract::Query, response::Html, routing::get, Router}; #[derive(serde::Deserialize)] struct GreetingParams { name: String, + external_domain: String, +} + +struct ServerConfig { + external_domain: String, } async fn greet_handler(Query(params): Query) -> Html { @@ -10,9 +15,29 @@ async fn greet_handler(Query(params): Query) -> Html { Html(html_content) // $ Alert[rust/xss]=greet } +async fn request_host_handler(Query(params): Query) -> Html { + Html(format!("

Host: {}!

", params.external_domain)) // $ Alert[rust/xss]=requestHost +} + +async fn config_host_handler() -> Html { + let config = ServerConfig { + external_domain: std::env::var("EXTERNAL_DOMAIN").unwrap(), + }; + Html(format!("

Host: {}!

", config.external_domain)) +} + +async fn environment_handler() -> Html { + let value = std::env::var("HTML").unwrap(); // $ Source=environment + Html(format!("

{value}

")) // $ Alert[rust/xss]=environment +} + #[tokio::main] pub async fn main() { - let app = Router::<()>::new().route("/greet", get(greet_handler)); // $ Source=greet + let app = Router::<()>::new() + .route("/greet", get(greet_handler)) // $ Source=greet + .route("/request-host", get(request_host_handler)) // $ Source=requestHost + .route("/config-host", get(config_host_handler)) + .route("/environment", get(environment_handler)); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .unwrap();