From 509c6ffb7a67e00387f36c5d9da86793a86515fb Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 3 Jan 2025 11:20:08 +0000
Subject: [PATCH 01/18] Rust: Add tests for weak hashing.
---
.../query-tests/security/CWE-328/options.yml | 12 ++
.../test/query-tests/security/CWE-328/test.rs | 160 ++++++++++++++++++
2 files changed, 172 insertions(+)
create mode 100644 rust/ql/test/query-tests/security/CWE-328/options.yml
create mode 100644 rust/ql/test/query-tests/security/CWE-328/test.rs
diff --git a/rust/ql/test/query-tests/security/CWE-328/options.yml b/rust/ql/test/query-tests/security/CWE-328/options.yml
new file mode 100644
index 000000000000..3c69a7519e76
--- /dev/null
+++ b/rust/ql/test/query-tests/security/CWE-328/options.yml
@@ -0,0 +1,12 @@
+qltest_cargo_check: true
+qltest_dependencies:
+ - digest = { version = "0.10.7" }
+ - md-5 = { version = "0.10.6" }
+ - md5_alt = { package = "md5", version = "0.7.0" }
+ - sha1 = { version = "0.10.6" }
+ - sha1-checked = { version = "0.10.0" }
+ - sha3 = { version = "0.10.8" }
+ - argon2 = { version = "0.5.3" }
+ - serde = { version = "1.0.217", features = ["derive"] }
+ - serde_json = { version = "1.0.134" }
+ - serde_urlencoded = { version = "0.7.1" }
diff --git a/rust/ql/test/query-tests/security/CWE-328/test.rs b/rust/ql/test/query-tests/security/CWE-328/test.rs
new file mode 100644
index 000000000000..a837a360bda4
--- /dev/null
+++ b/rust/ql/test/query-tests/security/CWE-328/test.rs
@@ -0,0 +1,160 @@
+use md5::{Digest};
+use serde::{Serialize};
+use argon2::{PasswordHasher};
+
+// --- tests ---
+
+fn test_hash_algorithms(
+ harmless: &str, credit_card_no: &str, password: &str, encrypted_password: &str, salt: &str
+) {
+ // test hashing with different algorithms and data
+
+ // MD5
+ _ = md5::Md5::digest(harmless);
+ _ = md5::Md5::digest(credit_card_no); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(encrypted_password);
+
+ // MD5 (alternative / older library)
+ _ = md5_alt::compute(harmless);
+ _ = md5_alt::compute(credit_card_no); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5_alt::compute(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5_alt::compute(encrypted_password);
+
+ // SHA-1
+ _ = sha1::Sha1::digest(harmless);
+ _ = sha1::Sha1::digest(credit_card_no); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = sha1::Sha1::digest(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = sha1::Sha1::digest(encrypted_password);
+
+ // SHA-1 checked
+ _ = sha1_checked::Sha1::digest(harmless);
+ _ = sha1_checked::Sha1::digest(credit_card_no); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = sha1_checked::Sha1::digest(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = sha1_checked::Sha1::digest(encrypted_password);
+
+ // SHA-256 (appropriate for sensitive data hashing)
+ _ = sha3::Sha3_256::digest(harmless);
+ _ = sha3::Sha3_256::digest(credit_card_no);
+ _ = sha3::Sha3_256::digest(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = sha3::Sha3_256::digest(encrypted_password);
+
+ // Argon2 (appropriate for password hashing)
+ let argon2_salt = argon2::password_hash::Salt::from_b64(salt).unwrap();
+ _ = argon2::Argon2::default().hash_password(harmless.as_bytes(), argon2_salt).unwrap().to_string();
+ _ = argon2::Argon2::default().hash_password(credit_card_no.as_bytes(), argon2_salt).unwrap().to_string();
+ _ = argon2::Argon2::default().hash_password(password.as_bytes(), argon2_salt).unwrap().to_string();
+ _ = argon2::Argon2::default().hash_password(encrypted_password.as_bytes(), argon2_salt).unwrap().to_string();
+}
+
+fn test_hash_code_patterns(
+ harmless: &str, password: &str,
+ harmless_str: String, password_str: String,
+ harmless_arr: &[u8], password_arr: &[u8],
+ harmless_vec: Vec, password_vec: Vec
+) {
+ // test hashing with different code patterns
+
+ // hash different types of data
+ _ = md5::Md5::digest(harmless_str);
+ _ = md5::Md5::digest(password_str); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(harmless_arr);
+ _ = md5::Md5::digest(password_arr); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(harmless_vec);
+ _ = md5::Md5::digest(password_vec); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+
+ // hash through a hasher object
+ let mut md5_hasher = md5::Md5::new();
+ md5_hasher.update(b"abc");
+ md5_hasher.update(harmless);
+ md5_hasher.update(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5_hasher.finalize();
+
+ _ = md5::Md5::new().chain_update(harmless).chain_update(harmless).chain_update(harmless).finalize();
+ _ = md5::Md5::new().chain_update(harmless).chain_update(password).chain_update(harmless).finalize(); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+
+ _ = md5::Md5::new_with_prefix(harmless).finalize();
+ _ = md5::Md5::new_with_prefix(password).finalize(); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+
+ // hash transformed data
+ _ = md5::Md5::digest(harmless.trim());
+ _ = md5::Md5::digest(password.trim()); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(harmless.as_bytes());
+ _ = md5::Md5::digest(password.as_bytes()); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(std::str::from_utf8(harmless_arr).unwrap());
+ _ = md5::Md5::digest(std::str::from_utf8(password_arr).unwrap()); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+}
+
+#[derive(Serialize)]
+struct MyStruct1 {
+ id: u64,
+ data: String,
+}
+
+#[derive(Serialize)]
+struct MyStruct2 {
+ id: u64,
+ credit_card_no: String,
+}
+
+#[derive(Serialize)]
+struct MyStruct3 {
+ id: u64,
+ password: String,
+}
+
+fn test_hash_structs() {
+ // test hashing with data in a struct
+ let s1 = MyStruct1 {
+ id: 1,
+ data: "0123456789".to_string(),
+ };
+ let s2 = MyStruct2 {
+ id: 2,
+ credit_card_no: "0123456789".to_string(),
+ };
+ let s3 = MyStruct3 {
+ id: 3,
+ password: "0123456789".to_string(),
+ };
+
+ // serialize with serde
+ let str1a = serde_json::to_string(&s1).unwrap();
+ let str2a = serde_json::to_string(&s2).unwrap();
+ let str3a = serde_json::to_string(&s3).unwrap();
+ let str1b = serde_json::to_vec(&s1).unwrap();
+ let str2b = serde_json::to_vec(&s2).unwrap();
+ let str3b = serde_json::to_vec(&s3).unwrap();
+ let str1c = serde_urlencoded::to_string(&s1).unwrap();
+ let str2c = serde_urlencoded::to_string(&s2).unwrap();
+ let str3c = serde_urlencoded::to_string(&s3).unwrap();
+
+ // hash with MD5
+ let mut md5_hasher = md5::Md5::new();
+ md5_hasher.update(s1.data);
+ md5_hasher.update(s2.credit_card_no); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ md5_hasher.update(s3.password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ md5_hasher.update(str1a);
+ md5_hasher.update(str2a); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ md5_hasher.update(str3a); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ md5_hasher.update(str1b);
+ md5_hasher.update(str2b); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ md5_hasher.update(str3b); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ md5_hasher.update(str1c);
+ md5_hasher.update(str2c); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ md5_hasher.update(str3c); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5_hasher.finalize();
+}
+
+fn test_hash_file(
+ harmless_filename: &str, password_filename: &str
+) {
+ // test hashing files
+ let mut harmless_file = std::fs::File::open(harmless_filename).unwrap();
+ let mut password_file = std::fs::File::open(password_filename).unwrap();
+
+ let mut md5_hasher = md5::Md5::new();
+ _ = std::io::copy(&mut harmless_file, &mut md5_hasher);
+ _ = std::io::copy(&mut password_file, &mut md5_hasher); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5_hasher.finalize();
+}
From 8f4a52001f5ddd254187e5277cdfccdc5dd902b4 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 3 Jan 2025 13:21:09 +0000
Subject: [PATCH 02/18] Rust: Add query framework.
---
.../WeakSensitiveDataHashingExtensions.qll | 147 ++++++++++++++++++
.../CWE-328/WeakSensitiveDataHashing.ql | 117 ++++++++++++++
.../CWE-328/WeakSensitiveDataHashing.expected | 4 +
.../CWE-328/WeakSensitiveDataHashing.qlref | 4 +
4 files changed, 272 insertions(+)
create mode 100644 rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
create mode 100755 rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
create mode 100644 rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
create mode 100644 rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.qlref
diff --git a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
new file mode 100644
index 000000000000..24eede034491
--- /dev/null
+++ b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
@@ -0,0 +1,147 @@
+/**
+ * Provides default sources, sinks and sanitizers for detecting "use of a
+ * broken or weak cryptographic hashing algorithm on sensitive data"
+ * vulnerabilities, as well as extension points for adding your own. This is
+ * divided into two general cases:
+ * - hashing sensitive data
+ * - hashing passwords (which requires the hashing algorithm to be
+ * sufficiently computationally expensive in addition to other requirements)
+ */
+
+import rust
+private import codeql.rust.Concepts
+private import codeql.rust.dataflow.DataFlow
+
+/**
+ * Provides default sources, sinks and sanitizers for detecting "use of a broken or weak
+ * cryptographic hashing algorithm on sensitive data" vulnerabilities on sensitive data that does
+ * NOT require computationally expensive hashing, as well as extension points for adding your own.
+ *
+ * Also see the `ComputationallyExpensiveHashFunction` module.
+ */
+module NormalHashFunction {
+ /**
+ * A data flow source for "use of a broken or weak cryptographic hashing algorithm on sensitive
+ * data" vulnerabilities that does not require computationally expensive hashing. That is, a
+ * piece of sensitive data.
+ */
+ abstract class Source extends DataFlow::Node {
+ Source() { not this instanceof ComputationallyExpensiveHashFunction::Source }
+
+ /**
+ * Gets the classification of the sensitive data.
+ */
+ abstract string getClassification();
+ }
+
+ /**
+ * A data flow sink for "use of a broken or weak cryptographic hashing algorithm on sensitive
+ * data" vulnerabilities that applies to data that does not require computationally expensive
+ * hashing. That is, a broken or weak hashing algorithm.
+ */
+ abstract class Sink extends DataFlow::Node {
+ /**
+ * Gets the name of the weak hashing algorithm.
+ */
+ abstract string getAlgorithmName();
+ }
+
+ /**
+ * A barrier for "use of a broken or weak cryptographic hashing algorithm on sensitive data"
+ * vulnerabilities that applies to data that does not require computationally expensive hashing.
+ */
+ abstract class Barrier extends DataFlow::Node { }
+
+ // TODO: SensitiveDataSourceAsSource
+
+ /**
+ * A flow sink modelled by the `Cryptography` module.
+ */
+ class WeakHashingOperationInputAsSink extends Sink {
+ Cryptography::HashingAlgorithm algorithm;
+
+ WeakHashingOperationInputAsSink() {
+ exists(Cryptography::CryptographicOperation operation |
+ algorithm.isWeak() and
+ algorithm = operation.getAlgorithm() and
+ this = operation.getAnInput()
+ )
+ }
+
+ override string getAlgorithmName() { result = algorithm.getName() }
+ }
+}
+
+/**
+ * Provides default sources, sinks and sanitizers for detecting "use of a broken or weak
+ * cryptographic hashing algorithm on sensitive data" vulnerabilities on sensitive data that DOES
+ * require computationally expensive hashing, as well as extension points for adding your own.
+ *
+ * Also see the `NormalHashFunction` module.
+ */
+module ComputationallyExpensiveHashFunction {
+ /**
+ * A data flow source for "use of a broken or weak cryptographic hashing algorithm on sensitive
+ * data" vulnerabilities that does require computationally expensive hashing. That is, a
+ * password.
+ */
+ abstract class Source extends DataFlow::Node {
+ /**
+ * Gets the classification of the sensitive data.
+ */
+ abstract string getClassification();
+ }
+
+ /**
+ * A data flow sink for "use of a broken or weak cryptographic hashing algorithm on sensitive
+ * data" vulnerabilities that applies to data that does require computationally expensive
+ * hashing. That is, a broken or weak hashing algorithm or one that is not computationally
+ * expensive enough for password hashing.
+ */
+ abstract class Sink extends DataFlow::Node {
+ /**
+ * Gets the name of the weak hashing algorithm.
+ */
+ abstract string getAlgorithmName();
+
+ /**
+ * Holds if this sink is for a computationally expensive hash function (meaning that hash
+ * function is just weak in some other regard.
+ */
+ abstract predicate isComputationallyExpensive();
+ }
+
+ /**
+ * A barrier for "use of a broken or weak cryptographic hashing algorithm on sensitive data"
+ * vulnerabilities that applies to data that does require computationally expensive hashing.
+ */
+ abstract class Barrier extends DataFlow::Node { }
+
+ // TODO: PasswordSourceAsSource
+
+ /**
+ * A flow sink modelled by the `Cryptography` module.
+ */
+ class WeakPasswordHashingOperationInputSink extends Sink {
+ Cryptography::CryptographicAlgorithm algorithm;
+
+ WeakPasswordHashingOperationInputSink() {
+ exists(Cryptography::CryptographicOperation operation |
+ (
+ algorithm instanceof Cryptography::PasswordHashingAlgorithm and
+ algorithm.isWeak()
+ or
+ algorithm instanceof Cryptography::HashingAlgorithm // Note that HashingAlgorithm and PasswordHashingAlgorithm are disjoint
+ ) and
+ algorithm = operation.getAlgorithm() and
+ this = operation.getAnInput()
+ )
+ }
+
+ override string getAlgorithmName() { result = algorithm.getName() }
+
+ override predicate isComputationallyExpensive() {
+ algorithm instanceof Cryptography::PasswordHashingAlgorithm
+ }
+ }
+}
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
new file mode 100755
index 000000000000..8779ce56d273
--- /dev/null
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
@@ -0,0 +1,117 @@
+/**
+ * @name Use of a broken or weak cryptographic hashing algorithm on sensitive data
+ * @description Using broken or weak cryptographic hashing algorithms can compromise security.
+ * @kind path-problem
+ * @problem.severity warning
+ * @security-severity 7.5
+ * @precision high
+ * @id rust/weak-sensitive-data-hashing
+ * @tags security
+ * external/cwe/cwe-327
+ * external/cwe/cwe-328
+ * external/cwe/cwe-916
+ */
+
+import rust
+import codeql.rust.security.WeakSensitiveDataHashingExtensions
+import codeql.rust.dataflow.DataFlow
+import codeql.rust.dataflow.TaintTracking
+
+/**
+ * Provides a taint-tracking configuration for detecting use of a broken or weak
+ * cryptographic hash function on sensitive data, that does NOT require a
+ * computationally expensive hash function.
+ */
+module NormalHashFunctionFlow {
+ import NormalHashFunction
+
+ private module Config implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
+
+ predicate isBarrierIn(DataFlow::Node node) {
+ // make sources barriers so that we only report the closest instance
+ isSource(node)
+ }
+
+ predicate isBarrierOut(DataFlow::Node node) {
+ // make sinks barriers so that we only report the closest instance
+ isSink(node)
+ }
+ }
+
+ module Flow = TaintTracking::Global;
+}
+
+/**
+ * Provides a taint-tracking configuration for detecting use of a broken or weak
+ * cryptographic hashing algorithm on passwords.
+ *
+ * Passwords has stricter requirements on the hashing algorithm used (must be
+ * computationally expensive to prevent brute-force attacks).
+ */
+module ComputationallyExpensiveHashFunctionFlow {
+ import ComputationallyExpensiveHashFunction
+
+ private module Config implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
+
+ predicate isBarrierIn(DataFlow::Node node) {
+ // make sources barriers so that we only report the closest instance
+ isSource(node)
+ }
+
+ predicate isBarrierOut(DataFlow::Node node) {
+ // make sinks barriers so that we only report the closest instance
+ isSink(node)
+ }
+ }
+
+ module Flow = TaintTracking::Global;
+}
+
+/**
+ * Global taint-tracking for detecting both variants of "use of a broken or weak
+ * cryptographic hashing algorithm on sensitive data" vulnerabilities. The two configurations are
+ * merged to generate a combined path graph.
+ */
+module WeakSensitiveDataHashingFlow =
+ DataFlow::MergePathGraph;
+
+import WeakSensitiveDataHashingFlow::PathGraph
+
+from
+ WeakSensitiveDataHashingFlow::PathNode source, WeakSensitiveDataHashingFlow::PathNode sink,
+ string ending, string algorithmName, string classification
+where
+ NormalHashFunctionFlow::Flow::flowPath(source.asPathNode1(), sink.asPathNode1()) and
+ algorithmName = sink.getNode().(NormalHashFunction::Sink).getAlgorithmName() and
+ classification = source.getNode().(NormalHashFunction::Source).getClassification() and
+ ending = "."
+ or
+ ComputationallyExpensiveHashFunctionFlow::Flow::flowPath(source.asPathNode2(), sink.asPathNode2()) and
+ algorithmName = sink.getNode().(ComputationallyExpensiveHashFunction::Sink).getAlgorithmName() and
+ classification =
+ source.getNode().(ComputationallyExpensiveHashFunction::Source).getClassification() and
+ (
+ sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
+ ending = "."
+ or
+ not sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
+ ending =
+ " for " + classification +
+ " hashing, since it is not a computationally expensive hash function."
+ )
+select sink.getNode(), source, sink,
+ "$@ is used in a hashing algorithm (" + algorithmName + ") that is insecure" + ending,
+ source.getNode(), "Sensitive data (" + classification + ")"
diff --git a/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
new file mode 100644
index 000000000000..e217064d1dfc
--- /dev/null
+++ b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
@@ -0,0 +1,4 @@
+edges
+nodes
+subpaths
+#select
diff --git a/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.qlref b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.qlref
new file mode 100644
index 000000000000..718e500931d3
--- /dev/null
+++ b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.qlref
@@ -0,0 +1,4 @@
+query: queries/Security/CWE-328/WeakSensitiveDataHashing.ql
+postprocess:
+ - utils/test/PrettyPrintModels.ql
+ - utils/test/InlineExpectationsTestQuery.ql
From d72b978bc7b1eac5d32c52eb782dff94e78e0400 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 6 Jan 2025 14:26:20 +0000
Subject: [PATCH 03/18] Rust: Add sensitive data sources.
---
.../codeql/rust/security/SensitiveData.qll | 2 +-
.../WeakSensitiveDataHashingExtensions.qll | 30 +++++++++++++++++--
2 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/rust/ql/lib/codeql/rust/security/SensitiveData.qll b/rust/ql/lib/codeql/rust/security/SensitiveData.qll
index ac17a3ee0752..49db524cf235 100644
--- a/rust/ql/lib/codeql/rust/security/SensitiveData.qll
+++ b/rust/ql/lib/codeql/rust/security/SensitiveData.qll
@@ -6,7 +6,7 @@
*/
import rust
-private import internal.SensitiveDataHeuristics
+import internal.SensitiveDataHeuristics
private import codeql.rust.dataflow.DataFlow
/**
diff --git a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
index 24eede034491..bed193de32a0 100644
--- a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
+++ b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
@@ -10,6 +10,7 @@
import rust
private import codeql.rust.Concepts
+private import codeql.rust.security.SensitiveData
private import codeql.rust.dataflow.DataFlow
/**
@@ -23,7 +24,7 @@ module NormalHashFunction {
/**
* A data flow source for "use of a broken or weak cryptographic hashing algorithm on sensitive
* data" vulnerabilities that does not require computationally expensive hashing. That is, a
- * piece of sensitive data.
+ * piece of sensitive data that is not a password.
*/
abstract class Source extends DataFlow::Node {
Source() { not this instanceof ComputationallyExpensiveHashFunction::Source }
@@ -52,7 +53,19 @@ module NormalHashFunction {
*/
abstract class Barrier extends DataFlow::Node { }
- // TODO: SensitiveDataSourceAsSource
+ /**
+ * A flow source modelled by the `SensitiveData` library.
+ */
+ class SensitiveDataAsSource extends Source instanceof SensitiveData {
+ SensitiveDataAsSource() {
+ not this.(SensitiveData).getClassification() = SensitiveDataClassification::password() and // (covered in ComputationallyExpensiveHashFunction)
+ not this.(SensitiveData).getClassification() = SensitiveDataClassification::id() // (not accurate enough)
+ }
+
+ override SensitiveDataClassification getClassification() {
+ result = this.(SensitiveData).getClassification()
+ }
+ }
/**
* A flow sink modelled by the `Cryptography` module.
@@ -117,7 +130,18 @@ module ComputationallyExpensiveHashFunction {
*/
abstract class Barrier extends DataFlow::Node { }
- // TODO: PasswordSourceAsSource
+ /**
+ * A flow source modelled by the `SensitiveData` library.
+ */
+ class PasswordAsSource extends Source instanceof SensitiveData {
+ PasswordAsSource() {
+ this.(SensitiveData).getClassification() = SensitiveDataClassification::password()
+ }
+
+ override SensitiveDataClassification getClassification() {
+ result = this.(SensitiveData).getClassification()
+ }
+ }
/**
* A flow sink modelled by the `Cryptography` module.
From ae0f4f10def24ad664c0462de0e8fe0b795a33f0 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 6 Jan 2025 17:26:08 +0000
Subject: [PATCH 04/18] Rust: Add hash function sinks.
---
rust/ql/lib/codeql/rust/Frameworks.qll | 2 +-
.../{ => rustcrypto}/RustCrypto.qll | 28 +++++++++++
.../rustcrypto/rustcrypto.model.yml | 9 ++++
.../CWE-328/WeakSensitiveDataHashing.expected | 49 ++++++++++++++++++-
.../test/query-tests/security/CWE-328/test.rs | 22 ++++-----
5 files changed, 97 insertions(+), 13 deletions(-)
rename rust/ql/lib/codeql/rust/frameworks/{ => rustcrypto}/RustCrypto.qll (66%)
create mode 100644 rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml
diff --git a/rust/ql/lib/codeql/rust/Frameworks.qll b/rust/ql/lib/codeql/rust/Frameworks.qll
index 483056888ec6..6a5e95c82e56 100644
--- a/rust/ql/lib/codeql/rust/Frameworks.qll
+++ b/rust/ql/lib/codeql/rust/Frameworks.qll
@@ -3,6 +3,6 @@
*/
private import codeql.rust.frameworks.Reqwest
-private import codeql.rust.frameworks.RustCrypto
+private import codeql.rust.frameworks.rustcrypto.RustCrypto
private import codeql.rust.frameworks.stdlib.Env
private import codeql.rust.frameworks.Sqlx
diff --git a/rust/ql/lib/codeql/rust/frameworks/RustCrypto.qll b/rust/ql/lib/codeql/rust/frameworks/rustcrypto/RustCrypto.qll
similarity index 66%
rename from rust/ql/lib/codeql/rust/frameworks/RustCrypto.qll
rename to rust/ql/lib/codeql/rust/frameworks/rustcrypto/RustCrypto.qll
index 9dd40004766a..8a91044891f6 100644
--- a/rust/ql/lib/codeql/rust/frameworks/RustCrypto.qll
+++ b/rust/ql/lib/codeql/rust/frameworks/rustcrypto/RustCrypto.qll
@@ -5,6 +5,9 @@
private import rust
private import codeql.rust.Concepts
private import codeql.rust.dataflow.DataFlow
+private import codeql.rust.dataflow.FlowSource
+private import codeql.rust.dataflow.FlowSink
+private import codeql.rust.dataflow.internal.DataFlowImpl
bindingset[algorithmName]
private string simplifyAlgorithmName(string algorithmName) {
@@ -55,3 +58,28 @@ class StreamCipherInit extends Cryptography::CryptographicOperation::Range {
override Cryptography::BlockMode getBlockMode() { result = "" }
}
+
+/**
+ * An externally modelled operation that hashes data, for example a call to `md5::Md5::digest(data)`.
+ */
+class ModelledHashOperation extends Cryptography::CryptographicOperation::Range {
+ DataFlow::Node input;
+ CallExpr call;
+ string algorithmName;
+
+ ModelledHashOperation() {
+ sinkNode(input, "hasher-input") and
+ call = input.(Node::FlowSummaryNode).getSinkElement().getCall() and
+ call = this.asExpr().getExpr() and
+ algorithmName =
+ call.getFunction().(PathExpr).getPath().getQualifier().getPart().getNameRef().getText()
+ }
+
+ override DataFlow::Node getInitialization() { result = this }
+
+ override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(algorithmName) }
+
+ override DataFlow::Node getAnInput() { result = input }
+
+ override Cryptography::BlockMode getBlockMode() { none() } // (does not apply for hashing)
+}
diff --git a/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml b/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml
new file mode 100644
index 000000000000..3473ba51683d
--- /dev/null
+++ b/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml
@@ -0,0 +1,9 @@
+extensions:
+ - addsTo:
+ pack: codeql/rust-all
+ extensible: sinkModel
+ data:
+ - ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::new_with_prefix", "Argument[0]", "hasher-input", "manual"]
+ - ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::update", "Argument[0]", "hasher-input", "manual"]
+ - ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::chain_update", "Argument[0]", "hasher-input", "manual"]
+ - ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::digest", "Argument[0]", "hasher-input", "manual"]
diff --git a/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
index e217064d1dfc..aa91cb94090d 100644
--- a/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
+++ b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
@@ -1,4 +1,51 @@
+#select
+| test.rs:14:9:14:24 | ...::digest | test.rs:14:26:14:39 | credit_card_no | test.rs:14:9:14:24 | ...::digest | $@ is used in a hashing algorithm (MD5) that is insecure. | test.rs:14:26:14:39 | credit_card_no | Sensitive data (private) |
+| test.rs:15:9:15:24 | ...::digest | test.rs:15:26:15:33 | password | test.rs:15:9:15:24 | ...::digest | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:15:26:15:33 | password | Sensitive data (password) |
+| test.rs:26:9:26:26 | ...::digest | test.rs:26:28:26:41 | credit_card_no | test.rs:26:9:26:26 | ...::digest | $@ is used in a hashing algorithm (SHA1) that is insecure. | test.rs:26:28:26:41 | credit_card_no | Sensitive data (private) |
+| test.rs:27:9:27:26 | ...::digest | test.rs:27:28:27:35 | password | test.rs:27:9:27:26 | ...::digest | $@ is used in a hashing algorithm (SHA1) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:27:28:27:35 | password | Sensitive data (password) |
+| test.rs:32:9:32:34 | ...::digest | test.rs:32:36:32:49 | credit_card_no | test.rs:32:9:32:34 | ...::digest | $@ is used in a hashing algorithm (SHA1) that is insecure. | test.rs:32:36:32:49 | credit_card_no | Sensitive data (private) |
+| test.rs:33:9:33:34 | ...::digest | test.rs:33:36:33:43 | password | test.rs:33:9:33:34 | ...::digest | $@ is used in a hashing algorithm (SHA1) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:33:36:33:43 | password | Sensitive data (password) |
+| test.rs:39:9:39:30 | ...::digest | test.rs:39:32:39:39 | password | test.rs:39:9:39:30 | ...::digest | $@ is used in a hashing algorithm (SHA3256) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:39:32:39:39 | password | Sensitive data (password) |
+| test.rs:60:9:60:24 | ...::digest | test.rs:60:26:60:37 | password_str | test.rs:60:9:60:24 | ...::digest | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:60:26:60:37 | password_str | Sensitive data (password) |
+| test.rs:62:9:62:24 | ...::digest | test.rs:62:26:62:37 | password_arr | test.rs:62:9:62:24 | ...::digest | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:62:26:62:37 | password_arr | Sensitive data (password) |
+| test.rs:64:9:64:24 | ...::digest | test.rs:64:26:64:37 | password_vec | test.rs:64:9:64:24 | ...::digest | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:64:26:64:37 | password_vec | Sensitive data (password) |
+| test.rs:77:9:77:33 | ...::new_with_prefix | test.rs:77:35:77:42 | password | test.rs:77:9:77:33 | ...::new_with_prefix | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:77:35:77:42 | password | Sensitive data (password) |
edges
+| test.rs:14:26:14:39 | credit_card_no | test.rs:14:9:14:24 | ...::digest | provenance | MaD:1 |
+| test.rs:15:26:15:33 | password | test.rs:15:9:15:24 | ...::digest | provenance | MaD:1 |
+| test.rs:26:28:26:41 | credit_card_no | test.rs:26:9:26:26 | ...::digest | provenance | MaD:1 |
+| test.rs:27:28:27:35 | password | test.rs:27:9:27:26 | ...::digest | provenance | MaD:1 |
+| test.rs:32:36:32:49 | credit_card_no | test.rs:32:9:32:34 | ...::digest | provenance | MaD:1 |
+| test.rs:33:36:33:43 | password | test.rs:33:9:33:34 | ...::digest | provenance | MaD:1 |
+| test.rs:39:32:39:39 | password | test.rs:39:9:39:30 | ...::digest | provenance | MaD:1 |
+| test.rs:60:26:60:37 | password_str | test.rs:60:9:60:24 | ...::digest | provenance | MaD:1 |
+| test.rs:62:26:62:37 | password_arr | test.rs:62:9:62:24 | ...::digest | provenance | MaD:1 |
+| test.rs:64:26:64:37 | password_vec | test.rs:64:9:64:24 | ...::digest | provenance | MaD:1 |
+| test.rs:77:35:77:42 | password | test.rs:77:9:77:33 | ...::new_with_prefix | provenance | MaD:2 |
+models
+| 1 | Sink: repo:https://github.com/RustCrypto/traits:digest; <_ as crate::digest::Digest>::digest; hasher-input; Argument[0] |
+| 2 | Sink: repo:https://github.com/RustCrypto/traits:digest; <_ as crate::digest::Digest>::new_with_prefix; hasher-input; Argument[0] |
nodes
+| test.rs:14:9:14:24 | ...::digest | semmle.label | ...::digest |
+| test.rs:14:26:14:39 | credit_card_no | semmle.label | credit_card_no |
+| test.rs:15:9:15:24 | ...::digest | semmle.label | ...::digest |
+| test.rs:15:26:15:33 | password | semmle.label | password |
+| test.rs:26:9:26:26 | ...::digest | semmle.label | ...::digest |
+| test.rs:26:28:26:41 | credit_card_no | semmle.label | credit_card_no |
+| test.rs:27:9:27:26 | ...::digest | semmle.label | ...::digest |
+| test.rs:27:28:27:35 | password | semmle.label | password |
+| test.rs:32:9:32:34 | ...::digest | semmle.label | ...::digest |
+| test.rs:32:36:32:49 | credit_card_no | semmle.label | credit_card_no |
+| test.rs:33:9:33:34 | ...::digest | semmle.label | ...::digest |
+| test.rs:33:36:33:43 | password | semmle.label | password |
+| test.rs:39:9:39:30 | ...::digest | semmle.label | ...::digest |
+| test.rs:39:32:39:39 | password | semmle.label | password |
+| test.rs:60:9:60:24 | ...::digest | semmle.label | ...::digest |
+| test.rs:60:26:60:37 | password_str | semmle.label | password_str |
+| test.rs:62:9:62:24 | ...::digest | semmle.label | ...::digest |
+| test.rs:62:26:62:37 | password_arr | semmle.label | password_arr |
+| test.rs:64:9:64:24 | ...::digest | semmle.label | ...::digest |
+| test.rs:64:26:64:37 | password_vec | semmle.label | password_vec |
+| test.rs:77:9:77:33 | ...::new_with_prefix | semmle.label | ...::new_with_prefix |
+| test.rs:77:35:77:42 | password | semmle.label | password |
subpaths
-#select
diff --git a/rust/ql/test/query-tests/security/CWE-328/test.rs b/rust/ql/test/query-tests/security/CWE-328/test.rs
index a837a360bda4..1c97bf5773ac 100644
--- a/rust/ql/test/query-tests/security/CWE-328/test.rs
+++ b/rust/ql/test/query-tests/security/CWE-328/test.rs
@@ -11,8 +11,8 @@ fn test_hash_algorithms(
// MD5
_ = md5::Md5::digest(harmless);
- _ = md5::Md5::digest(credit_card_no); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
- _ = md5::Md5::digest(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(credit_card_no); // $ Source Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(password); // $ Source Alert[rust/weak-sensitive-data-hashing]
_ = md5::Md5::digest(encrypted_password);
// MD5 (alternative / older library)
@@ -23,20 +23,20 @@ fn test_hash_algorithms(
// SHA-1
_ = sha1::Sha1::digest(harmless);
- _ = sha1::Sha1::digest(credit_card_no); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
- _ = sha1::Sha1::digest(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = sha1::Sha1::digest(credit_card_no); // $ Source Alert[rust/weak-sensitive-data-hashing]
+ _ = sha1::Sha1::digest(password); // $ Source Alert[rust/weak-sensitive-data-hashing]
_ = sha1::Sha1::digest(encrypted_password);
// SHA-1 checked
_ = sha1_checked::Sha1::digest(harmless);
- _ = sha1_checked::Sha1::digest(credit_card_no); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
- _ = sha1_checked::Sha1::digest(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = sha1_checked::Sha1::digest(credit_card_no); // $ Source Alert[rust/weak-sensitive-data-hashing]
+ _ = sha1_checked::Sha1::digest(password); // $ Source Alert[rust/weak-sensitive-data-hashing]
_ = sha1_checked::Sha1::digest(encrypted_password);
// SHA-256 (appropriate for sensitive data hashing)
_ = sha3::Sha3_256::digest(harmless);
_ = sha3::Sha3_256::digest(credit_card_no);
- _ = sha3::Sha3_256::digest(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = sha3::Sha3_256::digest(password); // $ Source Alert[rust/weak-sensitive-data-hashing]
_ = sha3::Sha3_256::digest(encrypted_password);
// Argon2 (appropriate for password hashing)
@@ -57,11 +57,11 @@ fn test_hash_code_patterns(
// hash different types of data
_ = md5::Md5::digest(harmless_str);
- _ = md5::Md5::digest(password_str); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(password_str); // $ Source Alert[rust/weak-sensitive-data-hashing]
_ = md5::Md5::digest(harmless_arr);
- _ = md5::Md5::digest(password_arr); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(password_arr); // $ Source Alert[rust/weak-sensitive-data-hashing]
_ = md5::Md5::digest(harmless_vec);
- _ = md5::Md5::digest(password_vec); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::digest(password_vec); // $ Source Alert[rust/weak-sensitive-data-hashing]
// hash through a hasher object
let mut md5_hasher = md5::Md5::new();
@@ -74,7 +74,7 @@ fn test_hash_code_patterns(
_ = md5::Md5::new().chain_update(harmless).chain_update(password).chain_update(harmless).finalize(); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
_ = md5::Md5::new_with_prefix(harmless).finalize();
- _ = md5::Md5::new_with_prefix(password).finalize(); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5::Md5::new_with_prefix(password).finalize(); // $ Source Alert[rust/weak-sensitive-data-hashing]
// hash transformed data
_ = md5::Md5::digest(harmless.trim());
From babfa758a3a3d9f9fca5b0357a33fbbf6c2182cd Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 9 Jan 2025 17:38:05 +0000
Subject: [PATCH 05/18] Rust: Add models for an alternative md5 library.
---
.../rust/frameworks/rustcrypto/rustcrypto.model.yml | 1 +
.../security/CWE-328/WeakSensitiveDataHashing.expected | 9 +++++++++
rust/ql/test/query-tests/security/CWE-328/test.rs | 4 ++--
3 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml b/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml
index 3473ba51683d..fe3fd67a8fd4 100644
--- a/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml
+++ b/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml
@@ -7,3 +7,4 @@ extensions:
- ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::update", "Argument[0]", "hasher-input", "manual"]
- ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::chain_update", "Argument[0]", "hasher-input", "manual"]
- ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::digest", "Argument[0]", "hasher-input", "manual"]
+ - ["repo:https://github.com/stainless-steel/md5:md5", "crate::compute", "Argument[0]", "hasher-input", "manual"]
diff --git a/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
index aa91cb94090d..888902d913d1 100644
--- a/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
+++ b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
@@ -1,6 +1,8 @@
#select
| test.rs:14:9:14:24 | ...::digest | test.rs:14:26:14:39 | credit_card_no | test.rs:14:9:14:24 | ...::digest | $@ is used in a hashing algorithm (MD5) that is insecure. | test.rs:14:26:14:39 | credit_card_no | Sensitive data (private) |
| test.rs:15:9:15:24 | ...::digest | test.rs:15:26:15:33 | password | test.rs:15:9:15:24 | ...::digest | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:15:26:15:33 | password | Sensitive data (password) |
+| test.rs:20:9:20:24 | ...::compute | test.rs:20:26:20:39 | credit_card_no | test.rs:20:9:20:24 | ...::compute | $@ is used in a hashing algorithm (MD5) that is insecure. | test.rs:20:26:20:39 | credit_card_no | Sensitive data (private) |
+| test.rs:21:9:21:24 | ...::compute | test.rs:21:26:21:33 | password | test.rs:21:9:21:24 | ...::compute | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:21:26:21:33 | password | Sensitive data (password) |
| test.rs:26:9:26:26 | ...::digest | test.rs:26:28:26:41 | credit_card_no | test.rs:26:9:26:26 | ...::digest | $@ is used in a hashing algorithm (SHA1) that is insecure. | test.rs:26:28:26:41 | credit_card_no | Sensitive data (private) |
| test.rs:27:9:27:26 | ...::digest | test.rs:27:28:27:35 | password | test.rs:27:9:27:26 | ...::digest | $@ is used in a hashing algorithm (SHA1) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:27:28:27:35 | password | Sensitive data (password) |
| test.rs:32:9:32:34 | ...::digest | test.rs:32:36:32:49 | credit_card_no | test.rs:32:9:32:34 | ...::digest | $@ is used in a hashing algorithm (SHA1) that is insecure. | test.rs:32:36:32:49 | credit_card_no | Sensitive data (private) |
@@ -13,6 +15,8 @@
edges
| test.rs:14:26:14:39 | credit_card_no | test.rs:14:9:14:24 | ...::digest | provenance | MaD:1 |
| test.rs:15:26:15:33 | password | test.rs:15:9:15:24 | ...::digest | provenance | MaD:1 |
+| test.rs:20:26:20:39 | credit_card_no | test.rs:20:9:20:24 | ...::compute | provenance | MaD:3 |
+| test.rs:21:26:21:33 | password | test.rs:21:9:21:24 | ...::compute | provenance | MaD:3 |
| test.rs:26:28:26:41 | credit_card_no | test.rs:26:9:26:26 | ...::digest | provenance | MaD:1 |
| test.rs:27:28:27:35 | password | test.rs:27:9:27:26 | ...::digest | provenance | MaD:1 |
| test.rs:32:36:32:49 | credit_card_no | test.rs:32:9:32:34 | ...::digest | provenance | MaD:1 |
@@ -25,11 +29,16 @@ edges
models
| 1 | Sink: repo:https://github.com/RustCrypto/traits:digest; <_ as crate::digest::Digest>::digest; hasher-input; Argument[0] |
| 2 | Sink: repo:https://github.com/RustCrypto/traits:digest; <_ as crate::digest::Digest>::new_with_prefix; hasher-input; Argument[0] |
+| 3 | Sink: repo:https://github.com/stainless-steel/md5:md5; crate::compute; hasher-input; Argument[0] |
nodes
| test.rs:14:9:14:24 | ...::digest | semmle.label | ...::digest |
| test.rs:14:26:14:39 | credit_card_no | semmle.label | credit_card_no |
| test.rs:15:9:15:24 | ...::digest | semmle.label | ...::digest |
| test.rs:15:26:15:33 | password | semmle.label | password |
+| test.rs:20:9:20:24 | ...::compute | semmle.label | ...::compute |
+| test.rs:20:26:20:39 | credit_card_no | semmle.label | credit_card_no |
+| test.rs:21:9:21:24 | ...::compute | semmle.label | ...::compute |
+| test.rs:21:26:21:33 | password | semmle.label | password |
| test.rs:26:9:26:26 | ...::digest | semmle.label | ...::digest |
| test.rs:26:28:26:41 | credit_card_no | semmle.label | credit_card_no |
| test.rs:27:9:27:26 | ...::digest | semmle.label | ...::digest |
diff --git a/rust/ql/test/query-tests/security/CWE-328/test.rs b/rust/ql/test/query-tests/security/CWE-328/test.rs
index 1c97bf5773ac..768ab8a3f431 100644
--- a/rust/ql/test/query-tests/security/CWE-328/test.rs
+++ b/rust/ql/test/query-tests/security/CWE-328/test.rs
@@ -17,8 +17,8 @@ fn test_hash_algorithms(
// MD5 (alternative / older library)
_ = md5_alt::compute(harmless);
- _ = md5_alt::compute(credit_card_no); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
- _ = md5_alt::compute(password); // $ MISSING: Alert[rust/weak-sensitive-data-hashing]
+ _ = md5_alt::compute(credit_card_no); // $ Source Alert[rust/weak-sensitive-data-hashing]
+ _ = md5_alt::compute(password); // $ Source Alert[rust/weak-sensitive-data-hashing]
_ = md5_alt::compute(encrypted_password);
// SHA-1
From 59386597c382e74c04eb824eff33bbcc587bf3c8 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 9 Jan 2025 18:02:00 +0000
Subject: [PATCH 06/18] Rust: Add .qhelp.
---
.../CWE-328/WeakSensitiveDataHashing.qhelp | 109 ++++++++++++++++++
.../CWE-328/WeakSensitiveDataHashingBad.rs | 10 ++
.../CWE-328/WeakSensitiveDataHashingGood.rs | 11 ++
3 files changed, 130 insertions(+)
create mode 100755 rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
create mode 100755 rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashingBad.rs
create mode 100755 rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashingGood.rs
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
new file mode 100755
index 000000000000..6da303a42ce7
--- /dev/null
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
@@ -0,0 +1,109 @@
+
+
+
+
+ Using a broken or weak cryptographic hash function can leave data
+ vulnerable, and should not be used in security related code.
+
+
+
+ A strong cryptographic hash function should be resistant to:
+
+
+ -
+ Pre-image attacks. If you know a hash value
h(x),
+ you should not be able to easily find the input x.
+
+ -
+ Collision attacks. If you know a hash value
h(x),
+ you should not be able to easily find a different input
+ y
+ with the same hash value h(x) = h(y).
+
+ -
+ Brute force. For passwords and other data with limited
+ input space, if you know a hash value
h(x)
+ you should not be able to find the input x even using
+ a brute force attack (without significant computational effort).
+
+
+
+
+ As an example, both MD5 and SHA-1 are known to be vulnerable to collision attacks.
+
+
+
+ All of MD5, SHA-1, SHA-2 and SHA-3 are weak against offline brute forcing, so
+ they are not suitable for hashing passwords. This includes SHA-224, SHA-256,
+ SHA-384 and SHA-512, which are in the SHA-2 family.
+
+
+
+ Since it's OK to use a weak cryptographic hash function in a non-security
+ context, this query only alerts when these are used to hash sensitive
+ data (such as passwords, certificates, usernames).
+
+
+
+
+
+
+ Ensure that you use a strong, modern cryptographic hash function, such as:
+
+
+
+ -
+ Argon2, scrypt, bcrypt, or PBKDF2 for passwords and other data with limited input space where
+ a dictionary-like attack is feasible.
+
+ -
+ SHA-2, or SHA-3 in other cases.
+
+
+
+
+ Note that special purpose algorithms, which are used to ensure that a message comes from a
+ particular sender, exist for message authentication. These algorithms should be used when
+ appropriate, as they address common vulnerabilities of simple hashing schemes in this context.
+
+
+
+
+
+
+ The following examples show hashing sensitive data using the MD5 hashing algorithm that is known to be
+ vulnerable to collision attacks, and hashing passwords using the SHA-3 algorithm that is weak to brute
+ force attacks:
+
+
+
+
+ To make these secure, we can use the SHA-3 algorithm for sensitive data and Argon2 for passwords:
+
+
+
+
+
+
+ OWASP:
+
+ Password Storage Cheat Sheet
+
+ and
+
+ Transport Layer Security Cheat Sheet
+
+ GitHub:
+
+ RustCrypto: Hashes
+
+ and
+
+ RustCrypto: Password Hashes
+
+
+
+
+
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashingBad.rs b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashingBad.rs
new file mode 100755
index 000000000000..078fbe982e27
--- /dev/null
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashingBad.rs
@@ -0,0 +1,10 @@
+// MD5 is not appropriate for hashing sensitive data.
+let mut md5_hasher = md5::Md5::new();
+...
+md5_hasher.update(emergency_contact); // BAD
+md5_hasher.update(credit_card_no); // BAD
+...
+my_hash = md5_hasher.finalize();
+
+// SHA3-256 is not appropriate for hashing passwords.
+my_hash = sha3::Sha3_256::digest(password); // BAD
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashingGood.rs b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashingGood.rs
new file mode 100755
index 000000000000..e3417a488361
--- /dev/null
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashingGood.rs
@@ -0,0 +1,11 @@
+// SHA3-256 *is* appropriate for hashing sensitive data.
+let mut sha3_256_hasher = sha3::Sha3_256::new();
+...
+sha3_256_hasher.update(emergency_contact); // GOOD
+sha3_256_hasher.update(credit_card_no); // GOOD
+...
+my_hash = sha3_256_hasher.finalize();
+
+// Argon2 is appropriate for hashing passwords.
+let argon2_salt = argon2::password_hash::Salt::from_b64(salt)?;
+my_hash = argon2::Argon2::default().hash_password(password.as_bytes(), argon2_salt)?.to_string(); // GOOD
From 9b8f561614e963141af912371ad551e6695755da Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 10 Jan 2025 09:23:54 +0000
Subject: [PATCH 07/18] Rust: Add another reference.
---
.../queries/security/CWE-328/WeakSensitiveDataHashing.qhelp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
index 6da303a42ce7..0c25aa127417 100755
--- a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
@@ -103,6 +103,11 @@
RustCrypto: Password Hashes
+ The RustCrypto Book:
+
+ Password Hashing
+
+
From ae26cd6c32366d7491d1cd82a2fd7cc22053e6d1 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 10 Jan 2025 12:13:21 +0000
Subject: [PATCH 08/18] Rust: Update test for changes on main.
---
.../CWE-328/WeakSensitiveDataHashing.expected | 26 +++++++++----------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
index 888902d913d1..69e03bcca1ca 100644
--- a/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
+++ b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.expected
@@ -13,19 +13,19 @@
| test.rs:64:9:64:24 | ...::digest | test.rs:64:26:64:37 | password_vec | test.rs:64:9:64:24 | ...::digest | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:64:26:64:37 | password_vec | Sensitive data (password) |
| test.rs:77:9:77:33 | ...::new_with_prefix | test.rs:77:35:77:42 | password | test.rs:77:9:77:33 | ...::new_with_prefix | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test.rs:77:35:77:42 | password | Sensitive data (password) |
edges
-| test.rs:14:26:14:39 | credit_card_no | test.rs:14:9:14:24 | ...::digest | provenance | MaD:1 |
-| test.rs:15:26:15:33 | password | test.rs:15:9:15:24 | ...::digest | provenance | MaD:1 |
-| test.rs:20:26:20:39 | credit_card_no | test.rs:20:9:20:24 | ...::compute | provenance | MaD:3 |
-| test.rs:21:26:21:33 | password | test.rs:21:9:21:24 | ...::compute | provenance | MaD:3 |
-| test.rs:26:28:26:41 | credit_card_no | test.rs:26:9:26:26 | ...::digest | provenance | MaD:1 |
-| test.rs:27:28:27:35 | password | test.rs:27:9:27:26 | ...::digest | provenance | MaD:1 |
-| test.rs:32:36:32:49 | credit_card_no | test.rs:32:9:32:34 | ...::digest | provenance | MaD:1 |
-| test.rs:33:36:33:43 | password | test.rs:33:9:33:34 | ...::digest | provenance | MaD:1 |
-| test.rs:39:32:39:39 | password | test.rs:39:9:39:30 | ...::digest | provenance | MaD:1 |
-| test.rs:60:26:60:37 | password_str | test.rs:60:9:60:24 | ...::digest | provenance | MaD:1 |
-| test.rs:62:26:62:37 | password_arr | test.rs:62:9:62:24 | ...::digest | provenance | MaD:1 |
-| test.rs:64:26:64:37 | password_vec | test.rs:64:9:64:24 | ...::digest | provenance | MaD:1 |
-| test.rs:77:35:77:42 | password | test.rs:77:9:77:33 | ...::new_with_prefix | provenance | MaD:2 |
+| test.rs:14:26:14:39 | credit_card_no | test.rs:14:9:14:24 | ...::digest | provenance | MaD:1 Sink:MaD:1 |
+| test.rs:15:26:15:33 | password | test.rs:15:9:15:24 | ...::digest | provenance | MaD:1 Sink:MaD:1 |
+| test.rs:20:26:20:39 | credit_card_no | test.rs:20:9:20:24 | ...::compute | provenance | MaD:3 Sink:MaD:3 |
+| test.rs:21:26:21:33 | password | test.rs:21:9:21:24 | ...::compute | provenance | MaD:3 Sink:MaD:3 |
+| test.rs:26:28:26:41 | credit_card_no | test.rs:26:9:26:26 | ...::digest | provenance | MaD:1 Sink:MaD:1 |
+| test.rs:27:28:27:35 | password | test.rs:27:9:27:26 | ...::digest | provenance | MaD:1 Sink:MaD:1 |
+| test.rs:32:36:32:49 | credit_card_no | test.rs:32:9:32:34 | ...::digest | provenance | MaD:1 Sink:MaD:1 |
+| test.rs:33:36:33:43 | password | test.rs:33:9:33:34 | ...::digest | provenance | MaD:1 Sink:MaD:1 |
+| test.rs:39:32:39:39 | password | test.rs:39:9:39:30 | ...::digest | provenance | MaD:1 Sink:MaD:1 |
+| test.rs:60:26:60:37 | password_str | test.rs:60:9:60:24 | ...::digest | provenance | MaD:1 Sink:MaD:1 |
+| test.rs:62:26:62:37 | password_arr | test.rs:62:9:62:24 | ...::digest | provenance | MaD:1 Sink:MaD:1 |
+| test.rs:64:26:64:37 | password_vec | test.rs:64:9:64:24 | ...::digest | provenance | MaD:1 Sink:MaD:1 |
+| test.rs:77:35:77:42 | password | test.rs:77:9:77:33 | ...::new_with_prefix | provenance | MaD:2 Sink:MaD:2 |
models
| 1 | Sink: repo:https://github.com/RustCrypto/traits:digest; <_ as crate::digest::Digest>::digest; hasher-input; Argument[0] |
| 2 | Sink: repo:https://github.com/RustCrypto/traits:digest; <_ as crate::digest::Digest>::new_with_prefix; hasher-input; Argument[0] |
From c115169dbeaaae3c1396a25bb6a21eee326aaecc Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 10 Jan 2025 12:35:45 +0000
Subject: [PATCH 09/18] Rust: Move ModelledHashOperation to a more logical
location.
---
.../rust/frameworks/rustcrypto/RustCrypto.qll | 28 -------------------
.../WeakSensitiveDataHashingExtensions.qll | 28 +++++++++++++++++++
2 files changed, 28 insertions(+), 28 deletions(-)
diff --git a/rust/ql/lib/codeql/rust/frameworks/rustcrypto/RustCrypto.qll b/rust/ql/lib/codeql/rust/frameworks/rustcrypto/RustCrypto.qll
index 8a91044891f6..9dd40004766a 100644
--- a/rust/ql/lib/codeql/rust/frameworks/rustcrypto/RustCrypto.qll
+++ b/rust/ql/lib/codeql/rust/frameworks/rustcrypto/RustCrypto.qll
@@ -5,9 +5,6 @@
private import rust
private import codeql.rust.Concepts
private import codeql.rust.dataflow.DataFlow
-private import codeql.rust.dataflow.FlowSource
-private import codeql.rust.dataflow.FlowSink
-private import codeql.rust.dataflow.internal.DataFlowImpl
bindingset[algorithmName]
private string simplifyAlgorithmName(string algorithmName) {
@@ -58,28 +55,3 @@ class StreamCipherInit extends Cryptography::CryptographicOperation::Range {
override Cryptography::BlockMode getBlockMode() { result = "" }
}
-
-/**
- * An externally modelled operation that hashes data, for example a call to `md5::Md5::digest(data)`.
- */
-class ModelledHashOperation extends Cryptography::CryptographicOperation::Range {
- DataFlow::Node input;
- CallExpr call;
- string algorithmName;
-
- ModelledHashOperation() {
- sinkNode(input, "hasher-input") and
- call = input.(Node::FlowSummaryNode).getSinkElement().getCall() and
- call = this.asExpr().getExpr() and
- algorithmName =
- call.getFunction().(PathExpr).getPath().getQualifier().getPart().getNameRef().getText()
- }
-
- override DataFlow::Node getInitialization() { result = this }
-
- override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(algorithmName) }
-
- override DataFlow::Node getAnInput() { result = input }
-
- override Cryptography::BlockMode getBlockMode() { none() } // (does not apply for hashing)
-}
diff --git a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
index bed193de32a0..90c57db3b669 100644
--- a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
+++ b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
@@ -12,6 +12,9 @@ import rust
private import codeql.rust.Concepts
private import codeql.rust.security.SensitiveData
private import codeql.rust.dataflow.DataFlow
+private import codeql.rust.dataflow.FlowSource
+private import codeql.rust.dataflow.FlowSink
+private import codeql.rust.dataflow.internal.DataFlowImpl
/**
* Provides default sources, sinks and sanitizers for detecting "use of a broken or weak
@@ -169,3 +172,28 @@ module ComputationallyExpensiveHashFunction {
}
}
}
+
+/**
+ * An externally modelled operation that hashes data, for example a call to `md5::Md5::digest(data)`.
+ */
+class ModelledHashOperation extends Cryptography::CryptographicOperation::Range {
+ DataFlow::Node input;
+ CallExpr call;
+ string algorithmName;
+
+ ModelledHashOperation() {
+ sinkNode(input, "hasher-input") and
+ call = input.(Node::FlowSummaryNode).getSinkElement().getCall() and
+ call = this.asExpr().getExpr() and
+ algorithmName =
+ call.getFunction().(PathExpr).getPath().getQualifier().getPart().getNameRef().getText()
+ }
+
+ override DataFlow::Node getInitialization() { result = this }
+
+ override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(algorithmName) }
+
+ override DataFlow::Node getAnInput() { result = input }
+
+ override Cryptography::BlockMode getBlockMode() { none() } // (does not apply for hashing)
+}
From bb4322cf7c436ff29ed485d423ba7fb9b9542fbb Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 10 Jan 2025 12:39:10 +0000
Subject: [PATCH 10/18] Rust: Make a type more accurate.
---
.../codeql/rust/security/WeakSensitiveDataHashingExtensions.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
index 90c57db3b669..37caec9e64c8 100644
--- a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
+++ b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
@@ -191,7 +191,7 @@ class ModelledHashOperation extends Cryptography::CryptographicOperation::Range
override DataFlow::Node getInitialization() { result = this }
- override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(algorithmName) }
+ override Cryptography::HashingAlgorithm getAlgorithm() { result.matchesName(algorithmName) }
override DataFlow::Node getAnInput() { result = input }
From 39a38c4c53325c4f8eaf75b887cd43f0a504cb99 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 10 Jan 2025 12:48:53 +0000
Subject: [PATCH 11/18] Rust: Tweak .qhelp layout.
---
.../CWE-328/WeakSensitiveDataHashing.qhelp | 62 ++++++++++---------
1 file changed, 32 insertions(+), 30 deletions(-)
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
index 0c25aa127417..ffe905ea4231 100755
--- a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
@@ -4,31 +4,31 @@
- Using a broken or weak cryptographic hash function can leave data
+ A broken or weak cryptographic hash function can leave data
vulnerable, and should not be used in security related code.
A strong cryptographic hash function should be resistant to:
+
+ -
+ Pre-image attacks. If you know a hash value
h(x),
+ you should not be able to easily find the input x.
+
+ -
+ Collision attacks. If you know a hash value
h(x),
+ you should not be able to easily find a different input
+ y
+ with the same hash value h(x) = h(y).
+
+ -
+ Brute force. For passwords and other data with limited
+ input space, if you know a hash value
h(x)
+ you should not be able to find the input x even using
+ a brute force attack (without significant computational effort).
+
+
-
- -
- Pre-image attacks. If you know a hash value
h(x),
- you should not be able to easily find the input x.
-
- -
- Collision attacks. If you know a hash value
h(x),
- you should not be able to easily find a different input
- y
- with the same hash value h(x) = h(y).
-
- -
- Brute force. For passwords and other data with limited
- input space, if you know a hash value
h(x)
- you should not be able to find the input x even using
- a brute force attack (without significant computational effort).
-
-
As an example, both MD5 and SHA-1 are known to be vulnerable to collision attacks.
@@ -51,18 +51,17 @@
Ensure that you use a strong, modern cryptographic hash function, such as:
+
+ -
+ Argon2, scrypt, bcrypt, or PBKDF2 for passwords and other data with limited input space where
+ a dictionary-like attack is feasible.
+
+ -
+ SHA-2, or SHA-3 in other cases.
+
+
-
- -
- Argon2, scrypt, bcrypt, or PBKDF2 for passwords and other data with limited input space where
- a dictionary-like attack is feasible.
-
- -
- SHA-2, or SHA-3 in other cases.
-
-
-
Note that special purpose algorithms, which are used to ensure that a message comes from a
particular sender, exist for message authentication. These algorithms should be used when
@@ -95,6 +94,8 @@
Transport Layer Security Cheat Sheet
+
+
GitHub:
RustCrypto: Hashes
@@ -103,11 +104,12 @@
RustCrypto: Password Hashes
+
+
The RustCrypto Book:
Password Hashing
-
From ad268220bfed2ba23c3a214642bc0266c42fd84b Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 10 Jan 2025 12:53:12 +0000
Subject: [PATCH 12/18] Rust: Address QL-for-QL comments.
---
.../WeakSensitiveDataHashingExtensions.qll | 27 ++++++++++---------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
index 37caec9e64c8..80612d615764 100644
--- a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
+++ b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
@@ -57,7 +57,7 @@ module NormalHashFunction {
abstract class Barrier extends DataFlow::Node { }
/**
- * A flow source modelled by the `SensitiveData` library.
+ * A flow source modeled by the `SensitiveData` library.
*/
class SensitiveDataAsSource extends Source instanceof SensitiveData {
SensitiveDataAsSource() {
@@ -71,7 +71,7 @@ module NormalHashFunction {
}
/**
- * A flow sink modelled by the `Cryptography` module.
+ * A flow sink modeled by the `Cryptography` module.
*/
class WeakHashingOperationInputAsSink extends Sink {
Cryptography::HashingAlgorithm algorithm;
@@ -134,7 +134,7 @@ module ComputationallyExpensiveHashFunction {
abstract class Barrier extends DataFlow::Node { }
/**
- * A flow source modelled by the `SensitiveData` library.
+ * A flow source modeled by the `SensitiveData` library.
*/
class PasswordAsSource extends Source instanceof SensitiveData {
PasswordAsSource() {
@@ -147,7 +147,7 @@ module ComputationallyExpensiveHashFunction {
}
/**
- * A flow sink modelled by the `Cryptography` module.
+ * A flow sink modeled by the `Cryptography` module.
*/
class WeakPasswordHashingOperationInputSink extends Sink {
Cryptography::CryptographicAlgorithm algorithm;
@@ -174,19 +174,20 @@ module ComputationallyExpensiveHashFunction {
}
/**
- * An externally modelled operation that hashes data, for example a call to `md5::Md5::digest(data)`.
+ * An externally modeled operation that hashes data, for example a call to `md5::Md5::digest(data)`.
*/
-class ModelledHashOperation extends Cryptography::CryptographicOperation::Range {
+class ModeledHashOperation extends Cryptography::CryptographicOperation::Range {
DataFlow::Node input;
- CallExpr call;
string algorithmName;
- ModelledHashOperation() {
- sinkNode(input, "hasher-input") and
- call = input.(Node::FlowSummaryNode).getSinkElement().getCall() and
- call = this.asExpr().getExpr() and
- algorithmName =
- call.getFunction().(PathExpr).getPath().getQualifier().getPart().getNameRef().getText()
+ ModeledHashOperation() {
+ exists(CallExpr call |
+ sinkNode(input, "hasher-input") and
+ call = input.(Node::FlowSummaryNode).getSinkElement().getCall() and
+ call = this.asExpr().getExpr() and
+ algorithmName =
+ call.getFunction().(PathExpr).getPath().getQualifier().getPart().getNameRef().getText()
+ )
}
override DataFlow::Node getInitialization() { result = this }
From 19d3e9dbcadbc45a4f2edc5e59b9f9cb8c59d657 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 10 Jan 2025 14:19:12 +0000
Subject: [PATCH 13/18] Rust: Correct the qhelp.
---
.../CWE-328/WeakSensitiveDataHashing.qhelp | 55 ++++++++++---------
1 file changed, 28 insertions(+), 27 deletions(-)
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
index ffe905ea4231..44f824d89fdc 100755
--- a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
@@ -10,25 +10,25 @@
A strong cryptographic hash function should be resistant to:
-
- -
- Pre-image attacks. If you know a hash value
h(x),
- you should not be able to easily find the input x.
-
- -
- Collision attacks. If you know a hash value
h(x),
- you should not be able to easily find a different input
- y
- with the same hash value h(x) = h(y).
-
- -
- Brute force. For passwords and other data with limited
- input space, if you know a hash value
h(x)
- you should not be able to find the input x even using
- a brute force attack (without significant computational effort).
-
-
+
+ -
+ Pre-image attacks. If you know a hash value
h(x),
+ you should not be able to easily find the input x.
+
+ -
+ Collision attacks. If you know a hash value
h(x),
+ you should not be able to easily find a different input
+ y
+ with the same hash value h(x) = h(y).
+
+ -
+ Brute force. For passwords and other data with limited
+ input space, if you know a hash value
h(x)
+ you should not be able to find the input x even using
+ a brute force attack (without significant computational effort).
+
+
As an example, both MD5 and SHA-1 are known to be vulnerable to collision attacks.
@@ -51,17 +51,18 @@
Ensure that you use a strong, modern cryptographic hash function, such as:
-
- -
- Argon2, scrypt, bcrypt, or PBKDF2 for passwords and other data with limited input space where
- a dictionary-like attack is feasible.
-
- -
- SHA-2, or SHA-3 in other cases.
-
-
+
+ -
+ Argon2, scrypt, bcrypt, or PBKDF2 for passwords and other data with limited input space where
+ a dictionary-like attack is feasible.
+
+ -
+ SHA-2, or SHA-3 in other cases.
+
+
+
Note that special purpose algorithms, which are used to ensure that a message comes from a
particular sender, exist for message authentication. These algorithms should be used when
From 1b6c289cb4c222f6e65a6a705b62d134e9fd4857 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 10 Jan 2025 14:38:05 +0000
Subject: [PATCH 14/18] Rust: Unrelated MaD test impact. :(
---
.../test/library-tests/dataflow/taint/TaintFlowStep.expected | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rust/ql/test/library-tests/dataflow/taint/TaintFlowStep.expected b/rust/ql/test/library-tests/dataflow/taint/TaintFlowStep.expected
index 5233ed1d139f..b2b6872a6d25 100644
--- a/rust/ql/test/library-tests/dataflow/taint/TaintFlowStep.expected
+++ b/rust/ql/test/library-tests/dataflow/taint/TaintFlowStep.expected
@@ -1,5 +1,5 @@
-| file://:0:0:0:0 | [summary param] 0 in lang:alloc::_::crate::fmt::format | file://:0:0:0:0 | [summary] to write: ReturnValue in lang:alloc::_::crate::fmt::format | MaD:9 |
-| file://:0:0:0:0 | [summary param] self in lang:alloc::_::::as_str | file://:0:0:0:0 | [summary] to write: ReturnValue in lang:alloc::_::::as_str | MaD:7 |
+| file://:0:0:0:0 | [summary param] 0 in lang:alloc::_::crate::fmt::format | file://:0:0:0:0 | [summary] to write: ReturnValue in lang:alloc::_::crate::fmt::format | MaD:14 |
+| file://:0:0:0:0 | [summary param] self in lang:alloc::_::::as_str | file://:0:0:0:0 | [summary] to write: ReturnValue in lang:alloc::_::::as_str | MaD:12 |
| file://:0:0:0:0 | [summary param] self in repo:https://github.com/seanmonstar/reqwest:reqwest::_::::text | file://:0:0:0:0 | [summary] to write: ReturnValue.Variant[crate::result::Result::Ok(0)] in repo:https://github.com/seanmonstar/reqwest:reqwest::_::::text | MaD:0 |
| main.rs:4:5:4:8 | 1000 | main.rs:4:5:4:12 | ... + ... | |
| main.rs:4:12:4:12 | i | main.rs:4:5:4:12 | ... + ... | |
From edd1f257ad6a6b8d508b5c1e2fc860b1c7eb76a9 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 10 Jan 2025 14:51:15 +0000
Subject: [PATCH 15/18] Rust: Attempt to fix the test on CI.
---
.../query-tests/security/CWE-328/WeakSensitiveDataHashing.qlref | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.qlref b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.qlref
index 718e500931d3..fad3080280be 100644
--- a/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.qlref
+++ b/rust/ql/test/query-tests/security/CWE-328/WeakSensitiveDataHashing.qlref
@@ -1,4 +1,4 @@
-query: queries/Security/CWE-328/WeakSensitiveDataHashing.ql
+query: queries/security/CWE-328/WeakSensitiveDataHashing.ql
postprocess:
- utils/test/PrettyPrintModels.ql
- utils/test/InlineExpectationsTestQuery.ql
From 722b7bb55b218b5449614e19de3a7334bb27b713 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 13 Jan 2025 10:28:08 +0000
Subject: [PATCH 16/18] Apply suggestions from code review
Co-authored-by: mc <42146119+mchammer01@users.noreply.github.com>
---
.../CWE-328/WeakSensitiveDataHashing.qhelp | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
index 44f824d89fdc..8ee0fc81a807 100755
--- a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
@@ -5,7 +5,7 @@
A broken or weak cryptographic hash function can leave data
- vulnerable, and should not be used in security related code.
+ vulnerable, and should not be used in security-related code.
@@ -24,7 +24,7 @@
Brute force. For passwords and other data with limited
- input space, if you know a hash value h(x)
+ input space, if you know a hash value h(x),
you should not be able to find the input x even using
a brute force attack (without significant computational effort).
@@ -37,7 +37,8 @@
All of MD5, SHA-1, SHA-2 and SHA-3 are weak against offline brute forcing, so
they are not suitable for hashing passwords. This includes SHA-224, SHA-256,
- SHA-384 and SHA-512, which are in the SHA-2 family.
+
+ SHA-384, and SHA-512, which are in the SHA-2 family.
@@ -94,7 +95,8 @@
and
Transport Layer Security Cheat Sheet
-
+
+ .
GitHub:
@@ -104,13 +106,13 @@
and
RustCrypto: Password Hashes
-
+ .
The RustCrypto Book:
Password Hashing
-
+ .
From 676141bbb9d50ad4dbf33a758eeba59c5ac6eb0a Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 13 Jan 2025 10:36:46 +0000
Subject: [PATCH 17/18] Rust: More suggestions from review.
---
.../WeakSensitiveDataHashingExtensions.qll | 10 +++++-----
.../CWE-328/WeakSensitiveDataHashing.qhelp | 1 -
.../security/CWE-328/WeakSensitiveDataHashing.ql | 16 ++++++++--------
3 files changed, 13 insertions(+), 14 deletions(-)
diff --git a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
index 80612d615764..8407ee4467d1 100644
--- a/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
+++ b/rust/ql/lib/codeql/rust/security/WeakSensitiveDataHashingExtensions.qll
@@ -61,12 +61,12 @@ module NormalHashFunction {
*/
class SensitiveDataAsSource extends Source instanceof SensitiveData {
SensitiveDataAsSource() {
- not this.(SensitiveData).getClassification() = SensitiveDataClassification::password() and // (covered in ComputationallyExpensiveHashFunction)
- not this.(SensitiveData).getClassification() = SensitiveDataClassification::id() // (not accurate enough)
+ not SensitiveData.super.getClassification() = SensitiveDataClassification::password() and // (covered in ComputationallyExpensiveHashFunction)
+ not SensitiveData.super.getClassification() = SensitiveDataClassification::id() // (not accurate enough)
}
override SensitiveDataClassification getClassification() {
- result = this.(SensitiveData).getClassification()
+ result = SensitiveData.super.getClassification()
}
}
@@ -138,11 +138,11 @@ module ComputationallyExpensiveHashFunction {
*/
class PasswordAsSource extends Source instanceof SensitiveData {
PasswordAsSource() {
- this.(SensitiveData).getClassification() = SensitiveDataClassification::password()
+ SensitiveData.super.getClassification() = SensitiveDataClassification::password()
}
override SensitiveDataClassification getClassification() {
- result = this.(SensitiveData).getClassification()
+ result = SensitiveData.super.getClassification()
}
}
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
index 8ee0fc81a807..9fbeb22d39db 100755
--- a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.qhelp
@@ -95,7 +95,6 @@
and
Transport Layer Security Cheat Sheet
-
.
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
index 8779ce56d273..b7906d9af127 100755
--- a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
@@ -43,7 +43,7 @@ module NormalHashFunctionFlow {
}
}
- module Flow = TaintTracking::Global;
+ import TaintTracking::Global
}
/**
@@ -74,7 +74,7 @@ module ComputationallyExpensiveHashFunctionFlow {
}
}
- module Flow = TaintTracking::Global;
+ import TaintTracking::Global
}
/**
@@ -83,10 +83,10 @@ module ComputationallyExpensiveHashFunctionFlow {
* merged to generate a combined path graph.
*/
module WeakSensitiveDataHashingFlow =
- DataFlow::MergePathGraph;
+ DataFlow::MergePathGraph;
import WeakSensitiveDataHashingFlow::PathGraph
@@ -94,12 +94,12 @@ from
WeakSensitiveDataHashingFlow::PathNode source, WeakSensitiveDataHashingFlow::PathNode sink,
string ending, string algorithmName, string classification
where
- NormalHashFunctionFlow::Flow::flowPath(source.asPathNode1(), sink.asPathNode1()) and
+ NormalHashFunctionFlow::flowPath(source.asPathNode1(), sink.asPathNode1()) and
algorithmName = sink.getNode().(NormalHashFunction::Sink).getAlgorithmName() and
classification = source.getNode().(NormalHashFunction::Source).getClassification() and
ending = "."
or
- ComputationallyExpensiveHashFunctionFlow::Flow::flowPath(source.asPathNode2(), sink.asPathNode2()) and
+ ComputationallyExpensiveHashFunctionFlow::flowPath(source.asPathNode2(), sink.asPathNode2()) and
algorithmName = sink.getNode().(ComputationallyExpensiveHashFunction::Sink).getAlgorithmName() and
classification =
source.getNode().(ComputationallyExpensiveHashFunction::Source).getClassification() and
From e61d6aec224c02c2bb0d403163638aade2abb0cd Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 13 Jan 2025 10:51:49 +0000
Subject: [PATCH 18/18] Rust: Autoformat.
---
.../src/queries/security/CWE-328/WeakSensitiveDataHashing.ql | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
index b7906d9af127..b22fe5762128 100755
--- a/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
+++ b/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
@@ -84,8 +84,7 @@ module ComputationallyExpensiveHashFunctionFlow {
*/
module WeakSensitiveDataHashingFlow =
DataFlow::MergePathGraph;
import WeakSensitiveDataHashingFlow::PathGraph