mirror of
https://github.com/github/codeql.git
synced 2025-12-18 09:43:15 +01:00
Revamp the source and the sink of the query
This commit is contained in:
@@ -9,9 +9,26 @@
|
|||||||
|
|
||||||
import java
|
import java
|
||||||
import semmle.code.java.dataflow.TaintTracking
|
import semmle.code.java.dataflow.TaintTracking
|
||||||
import semmle.code.java.dataflow.TaintTracking2
|
|
||||||
import DataFlow::PathGraph
|
import DataFlow::PathGraph
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a regular expression for matching common names of variables
|
||||||
|
* that indicate the value being held is a password.
|
||||||
|
*/
|
||||||
|
string getPasswordRegex() { result = "(?i).*pass(wd|word|code|phrase).*" }
|
||||||
|
|
||||||
|
/** Finds variables that hold password information judging by their names. */
|
||||||
|
class PasswordVarExpr extends VarAccess {
|
||||||
|
PasswordVarExpr() {
|
||||||
|
exists(string name | name = this.getVariable().getName().toLowerCase() |
|
||||||
|
name.regexpMatch(getPasswordRegex()) and not name.matches("%hash%") // Exclude variable names such as `passwordHash` since their values were already hashed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if `Expr` e is a direct or indirect operand of `ae`. */
|
||||||
|
predicate hasAddExprAncestor(AddExpr ae, Expr e) { ae.getAnOperand+() = e }
|
||||||
|
|
||||||
/** The Java class `java.security.MessageDigest`. */
|
/** The Java class `java.security.MessageDigest`. */
|
||||||
class MessageDigest extends RefType {
|
class MessageDigest extends RefType {
|
||||||
MessageDigest() { this.hasQualifiedName("java.security", "MessageDigest") }
|
MessageDigest() { this.hasQualifiedName("java.security", "MessageDigest") }
|
||||||
@@ -54,41 +71,66 @@ class MDHashMethodAccess extends MethodAccess {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets a regular expression for matching common names of variables that indicate the value being held is a password. */
|
/**
|
||||||
string getPasswordRegex() { result = "(?i).*pass(wd|word|code|phrase).*" }
|
* Holds if `MethodAccess` ma is a method access of `MDHashMethodAccess` or
|
||||||
|
* invokes a method access of `MDHashMethodAccess` directly or indirectly.
|
||||||
/** Finds variables that hold password information judging by their names. */
|
*/
|
||||||
class PasswordVarExpr extends VarAccess {
|
predicate isHashAccess(MethodAccess ma) {
|
||||||
PasswordVarExpr() {
|
ma instanceof MDHashMethodAccess
|
||||||
exists(string name | name = this.getVariable().getName().toLowerCase() |
|
or
|
||||||
name.regexpMatch(getPasswordRegex()) and not name.matches("%hash%") // Exclude variable names such as `passwordHash` since their values were already hashed
|
exists(MethodAccess mca |
|
||||||
|
ma.getMethod().calls(mca.getMethod()) and
|
||||||
|
isHashAccess(mca) and
|
||||||
|
DataFlow::localExprFlow(ma.getMethod().getAParameter().getAnAccess(), mca.getAnArgument())
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Holds if `Expr` e is a direct or indirect operand of `ae`. */
|
/**
|
||||||
predicate hasAddExprAncestor(AddExpr ae, Expr e) { ae.getAnOperand+() = e }
|
* Holds if there is a second method access that satisfies `isHashAccess` whose qualifier or argument
|
||||||
|
* is the same as the method call `ma` that satisfies `isHashAccess`.
|
||||||
/** Holds if `MDHashMethodAccess ma` is a second `MDHashMethodAccess` call by the same object. */
|
*/
|
||||||
predicate hasAnotherHashCall(MDHashMethodAccess ma) {
|
predicate hasAnotherHashCall(MethodAccess ma) {
|
||||||
exists(MDHashMethodAccess ma2 |
|
isHashAccess(ma) and
|
||||||
|
exists(MethodAccess ma2, VarAccess va |
|
||||||
ma2 != ma and
|
ma2 != ma and
|
||||||
ma2.getQualifier() = ma.getQualifier().(VarAccess).getVariable().getAnAccess()
|
isHashAccess(ma2) and
|
||||||
|
not va.getVariable().getType() instanceof PrimitiveType and
|
||||||
|
(
|
||||||
|
ma.getQualifier() = va and
|
||||||
|
ma2.getQualifier() = va.getVariable().getAnAccess()
|
||||||
|
or
|
||||||
|
ma.getQualifier() = va and
|
||||||
|
ma2.getAnArgument() = va.getVariable().getAnAccess()
|
||||||
|
or
|
||||||
|
ma.getAnArgument() = va and
|
||||||
|
ma2.getQualifier() = va.getVariable().getAnAccess()
|
||||||
|
or
|
||||||
|
ma.getAnArgument() = va and
|
||||||
|
ma2.getAnArgument() = va.getVariable().getAnAccess()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `MethodAccess` ma is part of a call graph that satisfies `isHashAccess`
|
||||||
|
* but is not at the top of the call hierarchy.
|
||||||
|
*/
|
||||||
|
predicate hasHashAncestor(MethodAccess ma) {
|
||||||
|
exists(MethodAccess mpa |
|
||||||
|
mpa.getMethod().calls(ma.getMethod()) and
|
||||||
|
isHashAccess(mpa) and
|
||||||
|
DataFlow::localExprFlow(mpa.getMethod().getAParameter().getAnAccess(), ma.getAnArgument())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Holds if `MethodAccess` ma is a hashing call without a sibling node making another hashing call. */
|
/** Holds if `MethodAccess` ma is a hashing call without a sibling node making another hashing call. */
|
||||||
predicate isSingleHashMethodCall(MDHashMethodAccess ma) { not hasAnotherHashCall(ma) }
|
predicate isSingleHashMethodCall(MethodAccess ma) {
|
||||||
|
isHashAccess(ma) and not hasAnotherHashCall(ma)
|
||||||
/** Holds if `MethodAccess` ma is invoked by `MethodAccess` ma2 either directly or indirectly. */
|
|
||||||
predicate hasParentCall(MethodAccess ma2, MethodAccess ma) { ma.getCaller() = ma2.getMethod() }
|
|
||||||
|
|
||||||
/** Holds if `MethodAccess` is a single hashing call that is not invoked by a wrapper method. */
|
|
||||||
predicate isSink(MethodAccess ma) {
|
|
||||||
isSingleHashMethodCall(ma) and
|
|
||||||
not hasParentCall(_, ma) // Not invoked by a wrapper method which could invoke MDHashMethod in another call stack. This reduces FPs.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Holds if `MethodAccess` ma is a single hashing call that is not invoked by a wrapper method. */
|
||||||
|
predicate isSink(MethodAccess ma) { isSingleHashMethodCall(ma) and not hasHashAncestor(ma) }
|
||||||
|
|
||||||
/** Sink of hashing calls. */
|
/** Sink of hashing calls. */
|
||||||
class HashWithoutSaltSink extends DataFlow::ExprNode {
|
class HashWithoutSaltSink extends DataFlow::ExprNode {
|
||||||
HashWithoutSaltSink() {
|
HashWithoutSaltSink() {
|
||||||
@@ -99,7 +141,10 @@ class HashWithoutSaltSink extends DataFlow::ExprNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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. */
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
class HashWithoutSaltConfiguration extends TaintTracking::Configuration {
|
class HashWithoutSaltConfiguration extends TaintTracking::Configuration {
|
||||||
HashWithoutSaltConfiguration() { this = "HashWithoutSaltConfiguration" }
|
HashWithoutSaltConfiguration() { this = "HashWithoutSaltConfiguration" }
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
edges
|
edges
|
||||||
| HashWithoutSalt.java:10:36:10:43 | password : String | HashWithoutSalt.java:10:36:10:54 | getBytes(...) |
|
| HashWithoutSalt.java:10:36:10:43 | password : String | HashWithoutSalt.java:10:36:10:54 | getBytes(...) |
|
||||||
| HashWithoutSalt.java:17:13:17:20 | password : String | HashWithoutSalt.java:17:13:17:31 | getBytes(...) |
|
| HashWithoutSalt.java:25:13:25:20 | password : String | HashWithoutSalt.java:25:13:25:31 | getBytes(...) |
|
||||||
|
| HashWithoutSalt.java:93:22:93:29 | password : String | HashWithoutSalt.java:94:17:94:25 | passBytes |
|
||||||
|
| HashWithoutSalt.java:111:22:111:29 | password : String | HashWithoutSalt.java:112:18:112:26 | passBytes |
|
||||||
nodes
|
nodes
|
||||||
| HashWithoutSalt.java:10:36:10:43 | password : String | semmle.label | password : String |
|
| HashWithoutSalt.java:10:36:10:43 | password : String | semmle.label | password : String |
|
||||||
| HashWithoutSalt.java:10:36:10:54 | getBytes(...) | semmle.label | getBytes(...) |
|
| HashWithoutSalt.java:10:36:10:54 | getBytes(...) | semmle.label | getBytes(...) |
|
||||||
| HashWithoutSalt.java:17:13:17:20 | password : String | semmle.label | password : String |
|
| HashWithoutSalt.java:25:13:25:20 | password : String | semmle.label | password : String |
|
||||||
| HashWithoutSalt.java:17:13:17:31 | getBytes(...) | semmle.label | getBytes(...) |
|
| HashWithoutSalt.java:25:13:25:31 | getBytes(...) | semmle.label | getBytes(...) |
|
||||||
|
| HashWithoutSalt.java:93:22:93:29 | password : String | semmle.label | password : String |
|
||||||
|
| HashWithoutSalt.java:94:17:94:25 | passBytes | semmle.label | passBytes |
|
||||||
|
| HashWithoutSalt.java:111:22:111:29 | password : String | semmle.label | password : String |
|
||||||
|
| HashWithoutSalt.java:112:18:112:26 | passBytes | semmle.label | passBytes |
|
||||||
#select
|
#select
|
||||||
| HashWithoutSalt.java:10:36:10:54 | getBytes(...) | HashWithoutSalt.java:10:36:10:43 | password : String | HashWithoutSalt.java:10:36:10:54 | getBytes(...) | $@ is hashed without a salt. | HashWithoutSalt.java:10:36:10:43 | password : String | The password |
|
| HashWithoutSalt.java:10:36:10:54 | getBytes(...) | HashWithoutSalt.java:10:36:10:43 | password : String | HashWithoutSalt.java:10:36:10:54 | getBytes(...) | $@ is hashed without a salt. | HashWithoutSalt.java:10:36:10:43 | password : String | The password |
|
||||||
| HashWithoutSalt.java:17:13:17:31 | getBytes(...) | HashWithoutSalt.java:17:13:17:20 | password : String | HashWithoutSalt.java:17:13:17:31 | getBytes(...) | $@ is hashed without a salt. | HashWithoutSalt.java:17:13:17:20 | password : String | The password |
|
| HashWithoutSalt.java:25:13:25:31 | getBytes(...) | HashWithoutSalt.java:25:13:25:20 | password : String | HashWithoutSalt.java:25:13:25:31 | getBytes(...) | $@ is hashed without a salt. | HashWithoutSalt.java:25:13:25:20 | password : String | The password |
|
||||||
|
| HashWithoutSalt.java:94:17:94:25 | passBytes | HashWithoutSalt.java:93:22:93:29 | password : String | HashWithoutSalt.java:94:17:94:25 | passBytes | $@ is hashed without a salt. | HashWithoutSalt.java:93:22:93:29 | password : String | The password |
|
||||||
|
| HashWithoutSalt.java:112:18:112:26 | passBytes | HashWithoutSalt.java:111:22:111:29 | password : String | HashWithoutSalt.java:112:18:112:26 | passBytes | $@ is hashed without a salt. | HashWithoutSalt.java:111:22:111:29 | password : String | The password |
|
||||||
|
|||||||
@@ -11,14 +11,6 @@ public class HashWithoutSalt {
|
|||||||
return Base64.getEncoder().encodeToString(messageDigest);
|
return Base64.getEncoder().encodeToString(messageDigest);
|
||||||
}
|
}
|
||||||
|
|
||||||
// BAD - Hash without a salt.
|
|
||||||
public String getSHA256Hash2(String password) throws NoSuchAlgorithmException {
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
|
||||||
md.update(password.getBytes());
|
|
||||||
byte[] messageDigest = md.digest();
|
|
||||||
return Base64.getEncoder().encodeToString(messageDigest);
|
|
||||||
}
|
|
||||||
|
|
||||||
// GOOD - Hash with a salt.
|
// GOOD - Hash with a salt.
|
||||||
public String getSHA256Hash(String password, byte[] salt) throws NoSuchAlgorithmException {
|
public String getSHA256Hash(String password, byte[] salt) throws NoSuchAlgorithmException {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
@@ -27,6 +19,14 @@ public class HashWithoutSalt {
|
|||||||
return Base64.getEncoder().encodeToString(messageDigest);
|
return Base64.getEncoder().encodeToString(messageDigest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BAD - Hash without a salt.
|
||||||
|
public String getSHA256Hash2(String password) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
md.update(password.getBytes());
|
||||||
|
byte[] messageDigest = md.digest();
|
||||||
|
return Base64.getEncoder().encodeToString(messageDigest);
|
||||||
|
}
|
||||||
|
|
||||||
// GOOD - Hash with a salt.
|
// GOOD - Hash with a salt.
|
||||||
public String getSHA256Hash2(String password, byte[] salt) throws NoSuchAlgorithmException {
|
public String getSHA256Hash2(String password, byte[] salt) throws NoSuchAlgorithmException {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
@@ -77,8 +77,8 @@ public class HashWithoutSalt {
|
|||||||
sha256.update(foo, start, len);
|
sha256.update(foo, start, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// BAD - Invoking a wrapper implementation without a salt is not detected.
|
// GOOD - Invoking a wrapper implementation through qualifier with a salt.
|
||||||
public String getSHA256Hash4(String password) throws NoSuchAlgorithmException, ClassNotFoundException, IllegalAccessException, InstantiationException {
|
public String getWrapperSHA256Hash(String password) throws NoSuchAlgorithmException, ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||||
SHA256 sha256 = new SHA256();
|
SHA256 sha256 = new SHA256();
|
||||||
byte[] salt = getSalt();
|
byte[] salt = getSalt();
|
||||||
byte[] passBytes = password.getBytes();
|
byte[] passBytes = password.getBytes();
|
||||||
@@ -87,8 +87,16 @@ public class HashWithoutSalt {
|
|||||||
return Base64.getEncoder().encodeToString(sha256.digest());
|
return Base64.getEncoder().encodeToString(sha256.digest());
|
||||||
}
|
}
|
||||||
|
|
||||||
// BAD - Invoking a wrapper implementation without a salt is not detected.
|
// BAD - Invoking a wrapper implementation through qualifier without a salt.
|
||||||
public String getSHA256Hash5(String password) throws NoSuchAlgorithmException {
|
public String getWrapperSHA256Hash2(String password) throws NoSuchAlgorithmException, ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||||
|
SHA256 sha256 = new SHA256();
|
||||||
|
byte[] passBytes = password.getBytes();
|
||||||
|
sha256.update(passBytes, 0, passBytes.length);
|
||||||
|
return Base64.getEncoder().encodeToString(sha256.digest());
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD - Invoking a wrapper implementation through qualifier and argument with a salt.
|
||||||
|
public String getWrapperSHA256Hash3(String password) throws NoSuchAlgorithmException {
|
||||||
SHA256 sha256 = new SHA256();
|
SHA256 sha256 = new SHA256();
|
||||||
byte[] salt = getSalt();
|
byte[] salt = getSalt();
|
||||||
byte[] passBytes = password.getBytes();
|
byte[] passBytes = password.getBytes();
|
||||||
@@ -97,16 +105,26 @@ public class HashWithoutSalt {
|
|||||||
return Base64.getEncoder().encodeToString(sha256.digest());
|
return Base64.getEncoder().encodeToString(sha256.digest());
|
||||||
}
|
}
|
||||||
|
|
||||||
// BAD - Invoking a wrapper implementation without a salt is not detected.
|
// BAD - Invoking a wrapper implementation through argument without a salt.
|
||||||
public String getSHA512Hash6(String password) throws NoSuchAlgorithmException {
|
public String getWrapperSHA256Hash4(String password) throws NoSuchAlgorithmException {
|
||||||
SHA512 sha512 = new SHA512();
|
SHA256 sha256 = new SHA256();
|
||||||
byte[] passBytes = password.getBytes();
|
byte[] passBytes = password.getBytes();
|
||||||
sha512.update(passBytes, 0, passBytes.length);
|
update(sha256, passBytes, 0, passBytes.length);
|
||||||
return Base64.getEncoder().encodeToString(sha512.digest());
|
return Base64.getEncoder().encodeToString(sha256.digest());
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD - Invoking a wrapper implementation through argument with a salt.
|
||||||
|
public String getWrapperSHA256Hash5(String password) throws NoSuchAlgorithmException {
|
||||||
|
SHA256 sha256 = new SHA256();
|
||||||
|
byte[] salt = getSalt();
|
||||||
|
byte[] passBytes = password.getBytes();
|
||||||
|
update(sha256, passBytes, 0, passBytes.length);
|
||||||
|
update(sha256, salt, 0, salt.length);
|
||||||
|
return Base64.getEncoder().encodeToString(sha256.digest());
|
||||||
}
|
}
|
||||||
|
|
||||||
// BAD - Invoke a wrapper implementation with a salt, which is not detected with an interface type variable.
|
// BAD - Invoke a wrapper implementation with a salt, which is not detected with an interface type variable.
|
||||||
public String getSHA512Hash7(byte[] passphrase) throws NoSuchAlgorithmException, ClassNotFoundException, IllegalAccessException, InstantiationException {
|
public String getSHA512Hash8(byte[] passphrase) throws NoSuchAlgorithmException, ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||||
Class c = Class.forName("SHA512");
|
Class c = Class.forName("SHA512");
|
||||||
HASH sha512 = (HASH) (c.newInstance());
|
HASH sha512 = (HASH) (c.newInstance());
|
||||||
byte[] tmp = new byte[4];
|
byte[] tmp = new byte[4];
|
||||||
|
|||||||
Reference in New Issue
Block a user