Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions ql/src/experimental/CWE-321/HardcodedKeys.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
A JSON Web Token (JWT) is used for authenticating and managing users in an application.
</p>
<p>
Using a hard-coded secret key for signing JWT tokens in open source projects
can leave the application using the token vulnerable to authentication bypasses.
</p>

<p>
A JWT token is safe for enforing authentication and access control only till the day it can't be forged by a malicious actor. However, when a project exposes this secret publicly, these seemingly unforgeable tokens can now be easily forged.
Comment thread
This conversation was marked as resolved.
Comment thread
This conversation was marked as resolved.
Since the authentication as well as access control is typically enforced through these JWT tokens, an attacker armed with the secret can create a valid authentication token for any user and may even gain access to other privileged parts of the application.
</p>

</overview>
<recommendation>

<p>
Generating a crytograhically secure secret key during application initialization and using this generated key for future JWT signing requests can prevent this vulnerability
Comment thread
This conversation was marked as resolved.
</p>

</recommendation>
<example>

<p>
The following code uses a hard-coded string a a secret for signing the tokens. In this case, an attacker can very easily forge a token by using the hard-coded secret.
Comment thread
This conversation was marked as resolved.
</p>

<sample src="HardcodedKeysBad.go" />

</example>
<example>

<p>
In the following case, the application uses a programatically generated string as a secret for signing the tokens. In this case, since the secret can't be predicted, the code is secure. A fucntion like `GenerateCryptoString` can be run to generate a secure secret key at the time of application installation/initialization. This generated key can then be used for all future signing requests.
Comment thread
This conversation was marked as resolved.
</p>

<sample src="HardcodedKeysGood.go" />

</example>
<references>
<li>
CVE-2022-0664:
<a href="https://nvd.nist.gov/vuln/detail/CVE-2022-0664">Use of Hard-coded Cryptographic Key in Go github.com/gravitl/netmaker prior to 0.8.5,0.9.4,0.10.0,0.10.1. </a>
</li>
</references>

</qhelp>
18 changes: 18 additions & 0 deletions ql/src/experimental/CWE-321/HardcodedKeys.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @name Use of a hardcoded key for signing JWT
* @description Using a fixed hardcoded key for signing JWT's can allow an attacker to compromise security.
* @kind path-problem
* @problem.severity error
* @id go/hardcoded-key
* @tags security
* external/cwe/cwe-321
*/

import go
import HardcodedKeysLib
import DataFlow::PathGraph

from HardcodedKeys::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ is used to sign a JWT token.", source.getNode(),
"Hardcoded String"
9 changes: 9 additions & 0 deletions ql/src/experimental/CWE-321/HardcodedKeysBad.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mySigningKey := []byte("AllYourBase")

claims := &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Unix(1516239022, 0)),
Issuer: "test",
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
23 changes: 23 additions & 0 deletions ql/src/experimental/CWE-321/HardcodedKeysGood.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
func GenerateCryptoString(n int) (string, error) {
const chars = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
ret := make([]byte, n)
for i := range ret {
num, err := crand.Int(crand.Reader, big.NewInt(int64(len(chars))))
if err != nil {
return "", err
}
ret[i] = chars[num.Int64()]
}
return string(ret), nil
}

mySigningKey := GenerateCryptoString(64)


claims := &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Unix(1516239022, 0)),
Issuer: "test",
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
190 changes: 190 additions & 0 deletions ql/src/experimental/CWE-321/HardcodedKeysLib.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about
* JWT token signing vulnerabilities as well as extension points
* for adding your own.
*/

import go
import DataFlow::PathGraph

/**
* Provides default sources, sinks and sanitizers for reasoning about
* JWT token signing vulnerabilities as well as extension points
* for adding your own.
*/
module HardcodedKeys {
/**
* A data flow source for JWT token signing vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }

/**
* A data flow sink for JWT token signing vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }

/**
* A sanitizer for JWT token signing vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }

/**
* A sanitizer guard for JWT token signing vulnerabilities.
*/
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }

