Skip to content

Commit f1788ed

Browse files
committed
Revamp the query to handle more cases
1 parent 8ed2bc5 commit f1788ed

File tree

2 files changed

+95
-5
lines changed

2 files changed

+95
-5
lines changed

csharp/ql/src/experimental/Security Features/CWE-759/HashWithoutSalt.ql

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
*/
99

1010
import csharp
11+
import semmle.code.csharp.dataflow.DataFlow2
1112
import semmle.code.csharp.dataflow.TaintTracking
13+
import semmle.code.csharp.dataflow.TaintTracking2
1214
import DataFlow::PathGraph
1315

1416
/** The C# class `Windows.Security.Cryptography.Core.HashAlgorithmProvider`. */
@@ -23,6 +25,13 @@ class HashAlgorithm extends RefType {
2325
HashAlgorithm() { this.hasQualifiedName("System.Security.Cryptography", "HashAlgorithm") }
2426
}
2527

28+
/** The C# class `System.Security.Cryptography.KeyedHashAlgorithm`. */
29+
class KeyedHashAlgorithm extends RefType {
30+
KeyedHashAlgorithm() {
31+
this.hasQualifiedName("System.Security.Cryptography", "KeyedHashAlgorithm")
32+
}
33+
}
34+
2635
/**
2736
* The method `ComputeHash()` declared in `System.Security.Cryptography.HashAlgorithm` and
2837
* the method `HashData()` declared in `Windows.Security.Cryptography.Core.HashAlgorithmProvider`.
@@ -47,6 +56,20 @@ class PasswordVarExpr extends Expr {
4756
}
4857
}
4958

59+
/** Holds if there is another hashing method call. */
60+
predicate hasAnotherHashCall(MethodCall mc) {
61+
exists(Call mc2, DataFlow2::Node src, DataFlow2::Node sink |
62+
(
63+
mc2.getTarget() instanceof HashMethod or
64+
mc2.(ObjectCreation).getObjectType().getABaseType+() instanceof HashAlgorithm
65+
) and
66+
mc2 != mc and
67+
src.asExpr() = mc.getAReachableElement() and
68+
sink.asExpr() = mc2.getAChildExpr() and
69+
TaintTracking2::localTaint(src, sink)
70+
)
71+
}
72+
5073
/** Taint configuration tracking flow from an expression whose name suggests it holds password data to a method call that generates a hash without a salt. */
5174
class HashWithoutSaltConfiguration extends TaintTracking::Configuration {
5275
HashWithoutSaltConfiguration() { this = "HashWithoutSaltConfiguration" }
@@ -56,7 +79,8 @@ class HashWithoutSaltConfiguration extends TaintTracking::Configuration {
5679
override predicate isSink(DataFlow::Node sink) {
5780
exists(MethodCall mc |
5881
sink.asExpr() = mc.getArgument(0) and
59-
mc.getTarget() instanceof HashMethod
82+
mc.getTarget() instanceof HashMethod and
83+
not hasAnotherHashCall(mc)
6084
)
6185
}
6286

@@ -71,7 +95,7 @@ class HashWithoutSaltConfiguration extends TaintTracking::Configuration {
7195
}
7296

7397
/**
74-
* Holds if a password is concatenated with a salt then hashed together through the call `System.Array.CopyTo()`, for example,
98+
* Holds if a password is concatenated with a salt then hashed together through calls such as `System.Array.CopyTo()`, for example,
7599
* `byte[] rawSalted = new byte[passBytes.Length + salt.Length];`
76100
* `passBytes.CopyTo(rawSalted, 0);`
77101
* `salt.CopyTo(rawSalted, passBytes.Length);`
@@ -81,11 +105,27 @@ class HashWithoutSaltConfiguration extends TaintTracking::Configuration {
81105
override predicate isSanitizer(DataFlow::Node node) {
82106
exists(MethodCall mc |
83107
mc.getTarget().fromLibrary() and
84-
mc.getTarget().hasQualifiedName("System.Array", "CopyTo") and
85-
mc.getArgument(0) = node.asExpr()
86-
) // passBytes.CopyTo(rawSalted, 0)
108+
(
109+
mc.getTarget().hasQualifiedName("System.Array", "CopyTo") or // passBytes.CopyTo(rawSalted, 0)
110+
mc.getTarget().hasQualifiedName("System.String", "Concat") or // string.Concat(password, saltkey)
111+
mc.getTarget().hasQualifiedName("System.Buffer", "BlockCopy") or // Buffer.BlockCopy(salt, 0, allBytes, 0, 20)
112+
mc.getTarget().hasQualifiedName("System.String", "Format") // String.Format("{0}:{1}:{2}", username, salt, password)
113+
) and
114+
mc.getAnArgument() = node.asExpr()
115+
)
87116
or
88117
exists(AddExpr e | node.asExpr() = e.getAnOperand()) // password+salt
118+
or
119+
exists(InterpolatedStringExpr e | node.asExpr() = e.getAnInsert())
120+
or
121+
// a salt or key is included in subclasses of `KeyedHashAlgorithm`
122+
exists(MethodCall mc, Assignment ass, ObjectCreation oc |
123+
ass.getRValue() = oc and
124+
oc.getObjectType().getABaseType+() instanceof KeyedHashAlgorithm and
125+
mc.getTarget() instanceof HashMethod and
126+
ass.getLValue() = mc.getQualifier().(VariableAccess).getTarget().getAnAccess() and
127+
mc.getArgument(0) = node.asExpr()
128+
)
89129
}
90130
}
91131

csharp/ql/test/experimental/Security Features/CWE-759/HashWithoutSalt.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,21 @@ public static string HashPassword2(string password)
6262
return Convert.ToBase64String(dbPassword);
6363
}
6464

65+
// GOOD - Hash with a salt.
66+
public bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
67+
{
68+
using(var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
69+
{
70+
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
71+
for(int i = 0;i<computedHash.Length;i++)
72+
{
73+
if (computedHash[i] != passwordHash[i])
74+
return false;
75+
}
76+
return true;
77+
}
78+
}
79+
6580
public static byte[] GenerateSalt()
6681
{
6782
using (var rng = new RNGCryptoServiceProvider())
@@ -71,4 +86,39 @@ public static byte[] GenerateSalt()
7186
return randomNumber;
7287
}
7388
}
89+
90+
public static byte[] Combine(byte[] first, byte[] second)
91+
{
92+
// helper to combine two byte arrays
93+
byte[] ret = new byte[first.Length + second.Length];
94+
Buffer.BlockCopy(first, 0, ret, 0, first.Length);
95+
Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
96+
return ret;
97+
}
98+
99+
// GOOD - Hash with a salt.
100+
public static byte[] CalculateKeys(string password, string userid)
101+
{
102+
var utf16pass = System.Text.Encoding.UTF8.GetBytes(password);
103+
var utf16sid = System.Text.Encoding.UTF8.GetBytes(userid);
104+
105+
var utf16sidfinal = new byte[utf16sid.Length + 2];
106+
utf16sid.CopyTo(utf16sidfinal, 0);
107+
utf16sidfinal[utf16sidfinal.Length - 2] = 0x00;
108+
109+
byte[] sha1bytes_password;
110+
byte[] hmacbytes;
111+
112+
//Calculate SHA1 from user password
113+
using (var sha1 = new SHA1Managed())
114+
{
115+
sha1bytes_password = sha1.ComputeHash(utf16pass);
116+
}
117+
var combined = Combine(sha1bytes_password, utf16sidfinal);
118+
using (var hmac = new HMACSHA1(sha1bytes_password))
119+
{
120+
hmacbytes = hmac.ComputeHash(utf16sidfinal);
121+
}
122+
return hmacbytes;
123+
}
74124
}

0 commit comments

Comments
 (0)