diff --git a/rust/ql/lib/codeql/rust/frameworks/reqwest.model.yml b/rust/ql/lib/codeql/rust/frameworks/reqwest.model.yml index 34ae80e4999a..3be832c8e7fb 100644 --- a/rust/ql/lib/codeql/rust/frameworks/reqwest.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/reqwest.model.yml @@ -5,6 +5,12 @@ extensions: data: - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "crate::get", "ReturnValue.Field[crate::result::Result::Ok(0)]", "remote", "manual"] - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "crate::blocking::get", "ReturnValue.Field[crate::result::Result::Ok(0)]", "remote", "manual"] + - addsTo: + pack: codeql/rust-all + extensible: sinkModel + data: + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::request", "Argument[1]", "transmission", "manual"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::request", "Argument[1]", "transmission", "manual"] - addsTo: pack: codeql/rust-all extensible: summaryModel diff --git a/rust/ql/lib/codeql/rust/frameworks/url.model.yml b/rust/ql/lib/codeql/rust/frameworks/url.model.yml new file mode 100644 index 000000000000..31a7c79011ab --- /dev/null +++ b/rust/ql/lib/codeql/rust/frameworks/url.model.yml @@ -0,0 +1,7 @@ +# Models for the `url` crate +extensions: + - addsTo: + pack: codeql/rust-all + extensible: summaryModel + data: + - ["repo:https://github.com/servo/rust-url:url", "::parse", "Argument[0].Reference", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"] \ No newline at end of file diff --git a/rust/ql/lib/codeql/rust/security/CleartextTransmissionExtensions.qll b/rust/ql/lib/codeql/rust/security/CleartextTransmissionExtensions.qll new file mode 100644 index 000000000000..73495cd1c0d3 --- /dev/null +++ b/rust/ql/lib/codeql/rust/security/CleartextTransmissionExtensions.qll @@ -0,0 +1,38 @@ +/** + * Provides classes and predicates for reasoning about cleartext transmission + * vulnerabilities. + */ + +private import codeql.util.Unit +private import rust +private import codeql.rust.dataflow.DataFlow +private import codeql.rust.dataflow.FlowSink + +/** + * A data flow sink for cleartext transmission vulnerabilities. That is, + * a `DataFlow::Node` of something that is transmitted over a network. + */ +abstract class CleartextTransmissionSink extends DataFlow::Node { } + +/** + * A barrier for cleartext transmission vulnerabilities. + */ +abstract class CleartextTransmissionBarrier extends DataFlow::Node { } + +/** + * A unit class for adding additional flow steps. + */ +class CleartextTransmissionAdditionalFlowStep extends Unit { + /** + * Holds if the step from `node1` to `node2` should be considered a flow + * step for paths related to cleartext transmission vulnerabilities. + */ + abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo); +} + +/** + * A sink defined through MaD. + */ +private class MadCleartextTransmissionSink extends CleartextTransmissionSink { + MadCleartextTransmissionSink() { sinkNode(this, "transmission") } +} diff --git a/rust/ql/lib/ext/generated/reqwest/repo-https-github.com-seanmonstar-reqwest-reqwest.model.yml b/rust/ql/lib/ext/generated/reqwest/repo-https-github.com-seanmonstar-reqwest-reqwest.model.yml new file mode 100644 index 000000000000..53f2675a0c0b --- /dev/null +++ b/rust/ql/lib/ext/generated/reqwest/repo-https-github.com-seanmonstar-reqwest-reqwest.model.yml @@ -0,0 +1,23 @@ +# THIS FILE IS AN AUTO-GENERATED MODELS AS DATA FILE. DO NOT EDIT. +extensions: + - addsTo: + pack: codeql/rust-all + extensible: sinkModel + data: + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::delete", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::get", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::head", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::patch", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::post", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::put", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::delete", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::get", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::head", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::patch", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::post", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::put", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::call", "Argument[0]", "log-injection", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "::call", "Argument[0]", "log-injection", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "crate::blocking::get", "Argument[0]", "transmission", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "crate::blocking::wait::timeout", "Argument[1]", "log-injection", "df-generated"] + - ["repo:https://github.com/seanmonstar/reqwest:reqwest", "crate::get", "Argument[0]", "transmission", "df-generated"] diff --git a/rust/ql/src/queries/security/CWE-311/CleartextTransmission.qhelp b/rust/ql/src/queries/security/CWE-311/CleartextTransmission.qhelp new file mode 100644 index 000000000000..1fb29c55ca00 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-311/CleartextTransmission.qhelp @@ -0,0 +1,51 @@ + + + + +

