Skip to content

Commit 9774c3c

Browse files
committed
Swift: Copy WeakPasswordHashing query from csharp.
1 parent be7d0ac commit 9774c3c

File tree

5 files changed

+241
-0
lines changed

5 files changed

+241
-0
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Provides a taint tracking configuration to find use of inappropriate
3+
* cryptographic hashing algorithms on passwords.
4+
*/
5+
6+
import csharp
7+
import semmle.code.csharp.security.SensitiveActions
8+
import semmle.code.csharp.dataflow.DataFlow
9+
import semmle.code.csharp.dataflow.TaintTracking
10+
11+
/**
12+
* A taint tracking configuration from password expressions to inappropriate
13+
* hashing sinks.
14+
*/
15+
module WeakHashingPasswordConfig implements DataFlow::ConfigSig {
16+
predicate isSource(DataFlow::Node node) { node.asExpr() instanceof PasswordExpr }
17+
18+
predicate isSink(DataFlow::Node node) { node instanceof WeakPasswordHashingSink }
19+
20+
predicate isBarrierIn(DataFlow::Node node) {
21+
// make sources barriers so that we only report the closest instance
22+
isSource(node)
23+
}
24+
25+
predicate isBarrierOut(DataFlow::Node node) {
26+
// make sinks barriers so that we only report the closest instance
27+
isSink(node)
28+
}
29+
}
30+
31+
module WeakHashingFlow = TaintTracking::Global<WeakHashingPasswordConfig>;
32+
33+
// TODO: rewrite with data extensions in mind, ref the Swift implementation
34+
class WeakPasswordHashingSink extends DataFlow::Node {
35+
string algorithm;
36+
37+
WeakPasswordHashingSink() {
38+
// a call to System.Security.Cryptography.MD5/SHA*.ComputeHash/ComputeHashAsync/HashData/HashDataAsync
39+
exists(MethodCall call, string name |
40+
(
41+
call.getTarget().getName() = name
42+
and name in ["ComputeHash", "ComputeHashAsync", "HashData", "HashDataAsync"]
43+
)
44+
// with this as the first argument - not arg 0, since arg 0 is 'this' for methods
45+
and call.getArgument(0) = this.asExpr()
46+
and
47+
// the call is to a method in the System.Security.Cryptography.MD* class
48+
// or the System.Security.Cryptography.SHA* classes
49+
(
50+
call.getQualifier().getType().getName() = algorithm
51+
and algorithm.matches(["MD%","SHA%"])
52+
)
53+
)
54+
}
55+
56+
string getAlgorithm() {
57+
result = algorithm
58+
}
59+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Using a insufficiently computationally hard hash function can leave data
8+
vulnerable, and should not be used for password hashing.
9+
</p>
10+
11+
<p>
12+
A strong cryptographic hash function should be resistant to:
13+
</p>
14+
<ul>
15+
<li>
16+
<strong>Pre-image attacks</strong>. 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+
<strong>Collision attacks</strong>. 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+
<strong>Brute force</strong>. If you know a hash value <code>h(x)</code>,
27+
you should not be able to find an input <code>y</code> that computes to that hash value
28+
using brute force attacks without significant computational effort.
29+
<li>
30+
</ul>
31+
32+
<p>
33+
All of MD5, SHA-1, SHA-2 and SHA-3 are weak against offline brute forcing, since they are not computationally hard.
34+
</p>
35+
36+
<p>
37+
Password hashing algorithms are designed to be slow and/or memory intenstive to compute, which makes brute force attacks more difficult.
38+
</p>
39+
40+
</overview>
41+
<recommendation>
42+
43+
<p>
44+
Ensure that for password storage you should use a computationally hard cryptographic hash function, such as:
45+
</p>
46+
47+
<ul>
48+
<li>
49+
Argon2
50+
</li>
51+
<li>
52+
scrypt
53+
</li>
54+
bcrypt
55+
<li>
56+
PBKDF2
57+
</li>
58+
</ul>
59+
60+
</recommendation>
61+
<example>
62+
63+
<p>
64+
The following examples show a function that hashes a password using a cryptographic hashing algorithm.
65+
66+
In the first case the SHA-512 hashing algorithm is used. It is vulnerable to offline brute force attacks:
67+
</p>
68+
<sample src="WeakPasswordHashingBad.csharp"/>
69+
<p>
70+
71+
Here is the same function using Argon2, which is suitable for password hashing:
72+
</p>
73+
<sample src="WeakPasswordHashingGood.csharp"/>
74+
75+
</example>
76+
<references>
77+
<li>
78+
OWASP:
79+
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html">Password Storage
80+
Cheat Sheet
81+
</a>
82+
</li>
83+
<li>
84+
nuget: <a href="https://www.nuget.org/packages/Konscious.Security.Cryptography.Argon2/">Konscious.Security.Cryptography.Argon2</a>
85+
</li>
86+
<li>
87+
nuget: <a href="https://www.nuget.org/packages/Isopoh.Cryptography.Argon2">Isopoh.Cryptography.Argon</a>
88+
</li>
89+
<li>
90+
libsodium: <a href="https://doc.libsodium.org/bindings_for_other_languages#bindings-programming-languages">libsodium bindings for other languages</a>
91+
</li>
92+
<li>
93+
nuget: <a href="https://www.nuget.org/packages/BCrypt.Net-Core/">BCrypt.Net-Core</a>
94+
</li>
95+
<li>
96+
nuget: <a href="https://www.nuget.org/packages/CryptSharpOfficial/">Scrypt and PBKDF2</a>
97+
</li>
98+
</references>
99+
100+
</qhelp>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @name Use of an inappropriate cryptographic hashing algorithm on passwords
3+
* @description Using inappropriate cryptographic hashing algorithms with passwords can compromise security.
4+
* @kind path-problem
5+
* @problem.severity warning
6+
* @security-severity 7.5
7+
* @precision high
8+
* @id csharp/weak-password-hashing
9+
* @tags security
10+
* external/cwe/cwe-327
11+
* external/cwe/cwe-328
12+
* external/cwe/cwe-916
13+
*/
14+
15+
import csharp
16+
import WeakPasswordHashingQuery
17+
import WeakHashingFlow::PathGraph
18+
19+
from
20+
WeakHashingFlow::PathNode source, WeakHashingFlow::PathNode sink, string algorithm,
21+
PasswordExpr expr
22+
where
23+
WeakHashingFlow::flowPath(source, sink) and
24+
algorithm = sink.getNode().(WeakPasswordHashingSink).getAlgorithm() and
25+
expr = source.getNode().asExpr()
26+
select sink.getNode(), source, sink,
27+
"Insecure hashing algorithm (" + algorithm + ") depends on $@.", source.getNode(),
28+
"password (" + expr + ")"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using var sha512 = System.Security.Cryptography.SHA512.Create();
2+
3+
var data = sha512.ComputeHash(Encoding.UTF8.GetBytes(content)); // BAD
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Security.Cryptography;
2+
using Konscious.Security.Cryptography; // use NuGet package Konscious.Security.Cryptography.Argon2
3+
4+
// See https://github.com/kmaragon/Konscious.Security.Cryptography#konscioussecuritycryptographyargon2
5+
6+
public class Argon2Hasher
7+
{
8+
public byte[] ComputeHash(byte[] password, byte[] salt)
9+
{
10+
// choose Argon2i, Argon2id or Argon2d as appropriate
11+
using var argon2 = new Argon2id(password);
12+
argon2.Salt = salt;
13+
14+
// read the Argon2 documentation to understand these parameters, and reference:
15+
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
16+
argon2.DegreeOfParallelism = 4; // number of threads you can spawn on your system - the higher, the better
17+
argon2.Iterations = 5; // set as high as your system can manage, within time constraints
18+
argon2.MemorySize = 1024 * 2048; // 2GB RAM, for example - set this as high as you can, within memory limits on your system
19+
20+
return argon2.GetBytes(32);
21+
}
22+
23+
// use a fixed-time comparison to avoid timing attacks
24+
public bool Equals(byte[] hash1, byte[] hash2) => CryptographicOperations.FixedTimeEquals(hash1, hash2);
25+
26+
// generate a salt securely with a cryptographic random number generator
27+
public static byte[] GenerateSalt()
28+
{
29+
var buffer = new byte[32];
30+
using var rng = new RNGCryptoServiceProvider();
31+
rng.GetBytes(buffer);
32+
return buffer;
33+
}
34+
}
35+
36+
var argon2 = new Argon2Hasher();
37+
38+
// Create the hash
39+
var bytes = Encoding.UTF8.GetBytes("this is a password"); // it should not be hardcoded in reality, but this is just a demo
40+
var salt = Argon2Hasher.GenerateSalt(); // salt is kept with hash; it is not secret, just unique per hash
41+
var hash = argon2.ComputeHash(bytes, salt);
42+
43+
// Check the hash - this will trivially always pass, but in reality you would have to retrieve the hash for the comparison
44+
if(argon2id.Equals(argon2.ComputeHash(bytes, salt), hash))
45+
{
46+
Console.WriteLine("PASS");
47+
}
48+
else
49+
{
50+
Console.WriteLine("FAIL");
51+
}

0 commit comments

Comments
 (0)