move new secret key sinks to existing CredentialsNode class,

add new additional global taint and dataflow steps
update tests of CWE-798
add a new sanitizer for `semmle.javascript.security.dataflow.HardcodedCredentialsQuery`
This commit is contained in:
amammad
2023-11-02 15:25:48 +01:00
parent 8e0f52cebc
commit e1d42fad2c
6 changed files with 549 additions and 274 deletions

View File

@@ -40,11 +40,163 @@ private module JsonWebToken {
}
/**
* The private key for a JWT as a `CredentialsNode`.
* The secret Or PublicKey for a JWT as a `CredentialsNode`.
*/
private class JwtKey extends CredentialsNode {
JwtKey() { this = DataFlow::moduleMember("jsonwebtoken", "sign").getACall().getArgument(1) }
JwtKey() {
this =
API::moduleImport("jsonwebtoken").getMember(["sign", "verify"]).getParameter(1).asSink()
}
override string getCredentialsKind() { result = "key" }
}
}
/**
* Provides classes and predicates modeling the `jose` library.
*/
private module Jose {
/**
* A taint-step for `succ = await jose.importSPKI(pred, 'RS256')`.
*/
private class ImportSpkiStep extends TaintTracking::SharedTaintStep, DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::Node n | n = API::moduleImport("jose").getMember("importSPKI") |
pred = n.getACall().getArgument(0) and
succ = n.getReturn().getPromised().asSource()
)
}
}
/**
* A taint-step for `succ = jose.base64url.encode(pred)` or `succ = jose.base64url.decode(pred)`.
*/
private class Base64urlStep extends TaintTracking::SharedTaintStep, DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::Node n |
n = API::moduleImport("jose").getMember("base64url").getMember(["decode", "encode"])
|
pred = n.getACall().getArgument(0) and
succ = n.getACall()
)
}
}
/**
* The asymmetric key or symmetric secret for a JWT as a `CredentialsNode`.
*/
private class JwtKey extends CredentialsNode {
JwtKey() { this = API::moduleImport("jose").getMember("jwtVerify").getParameter(1).asSink() }
override string getCredentialsKind() { result = "key" }
}
}
/**
* Provides classes and predicates modeling the `jwt-simple` library.
*/
private module JwtSimple {
/**
* The asymmetric key or symmetric secret for a JWT as a `CredentialsNode`.
*/
private class JwtKey extends CredentialsNode {
JwtKey() { this = API::moduleImport("jwt-simple").getMember("decode").getParameter(1).asSink() }
override string getCredentialsKind() { result = "key" }
}
}
/**
* Provides classes and predicates modeling the `koa-jwt` library.
*/
private module KoaJwt {
/**
* The shared secret for a JWT as a `CredentialsNode`.
*/
private class SharedSecret extends CredentialsNode {
SharedSecret() {
this = API::moduleImport("koa-jwt").getParameter(0).getMember("secret").asSink()
}
override string getCredentialsKind() { result = "key" }
}
}
/**
* Provides classes and predicates modeling the `express-jwt` library.
*/
private module ExpressJwt {
/**
* The shared secret for a JWT as a `CredentialsNode`.
*/
private class SharedSecret extends CredentialsNode {
SharedSecret() {
this =
API::moduleImport("express-jwt")
.getMember("expressjwt")
.getParameter(0)
.getMember("secret")
.asSink()
}
override string getCredentialsKind() { result = "key" }
}
}
/**
* Provides classes and predicates modeling the `passport-jwt` library.
*/
private module PassportJwt {
/**
* The secret (symmetric) or PEM-encoded public key (asymmetric) for a JWT as a `CredentialsNode`.
*/
private class JwtKey extends CredentialsNode {
JwtKey() {
this =
API::moduleImport("passport-jwt")
.getMember("Strategy")
.getParameter(0)
.getMember("secretOrKey")
.asSink()
or
this =
API::moduleImport("passport-jwt")
.getMember("Strategy")
.getParameter(0)
.getMember("secretOrKeyProvider")
.getParameter(2)
.getParameter(1)
.asSink()
}
override string getCredentialsKind() { result = "key" }
}
}
/**
* A taint-step for `succ = new TextEncoder().encode(pred)`.
*/
private class TextEncoderStep extends TaintTracking::SharedTaintStep, DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode n, DataFlow::NewNode nn |
n.getCalleeName() = "encode" and
nn.flowsTo(n.getReceiver()) and
nn.getCalleeName() = "TextEncoder"
|
pred = n.getArgument(0) and
succ = n
)
}
}
/**
* A taint-step for `succ = Buffer.from(pred, "base64")`.
*/
private class BufferFromStep extends TaintTracking::SharedTaintStep, DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode n | n = DataFlow::globalVarRef("Buffer").getAMemberCall("from") |
pred = n.getArgument(0) and
succ = [n, n.getAChainedMethodCall(["toString", "toJSON"])]
)
}
}

View File

@@ -255,4 +255,20 @@ module NextJS {
.getMember("router")
.asSource()
}
/**
* Provides classes and predicates modeling the `next-auth` library.
*/
private module NextAuth {
/**
* A random string used to hash tokens, sign cookies and generate cryptographic keys as a `CredentialsNode`.
*/
private class SecretKey extends CredentialsNode {
SecretKey() {
this = API::moduleImport("next-auth").getParameter(0).getMember("secret").asSink()
}
override string getCredentialsKind() { result = "key" }
}
}
}

View File

@@ -32,6 +32,16 @@ module HardcodedCredentials {
ConstantStringSource() { not astNode.getStringValue() = "" }
}
class NonProductionFiles extends Sanitizer {
NonProductionFiles() {
this.getFile()
.getLocation()
.hasLocationInfo(any(string s |
s.regexpMatch(["/.*test[.].*", "/.*demo[.].*", "/.*example[.].*", "/.*sample[.].*"])
), _, _, _, _)
}
}
/**
* A subclass of `Sink` that includes every `CredentialsNode`
* as a credentials sink.