Skip to content

Commit 96543b8

Browse files
authored
Merge pull request #14075 from amammad/amammad-go-JWT
Go: Improved JWT query, JWT decoding without verification
2 parents 0291558 + 8a3aa2c commit 96543b8

File tree

23 files changed

+1641
-0
lines changed

23 files changed

+1641
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/go-jose/go-jose/v3/jwt"
8+
)
9+
10+
var JwtKey = []byte("AllYourBase")
11+
12+
func main() {
13+
// BAD: usage of a harcoded Key
14+
verifyJWT(JWTFromUser)
15+
}
16+
17+
func LoadJwtKey(token *jwt.Token) (interface{}, error) {
18+
return JwtKey, nil
19+
}
20+
func verifyJWT(signedToken string) {
21+
fmt.Println("verifying JWT")
22+
DecodedToken, err := jwt.ParseWithClaims(signedToken, &CustomerInfo{}, LoadJwtKey)
23+
if claims, ok := DecodedToken.Claims.(*CustomerInfo); ok && DecodedToken.Valid {
24+
fmt.Printf("NAME:%v ,ID:%v\n", claims.Name, claims.ID)
25+
} else {
26+
log.Fatal(err)
27+
}
28+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
A JSON Web Token (JWT) is used for authenticating and managing users in an application.
6+
</p>
7+
<p>
8+
Using a hard-coded secret key for parsing JWT tokens in open source projects
9+
can leave the application using the token vulnerable to authentication bypasses.
10+
</p>
11+
12+
<p>
13+
A JWT token is safe for enforcing authentication and access control as long as 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.
14+
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.
15+
</p>
16+
17+
</overview>
18+
<recommendation>
19+
20+
<p>
21+
Generating a cryptographically secure secret key during application initialization and using this generated key for future JWT parsing requests can prevent this vulnerability.
22+
</p>
23+
24+
</recommendation>
25+
<example>
26+
27+
<p>
28+
The following code uses a hard-coded string as a secret for parsing user provided JWTs. In this case, an attacker can very easily forge a token by using the hard-coded secret.
29+
</p>
30+
31+
<sample src="ExampleBad.go" />
32+
33+
</example>
34+
<references>
35+
<li>
36+
CVE-2022-0664:
37+
<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>
38+
</li>
39+
</references>
40+
41+
</qhelp>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @name Decoding JWT with hardcoded key
3+
* @description Decoding JWT Secret with a Constant value lead to authentication or authorization bypass
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @id go/parse-jwt-with-hardcoded-key
7+
* @tags security
8+
* experimental
9+
* external/cwe/cwe-321
10+
*/
11+
12+
import go
13+
import experimental.frameworks.JWT
14+
15+
module JwtParseWithConstantKeyConfig implements DataFlow::ConfigSig {
16+
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof StringLit }
17+
18+
predicate isSink(DataFlow::Node sink) {
19+
// first part is the JWT Parsing Functions that get a func type as an argument
20+
// Find a node that has flow to a key Function argument
21+
// then find the first result node of this Function which is the secret key
22+
exists(FuncDef fd, DataFlow::Node n, DataFlow::ResultNode rn |
23+
fd = n.asExpr()
24+
or
25+
n = fd.(FuncDecl).getFunction().getARead()
26+
|
27+
GolangJwtKeyFunc::flow(n, _) and
28+
sink = rn and
29+
rn.getRoot() = fd and
30+
rn.getIndex() = 0
31+
)
32+
or
33+
// second part is the JWT Parsing Functions that get a string or byte as an argument
34+
sink = any(JwtParse jp).getKeyArg()
35+
}
36+
}
37+
38+
module GolangJwtKeyFuncConfig implements DataFlow::ConfigSig {
39+
predicate isSource(DataFlow::Node source) {
40+
source = any(Function f).getARead()
41+
or
42+
source.asExpr() = any(FuncDef fd)
43+
}
44+
45+
predicate isSink(DataFlow::Node sink) {
46+
sink = any(JwtParseWithKeyFunction parseJwt).getKeyFuncArg()
47+
}
48+
}
49+
50+
module JwtParseWithConstantKey = TaintTracking::Global<JwtParseWithConstantKeyConfig>;
51+
52+
module GolangJwtKeyFunc = TaintTracking::Global<GolangJwtKeyFuncConfig>;
53+
54+
import JwtParseWithConstantKey::PathGraph
55+
56+
from JwtParseWithConstantKey::PathNode source, JwtParseWithConstantKey::PathNode sink
57+
where JwtParseWithConstantKey::flowPath(source, sink)
58+
select sink.getNode(), source, sink, "This $@.", source.getNode(),
59+
"Constant Key is used as JWT Secret key"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/golang-jwt/jwt/v5"
8+
)
9+
10+
func main() {
11+
// BAD: only decode jwt without verification
12+
notVerifyJWT(token)
13+
14+
// GOOD: decode with verification or verify plus decode
15+
notVerifyJWT(token)
16+
VerifyJWT(token)
17+
}
18+
19+
func notVerifyJWT(signedToken string) {
20+
fmt.Println("only decoding JWT")
21+
DecodedToken, _, err := jwt.NewParser().ParseUnverified(signedToken, &CustomerInfo{})
22+
if claims, ok := DecodedToken.Claims.(*CustomerInfo); ok {
23+
fmt.Printf("DecodedToken:%v\n", claims)
24+
} else {
25+
log.Fatal("error", err)
26+
}
27+
}
28+
func LoadJwtKey(token *jwt.Token) (interface{}, error) {
29+
return ARandomJwtKey, nil
30+
}
31+
func verifyJWT(signedToken string) {
32+
fmt.Println("verifying JWT")
33+
DecodedToken, err := jwt.ParseWithClaims(signedToken, &CustomerInfo{}, LoadJwtKey)
34+
if claims, ok := DecodedToken.Claims.(*CustomerInfo); ok && DecodedToken.Valid {
35+
fmt.Printf("NAME:%v ,ID:%v\n", claims.Name, claims.ID)
36+
} else {
37+
log.Fatal(err)
38+
}
39+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
A JSON Web Token (JWT) is used for authenticating and managing users in an application.
6+
</p>
7+
<p>
8+
Only Decoding JWTs without checking if they have a valid signature or not can lead to security vulnerabilities.
9+
</p>
10+
11+
</overview>
12+
<recommendation>
13+
14+
<p>
15+
Don't use methods that only decode JWT, Instead use methods that verify the signature of JWT.
16+
</p>
17+
18+
</recommendation>
19+
<example>
20+
21+
<p>
22+
In the following code you can see an Example from a popular Library.
23+
</p>
24+
25+
<sample src="Example.go" />
26+
27+
</example>
28+
<references>
29+
<li>
30+
<a href="https://github.com/argoproj/argo-cd/security/advisories/GHSA-q9hr-j4rf-8fjc">JWT audience claim is not verified</a>
31+
</li>
32+
</references>
33+
34+
</qhelp>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @name Use of JWT Methods that only decode user provided Token
3+
* @description Using JWT methods without verification can cause to authorization or authentication bypass
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @id go/parse-jwt-without-verification
7+
* @tags security
8+
* experimental
9+
* external/cwe/cwe-321
10+
*/
11+
12+
import go
13+
import experimental.frameworks.JWT
14+
15+
module WithValidationConfig implements DataFlow::ConfigSig {
16+
predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }
17+
18+
predicate isSink(DataFlow::Node sink) {
19+
sink = any(JwtParse jwtParse).getTokenArg() or
20+
sink = any(JwtParseWithKeyFunction jwtParseWithKeyFunction).getTokenArg()
21+
}
22+
23+
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
24+
golangJwtIsAdditionalFlowStep(nodeFrom, nodeTo)
25+
or
26+
goJoseIsAdditionalFlowStep(nodeFrom, nodeTo)
27+
}
28+
}
29+
30+
module NoValidationConfig implements DataFlow::ConfigSig {
31+
predicate isSource(DataFlow::Node source) {
32+
source instanceof UntrustedFlowSource and
33+
not WithValidation::flow(source, _)
34+
}
35+
36+
predicate isSink(DataFlow::Node sink) {
37+
sink = any(JwtUnverifiedParse parseUnverified).getTokenArg()
38+
}
39+
40+
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
41+
golangJwtIsAdditionalFlowStep(nodeFrom, nodeTo)
42+
or
43+
goJoseIsAdditionalFlowStep(nodeFrom, nodeTo)
44+
}
45+
}
46+
47+
module WithValidation = TaintTracking::Global<WithValidationConfig>;
48+
49+
module NoValidation = TaintTracking::Global<NoValidationConfig>;
50+
51+
import NoValidation::PathGraph
52+
53+
from NoValidation::PathNode source, NoValidation::PathNode sink
54+
where NoValidation::flowPath(source, sink)
55+
select sink.getNode(), source, sink,
56+
"This JWT is parsed without verification and received from $@.", source.getNode(),
57+
"this user-controlled source"

0 commit comments

Comments
 (0)