private predicate isTestCode(Expr e) {
e.getFile().getAbsolutePath().toLowerCase().matches("%test%") and
not e.getFile().getAbsolutePath().toLowerCase().matches("%ql/test%")
}

/**
* A hardcoded string literal as a source for JWT token signing vulnerabilities.
*/
class HardcodedStringSource extends Source {
HardcodedStringSource() {
this.asExpr() instanceof StringLit and
not isTestCode(this.asExpr())
}
}

/**
* An expression used to sign JWT tokens as a sink for JWT token signing vulnerabilities.
*/
private class GolangJwtSign extends Sink {
string pkg;

GolangJwtSign() {
pkg =
Comment thread
This conversation was marked as resolved.
[
"github.com/golang-jwt/jwt/v4", "github.com/dgrijalva/jwt-go",
"github.com/form3tech-oss/jwt-go", "github.com/ory/fosite/token/jwt"
] and
(
exists(DataFlow::MethodCallNode m |
// Models the `SignedString` method
// `func (t *Token) SignedString(key interface{}) (string, error)`
m.getTarget().hasQualifiedName(pkg, "Token", "SignedString")
|
this = m.getArgument(0)
)
or
exists(DataFlow::MethodCallNode m |
// Model the `Sign` method of the `SigningMethod` interface
// type SigningMethod interface {
// Verify(signingString, signature string, key interface{}) error
// Sign(signingString string, key interface{}) (string, error)
// Alg() string
// }
m.getTarget().hasQualifiedName(pkg, "SigningMethod", "Sign")
|
this = m.getArgument(1)
)
)
}
}

private class GinJwtSign extends Sink {
GinJwtSign() {
exists(Field f |
// https://pkg.go.dev/github.com/appleboy/gin-jwt/v2#GinJWTMiddleware
f.hasQualifiedName("github.com/appleboy/gin-jwt/v2", "GinJWTMiddleware", "Key") and
f.getAWrite().getRhs() = this
)
}
}

private class SquareJoseKey extends Sink {
SquareJoseKey() {
exists(Field f, string pkg |
// type Recipient struct {
// Algorithm KeyAlgorithm
// Key interface{}
// KeyID string
// PBES2Count int
// PBES2Salt []byte
// }
// type SigningKey struct {
// Algorithm SignatureAlgorithm
// Key interface{}
// }
f.hasQualifiedName(pkg, ["Recipient", "SigningKey"], "Key") and
f.getAWrite().getRhs() = this
|
pkg = ["github.com/square/go-jose/v3", "gopkg.in/square/go-jose.v2"]
)
}
}

private class CrystalHqJwtSigner extends Sink {
CrystalHqJwtSigner() {
exists(DataFlow::CallNode m |
// `func NewSignerHS(alg Algorithm, key []byte) (Signer, error)`
m.getTarget().hasQualifiedName("github.com/cristalhq/jwt/v3", "NewSignerHS")
|
this = m.getArgument(1)
)
}
}

private class GoKitJwt extends Sink {
GoKitJwt() {
exists(DataFlow::CallNode m |
// `func NewSigner(kid string, key []byte, method jwt.SigningMethod, claims jwt.Claims) endpoint.Middleware`
m.getTarget().hasQualifiedName("github.com/go-kit/kit/auth/jwt", "NewSigner")
|
this = m.getArgument(1)
)
}
}

private class LestrratJwk extends Sink {
LestrratJwk() {
exists(DataFlow::CallNode m, string pkg |
pkg.matches([
"github.com/lestrrat-go/jwx", "github.com/lestrrat/go-jwx/jwk",
"github.com/lestrrat-go/jwx%/jwk"
]) and
// `func New(key interface{}) (Key, error)`
m.getTarget().hasQualifiedName(pkg, "New")
|
this = m.getArgument(0)
)
}
}

/**
* Mark any comparision expression where any operand is tainted as a
* sanitizer for all instances of the taint
*/
private class CompareExprSanitizer extends Sanitizer {
CompareExprSanitizer() {
exists(BinaryExpr c |
c.getAnOperand().getGlobalValueNumber() = this.asExpr().getGlobalValueNumber()
)
}
}

