Skip to content

Commit 6402aa5

Browse files
authored
Merge pull request github#18471 from geoffw0/weakhash
Rust: Weak hashing query
2 parents b2bb143 + e61d6ae commit 6402aa5

File tree

14 files changed

+705
-4
lines changed

14 files changed

+705
-4
lines changed

rust/ql/lib/codeql/rust/Frameworks.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
*/
44

55
private import codeql.rust.frameworks.Reqwest
6-
private import codeql.rust.frameworks.RustCrypto
6+
private import codeql.rust.frameworks.rustcrypto.RustCrypto
77
private import codeql.rust.frameworks.stdlib.Env
88
private import codeql.rust.frameworks.Sqlx
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/rust-all
4+
extensible: sinkModel
5+
data:
6+
- ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::new_with_prefix", "Argument[0]", "hasher-input", "manual"]
7+
- ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::update", "Argument[0]", "hasher-input", "manual"]
8+
- ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::chain_update", "Argument[0]", "hasher-input", "manual"]
9+
- ["repo:https://github.com/RustCrypto/traits:digest", "<_ as crate::digest::Digest>::digest", "Argument[0]", "hasher-input", "manual"]
10+
- ["repo:https://github.com/stainless-steel/md5:md5", "crate::compute", "Argument[0]", "hasher-input", "manual"]

rust/ql/lib/codeql/rust/security/SensitiveData.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import rust
9-
private import internal.SensitiveDataHeuristics
9+
import internal.SensitiveDataHeuristics
1010
private import codeql.rust.dataflow.DataFlow
1111

