Rust: Address PR feedback

This commit is contained in:
Simon Friis Vindum
2025-11-25 14:20:17 +01:00
parent 411d1fa861
commit 9ae4c14ffb
10 changed files with 54 additions and 38 deletions

View File

@@ -54,7 +54,7 @@ module Xss {
private class HeuristicHtmlEncodingBarrier extends Barrier {
HeuristicHtmlEncodingBarrier() {
exists(Call fc |
fc.getStaticTarget().(Function).getName().getText().regexpMatch(".*(escape|encode).*") and
fc.getStaticTarget().getName().getText().regexpMatch(".*(escape|encode).*") and
fc.getArgument(_) = this.asExpr()
)
}

View File

@@ -1,4 +1,4 @@
---
category: newQuery
---
* Added a new query `rust/xss`, to detect XSS security vulnerabilities.
* Added a new a query `rust/xss`, to detect cross-site scripting security vulnerabilities.

View File

@@ -10,20 +10,22 @@ scripting vulnerability.</p>
</overview>
<recommendation>
<p>To guard against cross-site scripting, consider encoding/escaping the unstrusted
<p>To guard against cross-site scripting, consider encoding/escaping the untrusted
input before including it in the HTML.</p>
</recommendation>
<example>
<p>The following example shows a simple web handler that writes a path of the
URL parameter directly to an HTML response, leaving the website vulnerable to
cross-site scripting:</p>
<p>The following example shows a simple web handler that writes a URL path parameter
directly to an HTML response, leaving the website vulnerable to cross-site
scripting:</p>
<sample src="XSSBad.rs" />
<p>To fix this vulnerability, the user input should be HTML-encoded before being
included in the response:</p>
included in the response. In the following example <code>encode_text</code> from
the <a href="https://docs.rs/html-escape/latest/html_escape/index.html">html_escape</a>
crate is used:</p>
<sample src="XSSGood.rs" />
@@ -36,7 +38,7 @@ included in the response:</p>
(Cross Site Scripting) Prevention Cheat Sheet</a>.
</li>
<li>
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
Wikipedia: <a href="https://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
</li>
<li>
OWASP:

View File

@@ -1,11 +1,9 @@
use actix_web::{web, HttpResponse, Result};
use askama::Template;
// GOOD: Manual HTML encoding using an `html_escape` function
// GOOD: Manual HTML encoding using an `html_escape::encode_text` function
async fn safe_handler_with_encoding(path: web::Path<String>) -> impl Responder {
let user_input = path.into_inner();
let escaped_input = html_escape(&user_input);
let escaped_input = html_escape::encode_text(&user_input);
let html = format!(
r#"
<!DOCTYPE html>

View File

@@ -31,6 +31,7 @@ private import codeql.rust.security.TaintedPathExtensions
private import codeql.rust.security.UncontrolledAllocationSizeExtensions
private import codeql.rust.security.UseOfHttpExtensions
private import codeql.rust.security.WeakSensitiveDataHashingExtensions
private import codeql.rust.security.XssExtensions
/**
* Gets a count of the total number of lines of code in the database.

View File

@@ -509,6 +509,15 @@ version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "html-escape"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
dependencies = [
"utf8-width",
]
[[package]]
name = "http"
version = "0.2.12"
@@ -1094,6 +1103,7 @@ name = "test"
version = "0.0.1"
dependencies = [
"actix-web",
"html-escape",
]
[[package]]
@@ -1228,6 +1238,12 @@ dependencies = [
"serde",
]
[[package]]
name = "utf8-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091"
[[package]]
name = "utf8_iter"
version = "1.0.4"

View File

@@ -5,7 +5,7 @@ use actix_web::{
};
// The "bad" example from the qldoc
#[get("/bad/{a}")] // $ Source
#[get("/bad/{a}")] // $ Source=a
async fn vulnerable_handler(path: web::Path<String>) -> impl Responder {
let user_input = path.into_inner();
@@ -22,7 +22,7 @@ async fn vulnerable_handler(path: web::Path<String>) -> impl Responder {
user_input
);
Html::new(html) // $ Alert[rust/xss]
Html::new(html) // $ Alert[rust/xss]=a
}
fn html_escape(s: &str) -> String {
@@ -42,7 +42,7 @@ fn html_escape(s: &str) -> String {
// The "good" example from the qldoc
async fn safe_handler_with_encoding(path: web::Path<String>) -> impl Responder {
let user_input = path.into_inner();
let escaped_input = html_escape(&user_input);
let escaped_input = html_escape::encode_text(&user_input);
let html = format!(
r#"

View File

@@ -1,3 +1,4 @@
qltest_use_nightly: true
qltest_dependencies:
- actix-web = { version = "4.12.0" }
- actix-web = { version = "4.12.0" }
- html-escape = { version = "0.2.13" }

View File

@@ -1,27 +1,27 @@
#select
| main.rs:12:13:12:29 | ...::html | main.rs:9:10:9:12 | map | main.rs:12:13:12:29 | ...::html | Cross-site scripting vulnerability due to a $@. | main.rs:9:10:9:12 | map | user-provided value |
| main.rs:10:13:10:29 | ...::html | main.rs:7:10:7:12 | map | main.rs:10:13:10:29 | ...::html | Cross-site scripting vulnerability due to a $@. | main.rs:7:10:7:12 | map | user-provided value |
edges
| main.rs:9:10:9:12 | map | main.rs:9:15:9:26 | ...: String | provenance | Src:MaD:2 |
| main.rs:9:15:9:26 | ...: String | main.rs:11:32:11:56 | MacroExpr | provenance | |
| main.rs:11:17:11:20 | body | main.rs:12:31:12:34 | body | provenance | |
| main.rs:11:32:11:56 | ...::format(...) | main.rs:11:32:11:56 | { ... } | provenance | |
| main.rs:11:32:11:56 | ...::must_use(...) | main.rs:11:17:11:20 | body | provenance | |
| main.rs:11:32:11:56 | MacroExpr | main.rs:11:32:11:56 | ...::format(...) | provenance | MaD:3 |
| main.rs:11:32:11:56 | { ... } | main.rs:11:32:11:56 | ...::must_use(...) | provenance | MaD:4 |
| main.rs:12:31:12:34 | body | main.rs:12:13:12:29 | ...::html | provenance | MaD:1 Sink:MaD:1 |
| main.rs:7:10:7:12 | map | main.rs:7:15:7:26 | ...: String | provenance | Src:MaD:2 |
| main.rs:7:15:7:26 | ...: String | main.rs:9:32:9:56 | MacroExpr | provenance | |
| main.rs:9:17:9:20 | body | main.rs:10:31:10:34 | body | provenance | |
| main.rs:9:32:9:56 | ...::format(...) | main.rs:9:32:9:56 | { ... } | provenance | |
| main.rs:9:32:9:56 | ...::must_use(...) | main.rs:9:17:9:20 | body | provenance | |
| main.rs:9:32:9:56 | MacroExpr | main.rs:9:32:9:56 | ...::format(...) | provenance | MaD:3 |
| main.rs:9:32:9:56 | { ... } | main.rs:9:32:9:56 | ...::must_use(...) | provenance | MaD:4 |
| main.rs:10:31:10:34 | body | main.rs:10:13:10:29 | ...::html | provenance | MaD:1 Sink:MaD:1 |
models
| 1 | Sink: warp::reply::html; Argument[0]; html-injection |
| 2 | Source: <_ as warp::filter::Filter>::map; Argument[0].Parameter[0..7]; remote |
| 3 | Summary: alloc::fmt::format; Argument[0]; ReturnValue; taint |
| 4 | Summary: core::hint::must_use; Argument[0]; ReturnValue; value |
nodes
| main.rs:9:10:9:12 | map | semmle.label | map |
| main.rs:9:15:9:26 | ...: String | semmle.label | ...: String |
| main.rs:11:17:11:20 | body | semmle.label | body |
| main.rs:11:32:11:56 | ...::format(...) | semmle.label | ...::format(...) |
| main.rs:11:32:11:56 | ...::must_use(...) | semmle.label | ...::must_use(...) |
| main.rs:11:32:11:56 | MacroExpr | semmle.label | MacroExpr |
| main.rs:11:32:11:56 | { ... } | semmle.label | { ... } |
| main.rs:12:13:12:29 | ...::html | semmle.label | ...::html |
| main.rs:12:31:12:34 | body | semmle.label | body |
| main.rs:7:10:7:12 | map | semmle.label | map |
| main.rs:7:15:7:26 | ...: String | semmle.label | ...: String |
| main.rs:9:17:9:20 | body | semmle.label | body |
| main.rs:9:32:9:56 | ...::format(...) | semmle.label | ...::format(...) |
| main.rs:9:32:9:56 | ...::must_use(...) | semmle.label | ...::must_use(...) |
| main.rs:9:32:9:56 | MacroExpr | semmle.label | MacroExpr |
| main.rs:9:32:9:56 | { ... } | semmle.label | { ... } |
| main.rs:10:13:10:29 | ...::html | semmle.label | ...::html |
| main.rs:10:31:10:34 | body | semmle.label | body |
subpaths

View File

@@ -1,15 +1,13 @@
//! Tests for XSS
//!
use warp::Filter;
#[tokio::main]
pub async fn main() {
let hello = warp::path("greet")
.and(warp::path::param())
.map(|name: String| { // $ Source
.map(|name: String| { // $ Source=name
// Vulnerable to XSS because it directly includes user input in the response
let body = format!("<h1>Hello, {name}!</h1>");
warp::reply::html(body) // $ Alert[rust/xss]
warp::reply::html(body) // $ Alert[rust/xss]=name
});
// Start the web server on port 3000