/**
* A configuration depicting taint flow for studying JWT token signing vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "Hard-coded JWT Signing Key" }

override predicate isSource(DataFlow::Node source) { source instanceof Source }

override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }

override predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ) {
exists(ConversionExpr ce | ce.getOperand() = prev.asExpr() and ce = succ.asExpr() |
ce.getTypeExpr().getType() instanceof ByteSliceType
)
}

override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof Sanitizer }

override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
guard instanceof SanitizerGuard
}
}
}
98 changes: 98 additions & 0 deletions ql/test/experimental/CWE-321/HardcodedKeys.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
edges
| HardcodedKeysBad.go:11:18:11:38 | type conversion : slice type | HardcodedKeysBad.go:19:28:19:39 | mySigningKey |
| HardcodedKeysBad.go:11:18:11:38 | type conversion : string | HardcodedKeysBad.go:19:28:19:39 | mySigningKey |
| HardcodedKeysBad.go:11:25:11:37 | "AllYourBase" : string | HardcodedKeysBad.go:11:18:11:38 | type conversion : slice type |
| HardcodedKeysBad.go:11:25:11:37 | "AllYourBase" : string | HardcodedKeysBad.go:11:18:11:38 | type conversion : string |
| main.go:25:18:25:31 | type conversion : slice type | main.go:34:28:34:39 | mySigningKey |
| main.go:25:18:25:31 | type conversion : string | main.go:34:28:34:39 | mySigningKey |
| main.go:25:25:25:30 | "key1" : string | main.go:25:18:25:31 | type conversion : slice type |
| main.go:25:25:25:30 | "key1" : string | main.go:25:18:25:31 | type conversion : string |
| main.go:42:23:42:28 | "key2" : string | main.go:42:16:42:29 | type conversion |
| main.go:60:9:60:22 | type conversion : slice type | main.go:61:44:61:46 | key |
| main.go:60:9:60:22 | type conversion : string | main.go:61:44:61:46 | key |
| main.go:60:16:60:21 | `key3` : string | main.go:60:9:60:22 | type conversion : slice type |
| main.go:60:16:60:21 | `key3` : string | main.go:60:9:60:22 | type conversion : string |
| main.go:65:9:65:22 | type conversion : slice type | main.go:66:66:66:68 | key |
| main.go:65:9:65:22 | type conversion : string | main.go:66:66:66:68 | key |
| main.go:65:16:65:21 | "key4" : string | main.go:65:9:65:22 | type conversion : slice type |
| main.go:65:16:65:21 | "key4" : string | main.go:65:9:65:22 | type conversion : string |
| main.go:69:10:69:23 | type conversion : slice type | main.go:74:15:74:18 | key2 |
| main.go:69:10:69:23 | type conversion : string | main.go:74:15:74:18 | key2 |
| main.go:69:17:69:22 | "key5" : string | main.go:69:10:69:23 | type conversion : slice type |
| main.go:69:17:69:22 | "key5" : string | main.go:69:10:69:23 | type conversion : string |
| main.go:80:9:80:22 | type conversion : slice type | main.go:84:41:84:43 | key |
| main.go:80:9:80:22 | type conversion : string | main.go:84:41:84:43 | key |
| main.go:80:16:80:21 | "key6" : string | main.go:80:9:80:22 | type conversion : slice type |
| main.go:80:16:80:21 | "key6" : string | main.go:80:9:80:22 | type conversion : string |
| main.go:89:10:89:23 | type conversion : slice type | main.go:91:66:91:69 | key2 |
| main.go:89:10:89:23 | type conversion : string | main.go:91:66:91:69 | key2 |
| main.go:89:17:89:22 | "key7" : string | main.go:89:10:89:23 | type conversion : slice type |
| main.go:89:17:89:22 | "key7" : string | main.go:89:10:89:23 | type conversion : string |
| main.go:97:9:97:22 | type conversion : slice type | main.go:103:30:103:32 | key |
| main.go:97:9:97:22 | type conversion : string | main.go:103:30:103:32 | key |
| main.go:97:16:97:21 | "key8" : string | main.go:97:9:97:22 | type conversion : slice type |
| main.go:97:16:97:21 | "key8" : string | main.go:97:9:97:22 | type conversion : string |
| main.go:107:15:107:28 | type conversion : slice type | main.go:108:16:108:24 | sharedKey |
| main.go:107:15:107:28 | type conversion : string | main.go:108:16:108:24 | sharedKey |
| main.go:107:22:107:27 | "key9" : string | main.go:107:15:107:28 | type conversion : slice type |
| main.go:107:22:107:27 | "key9" : string | main.go:107:15:107:28 | type conversion : string |
| main.go:111:23:111:37 | type conversion : slice type | main.go:114:16:114:30 | sharedKeyglobal |
| main.go:111:23:111:37 | type conversion : string | main.go:114:16:114:30 | sharedKeyglobal |
| main.go:111:30:111:36 | "key10" : string | main.go:111:23:111:37 | type conversion : slice type |
| main.go:111:30:111:36 | "key10" : string | main.go:111:23:111:37 | type conversion : string |
nodes
| HardcodedKeysBad.go:11:18:11:38 | type conversion : slice type | semmle.label | type conversion : slice type |
| HardcodedKeysBad.go:11:18:11:38 | type conversion : string | semmle.label | type conversion : string |
| HardcodedKeysBad.go:11:25:11:37 | "AllYourBase" : string | semmle.label | "AllYourBase" : string |
| HardcodedKeysBad.go:19:28:19:39 | mySigningKey | semmle.label | mySigningKey |
| main.go:25:18:25:31 | type conversion : slice type | semmle.label | type conversion : slice type |
| main.go:25:18:25:31 | type conversion : string | semmle.label | type conversion : string |
| main.go:25:25:25:30 | "key1" : string | semmle.label | "key1" : string |
| main.go:34:28:34:39 | mySigningKey | semmle.label | mySigningKey |
| main.go:42:16:42:29 | type conversion | semmle.label | type conversion |
| main.go:42:23:42:28 | "key2" : string | semmle.label | "key2" : string |
| main.go:60:9:60:22 | type conversion : slice type | semmle.label | type conversion : slice type |
| main.go:60:9:60:22 | type conversion : string | semmle.label | type conversion : string |
| main.go:60:16:60:21 | `key3` : string | semmle.label | `key3` : string |
| main.go:61:44:61:46 | key | semmle.label | key |
| main.go:65:9:65:22 | type conversion : slice type | semmle.label | type conversion : slice type |
| main.go:65:9:65:22 | type conversion : string | semmle.label | type conversion : string |
| main.go:65:16:65:21 | "key4" : string | semmle.label | "key4" : string |
| main.go:66:66:66:68 | key | semmle.label | key |
| main.go:69:10:69:23 | type conversion : slice type | semmle.label | type conversion : slice type |
| main.go:69:10:69:23 | type conversion : string | semmle.label | type conversion : string |
| main.go:69:17:69:22 | "key5" : string | semmle.label | "key5" : string |
| main.go:74:15:74:18 | key2 | semmle.label | key2 |
| main.go:80:9:80:22 | type conversion : slice type | semmle.label | type conversion : slice type |
| main.go:80:9:80:22 | type conversion : string | semmle.label | type conversion : string |
| main.go:80:16:80:21 | "key6" : string | semmle.label | "key6" : string |
| main.go:84:41:84:43 | key | semmle.label | key |
| main.go:89:10:89:23 | type conversion : slice type | semmle.label | type conversion : slice type |
| main.go:89:10:89:23 | type conversion : string | semmle.label | type conversion : string |
| main.go:89:17:89:22 | "key7" : string | semmle.label | "key7" : string |
| main.go:91:66:91:69 | key2 | semmle.label | key2 |
| main.go:97:9:97:22 | type conversion : slice type | semmle.label | type conversion : slice type |
| main.go:97:9:97:22 | type conversion : string | semmle.label | type conversion : string |
| main.go:97:16:97:21 | "key8" : string | semmle.label | "key8" : string |
| main.go:103:30:103:32 | key | semmle.label | key |
| main.go:107:15:107:28 | type conversion : slice type | semmle.label | type conversion : slice type |
| main.go:107:15:107:28 | type conversion : string | semmle.label | type conversion : string |
| main.go:107:22:107:27 | "key9" : string | semmle.label | "key9" : string |
| main.go:108:16:108:24 | sharedKey | semmle.label | sharedKey |
| main.go:111:23:111:37 | type conversion : slice type | semmle.label | type conversion : slice type |
| main.go:111:23:111:37 | type conversion : string | semmle.label | type conversion : string |
| main.go:111:30:111:36 | "key10" : string | semmle.label | "key10" : string |
| main.go:114:16:114:30 | sharedKeyglobal | semmle.label | sharedKeyglobal |
subpaths
#select
| HardcodedKeysBad.go:19:28:19:39 | mySigningKey | HardcodedKeysBad.go:11:25:11:37 | "AllYourBase" : string | HardcodedKeysBad.go:19:28:19:39 | mySigningKey | $@ is used to sign a JWT token. | HardcodedKeysBad.go:11:25:11:37 | "AllYourBase" | Hardcoded String |
| main.go:34:28:34:39 | mySigningKey | main.go:25:25:25:30 | "key1" : string | main.go:34:28:34:39 | mySigningKey | $@ is used to sign a JWT token. | main.go:25:25:25:30 | "key1" | Hardcoded String |
| main.go:42:16:42:29 | type conversion | main.go:42:23:42:28 | "key2" : string | main.go:42:16:42:29 | type conversion | $@ is used to sign a JWT token. | main.go:42:23:42:28 | "key2" | Hardcoded String |
| main.go:61:44:61:46 | key | main.go:60:16:60:21 | `key3` : string | main.go:61:44:61:46 | key | $@ is used to sign a JWT token. | main.go:60:16:60:21 | `key3` | Hardcoded String |
| main.go:66:66:66:68 | key | main.go:65:16:65:21 | "key4" : string | main.go:66:66:66:68 | key | $@ is used to sign a JWT token. | main.go:65:16:65:21 | "key4" | Hardcoded String |
| main.go:74:15:74:18 | key2 | main.go:69:17:69:22 | "key5" : string | main.go:74:15:74:18 | key2 | $@ is used to sign a JWT token. | main.go:69:17:69:22 | "key5" | Hardcoded String |
| main.go:84:41:84:43 | key | main.go:80:16:80:21 | "key6" : string | main.go:84:41:84:43 | key | $@ is used to sign a JWT token. | main.go:80:16:80:21 | "key6" | Hardcoded String |
| main.go:91:66:91:69 | key2 | main.go:89:17:89:22 | "key7" : string | main.go:91:66:91:69 | key2 | $@ is used to sign a JWT token. | main.go:89:17:89:22 | "key7" | Hardcoded String |
| main.go:103:30:103:32 | key | main.go:97:16:97:21 | "key8" : string | main.go:103:30:103:32 | key | $@ is used to sign a JWT token. | main.go:97:16:97:21 | "key8" | Hardcoded String |
| main.go:108:16:108:24 | sharedKey | main.go:107:22:107:27 | "key9" : string | main.go:108:16:108:24 | sharedKey | $@ is used to sign a JWT token. | main.go:107:22:107:27 | "key9" | Hardcoded String |
| main.go:114:16:114:30 | sharedKeyglobal | main.go:111:30:111:36 | "key10" : string | main.go:114:16:114:30 | sharedKeyglobal | $@ is used to sign a JWT token. | main.go:111:30:111:36 | "key10" | Hardcoded String |
1 change: 1 addition & 0 deletions ql/test/experimental/CWE-321/HardcodedKeys.qlref
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
experimental/CWE-321/HardcodedKeys.ql
Loading