1212
/**
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for detecting "use of a
3+
* broken or weak cryptographic hashing algorithm on sensitive data"
4+
* vulnerabilities, as well as extension points for adding your own. This is
5+
* divided into two general cases:
6+
* - hashing sensitive data
7+
* - hashing passwords (which requires the hashing algorithm to be
8+
* sufficiently computationally expensive in addition to other requirements)
9+
*/
10+
11+
import rust
12+
private import codeql.rust.Concepts
13+
private import codeql.rust.security.SensitiveData
14+
private import codeql.rust.dataflow.DataFlow
15+
private import codeql.rust.dataflow.FlowSource
16+
private import codeql.rust.dataflow.FlowSink
17+
private import codeql.rust.dataflow.internal.DataFlowImpl
18+
19+
/**
20+
* Provides default sources, sinks and sanitizers for detecting "use of a broken or weak
21+
* cryptographic hashing algorithm on sensitive data" vulnerabilities on sensitive data that does
22+
* NOT require computationally expensive hashing, as well as extension points for adding your own.
23+
*
24+
* Also see the `ComputationallyExpensiveHashFunction` module.
25+
*/
26+
module NormalHashFunction {
27+
/**
28+
* A data flow source for "use of a broken or weak cryptographic hashing algorithm on sensitive
29+
* data" vulnerabilities that does not require computationally expensive hashing. That is, a
30+
* piece of sensitive data that is not a password.
31+
*/
32+
abstract class Source extends DataFlow::Node {
33+
Source() { not this instanceof ComputationallyExpensiveHashFunction::Source }
34+
35+
/**
36+
* Gets the classification of the sensitive data.
37+
*/
38+
abstract string getClassification();
39+
}
40+
41+
/**
42+
* A data flow sink for "use of a broken or weak cryptographic hashing algorithm on sensitive
43+
* data" vulnerabilities that applies to data that does not require computationally expensive
44+
* hashing. That is, a broken or weak hashing algorithm.
45+
*/
46+
abstract class Sink extends DataFlow::Node {
47+
/**
48+
* Gets the name of the weak hashing algorithm.
49+
*/
50+
abstract string getAlgorithmName();
51+
}
52+
53+
/**
54+
* A barrier for "use of a broken or weak cryptographic hashing algorithm on sensitive data"
55+
* vulnerabilities that applies to data that does not require computationally expensive hashing.
56+
*/
57+
abstract class Barrier extends DataFlow::Node { }
58+
59+
/**
60+
* A flow source modeled by the `SensitiveData` library.
61+
*/
62+
class SensitiveDataAsSource extends Source instanceof SensitiveData {
63+
SensitiveDataAsSource() {
64+
not SensitiveData.super.getClassification() = SensitiveDataClassification::password() and // (covered in ComputationallyExpensiveHashFunction)
65+
not SensitiveData.super.getClassification() = SensitiveDataClassification::id() // (not accurate enough)
66+
}
67+
68+
override SensitiveDataClassification getClassification() {
69+
result = SensitiveData.super.getClassification()
70+
}
71+
}
72+
73+
/**
74+
* A flow sink modeled by the `Cryptography` module.
75+
*/
76+
class WeakHashingOperationInputAsSink extends Sink {
77+
Cryptography::HashingAlgorithm algorithm;
78+
79+
WeakHashingOperationInputAsSink() {
80+
exists(Cryptography::CryptographicOperation operation |
81+
algorithm.isWeak() and
82+
algorithm = operation.getAlgorithm() and
83+
this = operation.getAnInput()
84+
)
85+
}
86+
87+
override string getAlgorithmName() { result = algorithm.getName() }
88+
}
89+
}
90+
91+
/**
92+
* Provides default sources, sinks and sanitizers for detecting "use of a broken or weak
93+
* cryptographic hashing algorithm on sensitive data" vulnerabilities on sensitive data that DOES
94+
* require computationally expensive hashing, as well as extension points for adding your own.
95+
*
96+
* Also see the `NormalHashFunction` module.
97+
*/
98+
module ComputationallyExpensiveHashFunction {
99+
/**
100+
* A data flow source for "use of a broken or weak cryptographic hashing algorithm on sensitive
101+
* data" vulnerabilities that does require computationally expensive hashing. That is, a
102+
* password.
103+
*/
104+
abstract class Source extends DataFlow::Node {
105+
/**
106+
* Gets the classification of the sensitive data.
107+
*/
108+
abstract string getClassification();
109+
}
110+
111+
/**
112+
* A data flow sink for "use of a broken or weak cryptographic hashing algorithm on sensitive
113+
* data" vulnerabilities that applies to data that does require computationally expensive
114+
* hashing. That is, a broken or weak hashing algorithm or one that is not computationally
115+
* expensive enough for password hashing.
116+
*/
117+
abstract class Sink extends DataFlow::Node {
118+
/**
119+
* Gets the name of the weak hashing algorithm.
120+
*/
121+
abstract string getAlgorithmName();
122+
123+
/**
124+
* Holds if this sink is for a computationally expensive hash function (meaning that hash
125+
* function is just weak in some other regard.
126+
*/
127+
abstract predicate isComputationallyExpensive();
128+
}
129+
130+
/**
131+
* A barrier for "use of a broken or weak cryptographic hashing algorithm on sensitive data"
132+
* vulnerabilities that applies to data that does require computationally expensive hashing.
133+
*/
134+
abstract class Barrier extends DataFlow::Node { }
135+
136+
/**
137+
* A flow source modeled by the `SensitiveData` library.
138+
*/
139+
class PasswordAsSource extends Source instanceof SensitiveData {
140+
PasswordAsSource() {
141+
SensitiveData.super.getClassification() = SensitiveDataClassification::password()
142+
}
143+
144+
override SensitiveDataClassification getClassification() {
145+
result = SensitiveData.super.getClassification()
146+
}
147+
}
148+
149+
/**
150+
* A flow sink modeled by the `Cryptography` module.
151+
*/
152+
class WeakPasswordHashingOperationInputSink extends Sink {
153+
Cryptography::CryptographicAlgorithm algorithm;
154+
155+
WeakPasswordHashingOperationInputSink() {
156+
exists(Cryptography::CryptographicOperation operation |
157+
(
158+
algorithm instanceof Cryptography::PasswordHashingAlgorithm and
159+
algorithm.isWeak()
160+
or
161+
algorithm instanceof Cryptography::HashingAlgorithm // Note that HashingAlgorithm and PasswordHashingAlgorithm are disjoint
162+
) and
163+
algorithm = operation.getAlgorithm() and
164+
this = operation.getAnInput()
165+
)
166+
}
167+
168+
override string getAlgorithmName() { result = algorithm.getName() }
169+
170+
override predicate isComputationallyExpensive() {
171+
algorithm instanceof Cryptography::PasswordHashingAlgorithm
172+
}
173+
}
174+
}
175+
176+
/**
177+
* An externally modeled operation that hashes data, for example a call to `md5::Md5::digest(data)`.
178+
*/
179+
class ModeledHashOperation extends Cryptography::CryptographicOperation::Range {
180+
DataFlow::Node input;
181+
string algorithmName;
182+
183+
ModeledHashOperation() {
184+
exists(CallExpr call |
185+
sinkNode(input, "hasher-input") and
186+
call = input.(Node::FlowSummaryNode).getSinkElement().getCall() and
187+
call = this.asExpr().getExpr() and
188+
algorithmName =
189+
call.getFunction().(PathExpr).getPath().getQualifier().getPart().getNameRef().getText()
190+
)
191+
}
192+
193+
override DataFlow::Node getInitialization() { result = this }
194+
195+
override Cryptography::HashingAlgorithm getAlgorithm() { result.matchesName(algorithmName) }
196+
197+
override DataFlow::Node getAnInput() { result = input }
198+
199+
override Cryptography::BlockMode getBlockMode() { none() } // (does not apply for hashing)
200+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
A broken or weak cryptographic hash function can leave data
8+
vulnerable, and should not be used in security-related code.
9+
</p>
10+
11+
<p>
12+
A strong cryptographic hash function should be resistant to:
13+
</p>
14+
<ul>
15+
<li>
16+
<b>Pre-image attacks</b>. If you know a hash value <code>h(x)</code>,
17+
you should not be able to easily find the input <code>x</code>.
18+
</li>
19+
<li>
20+
<b>Collision attacks</b>. If you know a hash value <code>h(x)</code>,
21+
you should not be able to easily find a different input
22+
<code>y</code>
23+
with the same hash value <code>h(x) = h(y)</code>.
24+
</li>
25+
<li>
26+
<b>Brute force</b>. For passwords and other data with limited
27+
input space, if you know a hash value <code>h(x)</code>,
28+
you should not be able to find the input <code>x</code> even using
29+
a brute force attack (without significant computational effort).
30+
</li>
31+
</ul>
32+
33+
<p>
34+
As an example, both MD5 and SHA-1 are known to be vulnerable to collision attacks.
35+
</p>
36+
37+
<p>
38+
All of MD5, SHA-1, SHA-2 and SHA-3 are weak against offline brute forcing, so
39+
they are not suitable for hashing passwords. This includes SHA-224, SHA-256,
40+
41+
SHA-384, and SHA-512, which are in the SHA-2 family.
42+
</p>
43+
44+
<p>
45+
Since it's OK to use a weak cryptographic hash function in a non-security
46+
context, this query only alerts when these are used to hash sensitive
47+
data (such as passwords, certificates, usernames).
48+
</p>
49+
50+
</overview>
51+
<recommendation>
52+
53+
<p>
54+
Ensure that you use a strong, modern cryptographic hash function, such as:
55+
</p>
56+
57+
<ul>
58+
<li>
59+
Argon2, scrypt, bcrypt, or PBKDF2 for passwords and other data with limited input space where
60+
a dictionary-like attack is feasible.
61+
</li>
62+
<li>
63+
SHA-2, or SHA-3 in other cases.
64+
</li>
65+
</ul>
66+
67+
<p>
68+
Note that special purpose algorithms, which are used to ensure that a message comes from a
69+
particular sender, exist for message authentication. These algorithms should be used when
70+
appropriate, as they address common vulnerabilities of simple hashing schemes in this context.
71+
</p>
72+
73+
</recommendation>
74+
<example>
75+
76+
<p>
77+
The following examples show hashing sensitive data using the MD5 hashing algorithm that is known to be
78+
vulnerable to collision attacks, and hashing passwords using the SHA-3 algorithm that is weak to brute
79+
force attacks:
80+
</p>
81+
<sample src="WeakSensitiveDataHashingBad.rs"/>
82+
83+
<p>
84+
To make these secure, we can use the SHA-3 algorithm for sensitive data and Argon2 for passwords:
85+
</p>
86+
<sample src="WeakSensitiveDataHashingGood.rs"/>
87+
88+
</example>
89+
<references>
90+
<li>
91+
OWASP:
92+
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html">
93+
Password Storage Cheat Sheet
94+
</a>
95+
and
96+
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html">
97+
Transport Layer Security Cheat Sheet
98+
</a>.
99+
</li>
100+
<li>
101+
GitHub:
102+
<a href="https://github.com/RustCrypto/hashes?tab=readme-ov-file#rustcrypto-hashes">
103+
RustCrypto: Hashes
104+
</a>
105+
and
106+
<a href="https://github.com/RustCrypto/password-hashes?tab=readme-ov-file#rustcrypto-password-hashes">
107+
RustCrypto: Password Hashes
108+
</a>.
109+
</li>
110+
<li>
111+
The RustCrypto Book:
112+
<a href="https://rustcrypto.org/key-derivation/hashing-password.html">
113+
Password Hashing
114+
</a>.
115+
</li>
116+
</references>
117+
118+
</qhelp>

0 commit comments

Comments
 (0)