+Sensitive information that is transmitted without encryption may be accessible +to an attacker. +

+ +
+ + +

+Ensure that sensitive information is always encrypted before being transmitted +over the network. In general, decrypt sensitive information only at the point +where it is necessary for it to be used in cleartext. Avoid transmitting +sensitive information when it is not necessary to. +

+ +
+ + +

+The following example shows three cases of transmitting information. In the +'BAD' case, the transmitted data is sensitive (a credit card number) and is +included as cleartext in the URL. URLs are often logged or otherwise visible in +cleartext, and should not contain sensitive information. +

+ +

+In the 'GOOD' cases, the data is either not sensitive, or is protected with +encryption. When encryption is used, ensure that you select a secure modern +encryption algorithm, and put suitable key management practices into place. +

+ + + +
+ + +
  • + OWASP Top 10:2021: + A02:2021 - Cryptographic Failures. +
  • +
  • + OWASP: + Key Management Cheat Sheet. +
  • + +
    +
    diff --git a/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql b/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql new file mode 100644 index 000000000000..ccf01f6fddad --- /dev/null +++ b/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql @@ -0,0 +1,50 @@ +/** + * @name Cleartext transmission of sensitive information + * @description Transmitting sensitive information across a network in + * cleartext can expose it to an attacker. + * @kind path-problem + * @problem.severity warning + * @security-severity 7.5 + * @precision high + * @id rust/cleartext-transmission + * @tags security + * external/cwe/cwe-319 + */ + +import rust +import codeql.rust.dataflow.DataFlow +import codeql.rust.security.SensitiveData +import codeql.rust.dataflow.TaintTracking +import codeql.rust.security.CleartextTransmissionExtensions + +/** + * A taint configuration from sensitive information to expressions that are + * transmitted over a network. + */ +module CleartextTransmissionConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { node instanceof SensitiveData } + + predicate isSink(DataFlow::Node node) { node instanceof CleartextTransmissionSink } + + predicate isBarrier(DataFlow::Node barrier) { barrier instanceof CleartextTransmissionBarrier } + + predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + any(CleartextTransmissionAdditionalFlowStep s).step(nodeFrom, nodeTo) + } + + predicate isBarrierIn(DataFlow::Node node) { + // make sources barriers so that we only report the closest instance + isSource(node) + } +} + +module CleartextTransmissionFlow = TaintTracking::Global; + +import CleartextTransmissionFlow::PathGraph + +from CleartextTransmissionFlow::PathNode sourceNode, CleartextTransmissionFlow::PathNode sinkNode +where CleartextTransmissionFlow::flowPath(sourceNode, sinkNode) +select sinkNode.getNode(), sourceNode, sinkNode, + "The operation '" + sinkNode.getNode().toString() + + "', transmits data which may contain unencrypted sensitive data from $@.", sourceNode, + sourceNode.getNode().toString() diff --git a/rust/ql/src/queries/security/CWE-311/CleartextTransmission.rs b/rust/ql/src/queries/security/CWE-311/CleartextTransmission.rs new file mode 100644 index 000000000000..9856818525d2 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-311/CleartextTransmission.rs @@ -0,0 +1,15 @@ +func getData() { + // ... + + // GOOD: not sensitive information + let body = reqwest::get("https://example.com/song/{faveSong}").await?.text().await?; + + // BAD: sensitive information sent in cleartext in the URL + let body = reqwest::get(format!("https://example.com/card/{creditCardNo}")).await?.text().await?; + + // GOOD: encrypted sensitive information sent in the URL + let encryptedPassword = encrypt(password, encryptionKey); + let body = reqwest::get(format!("https://example.com/card/{creditCardNo}")).await?.text().await?; + + // ... +} diff --git a/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected b/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected index 8a8c5988426a..bc048e010ee9 100644 --- a/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected +++ b/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected @@ -2216,6 +2216,7 @@ storeStep | file://:0:0:0:0 | [summary] to write: ReturnValue.Field[crate::result::Result::Ok(0)] in repo:https://github.com/seanmonstar/reqwest:reqwest::_::::chunk | Ok | file://:0:0:0:0 | [summary] to write: ReturnValue in repo:https://github.com/seanmonstar/reqwest:reqwest::_::::chunk | | file://:0:0:0:0 | [summary] to write: ReturnValue.Field[crate::result::Result::Ok(0)] in repo:https://github.com/seanmonstar/reqwest:reqwest::_::::text | Ok | file://:0:0:0:0 | [summary] to write: ReturnValue in repo:https://github.com/seanmonstar/reqwest:reqwest::_::::text | | file://:0:0:0:0 | [summary] to write: ReturnValue.Field[crate::result::Result::Ok(0)] in repo:https://github.com/seanmonstar/reqwest:reqwest::_::::text_with_charset | Ok | file://:0:0:0:0 | [summary] to write: ReturnValue in repo:https://github.com/seanmonstar/reqwest:reqwest::_::::text_with_charset | +| file://:0:0:0:0 | [summary] to write: ReturnValue.Field[crate::result::Result::Ok(0)] in repo:https://github.com/servo/rust-url:url::_::::parse | Ok | file://:0:0:0:0 | [summary] to write: ReturnValue in repo:https://github.com/servo/rust-url:url::_::::parse | | file://:0:0:0:0 | [summary] to write: ReturnValue.Field[crate::result::Result::Ok(0)].Field[0] in lang:alloc::_::::search_tree_for_bifurcation | tuple.0 | file://:0:0:0:0 | [summary] to write: ReturnValue.Field[crate::result::Result::Ok(0)] in lang:alloc::_::::search_tree_for_bifurcation | | file://:0:0:0:0 | [summary] to write: ReturnValue.Field[crate::result::Result::Ok(0)].Field[0] in lang:std::_::::wait_timeout | tuple.0 | file://:0:0:0:0 | [summary] to write: ReturnValue.Field[crate::result::Result::Ok(0)] in lang:std::_::::wait_timeout | | file://:0:0:0:0 | [summary] to write: ReturnValue.Field[crate::result::Result::Ok(0)].Field[0] in lang:std::_::::wait_timeout_ms | tuple.0 | file://:0:0:0:0 | [summary] to write: ReturnValue.Field[crate::result::Result::Ok(0)] in lang:std::_::::wait_timeout_ms | @@ -2494,6 +2495,7 @@ readStep | file://:0:0:0:0 | [summary param] 0 in lang:std::_::crate::thread::current::try_with_current | function return | file://:0:0:0:0 | [summary] read: Argument[0].ReturnValue in lang:std::_::crate::thread::current::try_with_current | | file://:0:0:0:0 | [summary param] 0 in lang:std::_::crate::thread::with_current_name | function return | file://:0:0:0:0 | [summary] read: Argument[0].ReturnValue in lang:std::_::crate::thread::with_current_name | | file://:0:0:0:0 | [summary param] 0 in repo:https://github.com/rust-lang/regex:regex::_::crate::escape | &ref | file://:0:0:0:0 | [summary] read: Argument[0].Reference in repo:https://github.com/rust-lang/regex:regex::_::crate::escape | +| file://:0:0:0:0 | [summary param] 0 in repo:https://github.com/servo/rust-url:url::_::::parse | &ref | file://:0:0:0:0 | [summary] read: Argument[0].Reference in repo:https://github.com/servo/rust-url:url::_::::parse | | file://:0:0:0:0 | [summary param] 1 in lang:alloc::_::::fold | function return | file://:0:0:0:0 | [summary] read: Argument[1].ReturnValue in lang:alloc::_::::fold | | file://:0:0:0:0 | [summary param] 1 in lang:alloc::_::crate::collections::btree::mem::replace | function return | file://:0:0:0:0 | [summary] read: Argument[1].ReturnValue in lang:alloc::_::crate::collections::btree::mem::replace | | file://:0:0:0:0 | [summary param] 1 in lang:alloc::_::crate::collections::btree::mem::take_mut | function return | file://:0:0:0:0 | [summary] read: Argument[1].ReturnValue in lang:alloc::_::crate::collections::btree::mem::take_mut | diff --git a/rust/ql/test/query-tests/security/CWE-020/RegexInjection.expected b/rust/ql/test/query-tests/security/CWE-020/RegexInjection.expected index fd7ded696334..e204b5a39264 100644 --- a/rust/ql/test/query-tests/security/CWE-020/RegexInjection.expected +++ b/rust/ql/test/query-tests/security/CWE-020/RegexInjection.expected @@ -2,15 +2,15 @@ | main.rs:6:25:6:30 | ®ex | main.rs:4:20:4:32 | ...::var | main.rs:6:25:6:30 | ®ex | This regular expression is constructed from a $@. | main.rs:4:20:4:32 | ...::var | user-provided value | edges | main.rs:4:9:4:16 | username | main.rs:5:25:5:44 | MacroExpr | provenance | | -| main.rs:4:20:4:32 | ...::var | main.rs:4:20:4:40 | ...::var(...) [Ok] | provenance | Src:MaD:60 | -| main.rs:4:20:4:40 | ...::var(...) [Ok] | main.rs:4:20:4:66 | ... .unwrap_or(...) | provenance | MaD:1573 | +| main.rs:4:20:4:32 | ...::var | main.rs:4:20:4:40 | ...::var(...) [Ok] | provenance | Src:MaD:62 | +| main.rs:4:20:4:40 | ...::var(...) [Ok] | main.rs:4:20:4:66 | ... .unwrap_or(...) | provenance | MaD:1593 | | main.rs:4:20:4:66 | ... .unwrap_or(...) | main.rs:4:9:4:16 | username | provenance | | | main.rs:5:9:5:13 | regex | main.rs:6:26:6:30 | regex | provenance | | | main.rs:5:17:5:45 | res | main.rs:5:25:5:44 | { ... } | provenance | | | main.rs:5:25:5:44 | ...::format(...) | main.rs:5:17:5:45 | res | provenance | | | main.rs:5:25:5:44 | ...::must_use(...) | main.rs:5:9:5:13 | regex | provenance | | -| main.rs:5:25:5:44 | MacroExpr | main.rs:5:25:5:44 | ...::format(...) | provenance | MaD:64 | -| main.rs:5:25:5:44 | { ... } | main.rs:5:25:5:44 | ...::must_use(...) | provenance | MaD:2996 | +| main.rs:5:25:5:44 | MacroExpr | main.rs:5:25:5:44 | ...::format(...) | provenance | MaD:66 | +| main.rs:5:25:5:44 | { ... } | main.rs:5:25:5:44 | ...::must_use(...) | provenance | MaD:3016 | | main.rs:6:26:6:30 | regex | main.rs:6:25:6:30 | ®ex | provenance | | nodes | main.rs:4:9:4:16 | username | semmle.label | username | diff --git a/rust/ql/test/query-tests/security/CWE-311/CleartextTransmission.expected b/rust/ql/test/query-tests/security/CWE-311/CleartextTransmission.expected new file mode 100644 index 000000000000..763558a9c16c --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-311/CleartextTransmission.expected @@ -0,0 +1,113 @@ +#select +| main.rs:7:5:7:26 | ...::get | main.rs:6:50:6:57 | password | main.rs:7:5:7:26 | ...::get | The operation '...::get', transmits data which may contain unencrypted sensitive data from $@. | main.rs:6:50:6:57 | password | password | +| main.rs:14:5:14:26 | ...::get | main.rs:12:50:12:57 | password | main.rs:14:5:14:26 | ...::get | The operation '...::get', transmits data which may contain unencrypted sensitive data from $@. | main.rs:12:50:12:57 | password | password | +| main.rs:21:12:21:15 | post | main.rs:19:50:19:57 | password | main.rs:21:12:21:15 | post | The operation 'post', transmits data which may contain unencrypted sensitive data from $@. | main.rs:19:50:19:57 | password | password | +| main.rs:28:12:28:18 | request | main.rs:26:50:26:57 | password | main.rs:28:12:28:18 | request | The operation 'request', transmits data which may contain unencrypted sensitive data from $@. | main.rs:26:50:26:57 | password | password | +| main.rs:35:12:35:18 | request | main.rs:33:50:33:57 | password | main.rs:35:12:35:18 | request | The operation 'request', transmits data which may contain unencrypted sensitive data from $@. | main.rs:33:50:33:57 | password | password | +edges +| main.rs:6:9:6:11 | url | main.rs:7:28:7:30 | url | provenance | | +| main.rs:6:15:6:58 | res | main.rs:6:23:6:57 | { ... } | provenance | | +| main.rs:6:23:6:57 | ...::format(...) | main.rs:6:15:6:58 | res | provenance | | +| main.rs:6:23:6:57 | ...::must_use(...) | main.rs:6:9:6:11 | url | provenance | | +| main.rs:6:23:6:57 | MacroExpr | main.rs:6:23:6:57 | ...::format(...) | provenance | MaD:5 | +| main.rs:6:23:6:57 | { ... } | main.rs:6:23:6:57 | ...::must_use(...) | provenance | MaD:7 | +| main.rs:6:50:6:57 | password | main.rs:6:23:6:57 | MacroExpr | provenance | | +| main.rs:7:28:7:30 | url | main.rs:7:5:7:26 | ...::get | provenance | MaD:4 Sink:MaD:4 | +| main.rs:12:9:12:15 | address | main.rs:13:27:13:33 | address | provenance | | +| main.rs:12:19:12:60 | res | main.rs:12:27:12:59 | { ... } | provenance | | +| main.rs:12:27:12:59 | ...::format(...) | main.rs:12:19:12:60 | res | provenance | | +| main.rs:12:27:12:59 | ...::must_use(...) | main.rs:12:9:12:15 | address | provenance | | +| main.rs:12:27:12:59 | MacroExpr | main.rs:12:27:12:59 | ...::format(...) | provenance | MaD:5 | +| main.rs:12:27:12:59 | { ... } | main.rs:12:27:12:59 | ...::must_use(...) | provenance | MaD:7 | +| main.rs:12:50:12:57 | password | main.rs:12:27:12:59 | MacroExpr | provenance | | +| main.rs:13:9:13:11 | url | main.rs:14:28:14:30 | url | provenance | | +| main.rs:13:15:13:34 | ...::parse(...) [Ok] | main.rs:13:15:13:43 | ... .unwrap(...) | provenance | MaD:6 | +| main.rs:13:15:13:43 | ... .unwrap(...) | main.rs:13:9:13:11 | url | provenance | | +| main.rs:13:26:13:33 | &address [&ref] | main.rs:13:15:13:34 | ...::parse(...) [Ok] | provenance | MaD:8 | +| main.rs:13:27:13:33 | address | main.rs:13:26:13:33 | &address [&ref] | provenance | | +| main.rs:14:28:14:30 | url | main.rs:14:5:14:26 | ...::get | provenance | MaD:4 Sink:MaD:4 | +| main.rs:19:9:19:11 | url | main.rs:21:17:21:19 | url | provenance | | +| main.rs:19:15:19:58 | res | main.rs:19:23:19:57 | { ... } | provenance | | +| main.rs:19:23:19:57 | ...::format(...) | main.rs:19:15:19:58 | res | provenance | | +| main.rs:19:23:19:57 | ...::must_use(...) | main.rs:19:9:19:11 | url | provenance | | +| main.rs:19:23:19:57 | MacroExpr | main.rs:19:23:19:57 | ...::format(...) | provenance | MaD:5 | +| main.rs:19:23:19:57 | { ... } | main.rs:19:23:19:57 | ...::must_use(...) | provenance | MaD:7 | +| main.rs:19:50:19:57 | password | main.rs:19:23:19:57 | MacroExpr | provenance | | +| main.rs:21:17:21:19 | url | main.rs:21:12:21:15 | post | provenance | MaD:1 Sink:MaD:1 | +| main.rs:26:9:26:11 | url | main.rs:28:33:28:35 | url | provenance | | +| main.rs:26:15:26:58 | res | main.rs:26:23:26:57 | { ... } | provenance | | +| main.rs:26:23:26:57 | ...::format(...) | main.rs:26:15:26:58 | res | provenance | | +| main.rs:26:23:26:57 | ...::must_use(...) | main.rs:26:9:26:11 | url | provenance | | +| main.rs:26:23:26:57 | MacroExpr | main.rs:26:23:26:57 | ...::format(...) | provenance | MaD:5 | +| main.rs:26:23:26:57 | { ... } | main.rs:26:23:26:57 | ...::must_use(...) | provenance | MaD:7 | +| main.rs:26:50:26:57 | password | main.rs:26:23:26:57 | MacroExpr | provenance | | +| main.rs:28:33:28:35 | url | main.rs:28:12:28:18 | request | provenance | MaD:3 Sink:MaD:3 | +| main.rs:33:9:33:11 | url | main.rs:35:33:35:35 | url | provenance | | +| main.rs:33:15:33:58 | res | main.rs:33:23:33:57 | { ... } | provenance | | +| main.rs:33:23:33:57 | ...::format(...) | main.rs:33:15:33:58 | res | provenance | | +| main.rs:33:23:33:57 | ...::must_use(...) | main.rs:33:9:33:11 | url | provenance | | +| main.rs:33:23:33:57 | MacroExpr | main.rs:33:23:33:57 | ...::format(...) | provenance | MaD:5 | +| main.rs:33:23:33:57 | { ... } | main.rs:33:23:33:57 | ...::must_use(...) | provenance | MaD:7 | +| main.rs:33:50:33:57 | password | main.rs:33:23:33:57 | MacroExpr | provenance | | +| main.rs:35:33:35:35 | url | main.rs:35:12:35:18 | request | provenance | MaD:2 Sink:MaD:2 | +models +| 1 | Sink: repo:https://github.com/seanmonstar/reqwest:reqwest; ::post; transmission; Argument[0] | +| 2 | Sink: repo:https://github.com/seanmonstar/reqwest:reqwest; ::request; transmission; Argument[1] | +| 3 | Sink: repo:https://github.com/seanmonstar/reqwest:reqwest; ::request; transmission; Argument[1] | +| 4 | Sink: repo:https://github.com/seanmonstar/reqwest:reqwest; crate::blocking::get; transmission; Argument[0] | +| 5 | Summary: lang:alloc; crate::fmt::format; Argument[0]; ReturnValue; taint | +| 6 | Summary: lang:core; ::unwrap; Argument[self].Field[crate::result::Result::Ok(0)]; ReturnValue; value | +| 7 | Summary: lang:core; crate::hint::must_use; Argument[0]; ReturnValue; value | +| 8 | Summary: repo:https://github.com/servo/rust-url:url; ::parse; Argument[0].Reference; ReturnValue.Field[crate::result::Result::Ok(0)]; taint | +nodes +| main.rs:6:9:6:11 | url | semmle.label | url | +| main.rs:6:15:6:58 | res | semmle.label | res | +| main.rs:6:23:6:57 | ...::format(...) | semmle.label | ...::format(...) | +| main.rs:6:23:6:57 | ...::must_use(...) | semmle.label | ...::must_use(...) | +| main.rs:6:23:6:57 | MacroExpr | semmle.label | MacroExpr | +| main.rs:6:23:6:57 | { ... } | semmle.label | { ... } | +| main.rs:6:50:6:57 | password | semmle.label | password | +| main.rs:7:5:7:26 | ...::get | semmle.label | ...::get | +| main.rs:7:28:7:30 | url | semmle.label | url | +| main.rs:12:9:12:15 | address | semmle.label | address | +| main.rs:12:19:12:60 | res | semmle.label | res | +| main.rs:12:27:12:59 | ...::format(...) | semmle.label | ...::format(...) | +| main.rs:12:27:12:59 | ...::must_use(...) | semmle.label | ...::must_use(...) | +| main.rs:12:27:12:59 | MacroExpr | semmle.label | MacroExpr | +| main.rs:12:27:12:59 | { ... } | semmle.label | { ... } | +| main.rs:12:50:12:57 | password | semmle.label | password | +| main.rs:13:9:13:11 | url | semmle.label | url | +| main.rs:13:15:13:34 | ...::parse(...) [Ok] | semmle.label | ...::parse(...) [Ok] | +| main.rs:13:15:13:43 | ... .unwrap(...) | semmle.label | ... .unwrap(...) | +| main.rs:13:26:13:33 | &address [&ref] | semmle.label | &address [&ref] | +| main.rs:13:27:13:33 | address | semmle.label | address | +| main.rs:14:5:14:26 | ...::get | semmle.label | ...::get | +| main.rs:14:28:14:30 | url | semmle.label | url | +| main.rs:19:9:19:11 | url | semmle.label | url | +| main.rs:19:15:19:58 | res | semmle.label | res | +| main.rs:19:23:19:57 | ...::format(...) | semmle.label | ...::format(...) | +| main.rs:19:23:19:57 | ...::must_use(...) | semmle.label | ...::must_use(...) | +| main.rs:19:23:19:57 | MacroExpr | semmle.label | MacroExpr | +| main.rs:19:23:19:57 | { ... } | semmle.label | { ... } | +| main.rs:19:50:19:57 | password | semmle.label | password | +| main.rs:21:12:21:15 | post | semmle.label | post | +| main.rs:21:17:21:19 | url | semmle.label | url | +| main.rs:26:9:26:11 | url | semmle.label | url | +| main.rs:26:15:26:58 | res | semmle.label | res | +| main.rs:26:23:26:57 | ...::format(...) | semmle.label | ...::format(...) | +| main.rs:26:23:26:57 | ...::must_use(...) | semmle.label | ...::must_use(...) | +| main.rs:26:23:26:57 | MacroExpr | semmle.label | MacroExpr | +| main.rs:26:23:26:57 | { ... } | semmle.label | { ... } | +| main.rs:26:50:26:57 | password | semmle.label | password | +| main.rs:28:12:28:18 | request | semmle.label | request | +| main.rs:28:33:28:35 | url | semmle.label | url | +| main.rs:33:9:33:11 | url | semmle.label | url | +| main.rs:33:15:33:58 | res | semmle.label | res | +| main.rs:33:23:33:57 | ...::format(...) | semmle.label | ...::format(...) | +| main.rs:33:23:33:57 | ...::must_use(...) | semmle.label | ...::must_use(...) | +| main.rs:33:23:33:57 | MacroExpr | semmle.label | MacroExpr | +| main.rs:33:23:33:57 | { ... } | semmle.label | { ... } | +| main.rs:33:50:33:57 | password | semmle.label | password | +| main.rs:35:12:35:18 | request | semmle.label | request | +| main.rs:35:33:35:35 | url | semmle.label | url | +subpaths diff --git a/rust/ql/test/query-tests/security/CWE-311/CleartextTransmission.qlref b/rust/ql/test/query-tests/security/CWE-311/CleartextTransmission.qlref new file mode 100644 index 000000000000..a674e4002aab --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-311/CleartextTransmission.qlref @@ -0,0 +1,4 @@ +query: queries/security/CWE-311/CleartextTransmission.ql +postprocess: + - utils/test/PrettyPrintModels.ql + - utils/test/InlineExpectationsTestQuery.ql diff --git a/rust/ql/test/query-tests/security/CWE-311/main.rs b/rust/ql/test/query-tests/security/CWE-311/main.rs new file mode 100644 index 000000000000..63d16a46a369 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-311/main.rs @@ -0,0 +1,44 @@ +use http::Method; +use url::Url; + +fn get_with_password_in_url() { + let password = "Hunter12"; + let url = format!("example.com?password={}", password); // $ Source + reqwest::blocking::get(url).unwrap().text().unwrap(); // $ Alert[rust/cleartext-transmission] +} + +fn get_with_password_in_constructed_url() { + let password = "Hunter12"; + let address = format!("example.com?password={password}"); // $ Source + let url = Url::parse(&address).unwrap(); + reqwest::blocking::get(url).unwrap().text().unwrap(); // $ Alert[rust/cleartext-transmission] +} + +fn post_with_password_in_url() { + let password = "Hunter12"; + let url = format!("example.com?password={}", password); // $ Source + let client = reqwest::Client::new(); + client.post(url).body("body").send(); // $ Alert[rust/cleartext-transmission] +} + +fn request_blocking_put_with_password_in_url() { + let password = "Hunter12"; + let url = format!("example.com?password={}", password); // $ Source + let client = reqwest::blocking::Client::new(); + client.request(Method::PUT, url).body("body").send(); // $ Alert[rust/cleartext-transmission] +} + +fn request_put_with_password_in_url() { + let password = "Hunter12"; + let url = format!("example.com?password={}", password); // $ Source + let client = reqwest::Client::new(); + client.request(Method::PUT, url).body("body").send(); // $ Alert[rust/cleartext-transmission] +} + +fn main() { + get_with_password_in_url(); + get_with_password_in_constructed_url(); + post_with_password_in_url(); + request_blocking_put_with_password_in_url(); + request_put_with_password_in_url(); +} diff --git a/rust/ql/test/query-tests/security/CWE-311/options.yml b/rust/ql/test/query-tests/security/CWE-311/options.yml new file mode 100644 index 000000000000..613dff78668c --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-311/options.yml @@ -0,0 +1,5 @@ +qltest_cargo_check: true +qltest_dependencies: + - reqwest = { version = "0.12.9", features = ["blocking"] } + - http = { version = "1.2.0" } + - url = { version = "2.5.4" }