Skip to content

Commit f017821

Browse files
committed
Ruby: rb/weak-sensitive-data-hashing qhelp
1 parent d4203d9 commit f017821

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Using 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+
pre-image attacks: 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+
collision attacks: if you know a hash value <code>h(x)</code>,
21+
you should not be able to easily find a different input <code>y</code>
22+
with the same hash value <code>h(x) = h(y)</code>.
23+
</li>
24+
</ul>
25+
<p>
26+
In cases with a limited input space, such as for passwords, the hash
27+
function also needs to be computationally expensive to be resistant to
28+
brute-force attacks. Passwords should also have an unique salt applied
29+
before hashing, but that is not considered by this query.
30+
</p>
31+
32+
<p>
33+
As an example, both MD5 and SHA-1 are known to be vulnerable to collision attacks.
34+
</p>
35+
36+
<p>
37+
Since it's OK to use a weak cryptographic hash function in a non-security
38+
context, this query only alerts when these are used to hash sensitive
39+
data (such as passwords, certificates, usernames).
40+
</p>
41+
42+
<p>
43+
Use of broken or weak cryptographic algorithms that are not hashing algorithms, is
44+
handled by the <code>rb/weak-cryptographic-algorithm</code> query.
45+
</p>
46+
47+
</overview>
48+
<recommendation>
49+
50+
<p>
51+
Ensure that you use a strong, modern cryptographic hash function:
52+
</p>
53+
54+
<ul>
55+
<li>
56+
such as Argon2, scrypt, bcrypt, or PBKDF2 for passwords and other data with limited input space.
57+
</li>
58+
<li>
59+
such as SHA-2, or SHA-3 in other cases.
60+
</li>
61+
</ul>
62+
63+
</recommendation>
64+
<example>
65+
66+
<p>
67+
The following example shows two functions for checking whether the hash
68+
of a certificate matches a known value -- to prevent tampering.
69+
70+
The first function uses MD5 that is known to be vulnerable to collision attacks.
71+
72+
The second function uses SHA-256 that is a strong cryptographic hashing function.
73+
</p>
74+
75+
<sample src="examples/weak_certificate_hashing.rb" />
76+
77+
</example>
78+
<example>
79+
<p>
80+
The following example shows two functions for hashing passwords.
81+
82+
The first function uses SHA-256 to hash passwords. Although SHA-256 is a
83+
strong cryptographic hash function, it is not suitable for password
84+
hashing since it is not computationally expensive.
85+
</p>
86+
87+
<sample src="examples/weak_password_hashing_bad.rb" />
88+
89+
90+
<p>
91+
The second function uses Argon2 (through the <code>argon2</code>
92+
gem), which is a strong password hashing algorithm (and
93+
includes a per-password salt by default).
94+
</p>
95+
96+
<sample src="examples/weak_password_hashing_good.rb" />
97+
98+
</example>
99+
100+
<references>
101+
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html">Password Storage Cheat Sheet</a></li>
102+
</references>
103+
104+
</qhelp>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
require 'openssl'
2+
3+
def certificate_matches_known_hash_bad(certificate, known_hash)
4+
hash = OpenSSL::Digest.new('SHA1').digest certificate
5+
hash == known_hash
6+
end
7+
8+
def certificate_matches_known_hash_good(certificate, known_hash)
9+
hash = OpenSSL::Digest.new('SHA256').digest certificate
10+
hash == known_hash
11+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
require 'openssl'
2+
3+
def get_password_hash(password, salt)
4+
OpenSSL::Digest.new('SHA256').digest(password + salt) # BAD
5+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require 'argon2'
2+
3+
def get_initial_hash(password)
4+
Argon2::Password.create(password)
5+
end
6+
7+
def check_password(password, known_hash)
8+
Argon2::Password.verify_password(password, known_hash)
9+
end

0 commit comments

Comments
 (0)