Skip to content

Commit c78f390

Browse files
committed
add go generate support, upgrade JWT.qll
1 parent da864bf commit c78f390

File tree

36 files changed

+434
-2389
lines changed

36 files changed

+434
-2389
lines changed

go/ql/lib/semmle/go/security/JWT.qll

Lines changed: 150 additions & 138 deletions
Large diffs are not rendered by default.
Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/**
22
* @name Decoding JWT with hardcoded key
3-
* @description Decoding JWT Secrect with a Constant value lead to authentication or authorization bypass
3+
* @description Decoding JWT Secret with a Constant value lead to authentication or authorization bypass
44
* @kind path-problem
55
* @problem.severity error
6-
* @id go/hardcoded-key
6+
* @id go/parse-jwt-with-hardcoded-key
77
* @tags security
88
* experimental
99
* external/cwe/cwe-321
@@ -12,10 +12,13 @@
1212
import go
1313
import semmle.go.security.JWT
1414

15-
module JwtConfig implements DataFlow::ConfigSig {
15+
module JwtPaseWithConstantKeyConfig implements DataFlow::ConfigSig {
1616
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof StringLit }
1717

1818
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
1922
exists(FuncDef fd, DataFlow::Node n, DataFlow::ResultNode rn |
2023
GolangJwtKeyFunc::flow(n, _) and fd = n.asExpr()
2124
|
@@ -31,6 +34,9 @@ module JwtConfig implements DataFlow::ConfigSig {
3134
rn.getRoot() = fd.getFuncDecl() and
3235
rn.getIndex() = 0
3336
)
37+
or
38+
// second part is the JWT Parsing Functions that get a string or byte as an argument
39+
sink = any(JwtParse jp).getKeyArg()
3440
}
3541
}
3642

@@ -42,24 +48,17 @@ module GolangJwtKeyFuncConfig implements DataFlow::ConfigSig {
4248
}
4349

4450
predicate isSink(DataFlow::Node sink) {
45-
sink =
46-
[
47-
any(GolangJwtParse parseWithClaims).getKeyFuncArg(),
48-
any(GolangJwtParseWithClaims parseWithClaims).getKeyFuncArg(),
49-
any(GolangJwtParseFromRequest parseWithClaims).getKeyFuncArg(),
50-
any(GolangJwtParseFromRequestWithClaims parseWithClaims).getKeyFuncArg(),
51-
any(GoJoseClaims parseWithClaims).getKeyFuncArg(),
52-
]
51+
sink = any(JwtParseWithKeyFunction parseJWT).getKeyFuncArg()
5352
}
5453
}
5554

56-
module Jwt = TaintTracking::Global<JwtConfig>;
55+
module JwtPaseWithConstantKey = TaintTracking::Global<JwtPaseWithConstantKeyConfig>;
5756

5857
module GolangJwtKeyFunc = TaintTracking::Global<GolangJwtKeyFuncConfig>;
5958

60-
import Jwt::PathGraph
59+
import JwtPaseWithConstantKey::PathGraph
6160

62-
from Jwt::PathNode source, Jwt::PathNode sink
63-
where Jwt::flowPath(source, sink)
61+
from JwtPaseWithConstantKey::PathNode source, JwtPaseWithConstantKey::PathNode sink
62+
where JwtPaseWithConstantKey::flowPath(source, sink)
6463
select sink.getNode(), source, sink, "This $@.", source.getNode(),
6564
"Constant Key is used as JWT Secret key"

go/ql/src/experimental/CWE-347/NoVerification.ql renamed to go/ql/src/experimental/CWE-347/ParseJWTWithoutVerification.ql

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @description Using JWT methods without verification can cause to authorization or authentication bypass
44
* @kind path-problem
55
* @problem.severity error
6-
* @id go/hardcoded-key
6+
* @id go/parse-jwt-without-verification
77
* @tags security
88
* experimental
99
* external/cwe/cwe-321
@@ -16,8 +16,8 @@ module WithValidationConfig implements DataFlow::ConfigSig {
1616
predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }
1717

1818
predicate isSink(DataFlow::Node sink) {
19-
sink = any(GolangJwtValidField parse) or
20-
sink = any(GoJoseClaims parse).getACall().getReceiver()
19+
sink = any(JwtParse parseUnverified).getTokenArg() or
20+
sink = any(JwtParseWithKeyFunction parseUnverified).getTokenArg()
2121
}
2222

2323
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
@@ -34,9 +34,7 @@ module NoValidationConfig implements DataFlow::ConfigSig {
3434
}
3535

3636
predicate isSink(DataFlow::Node sink) {
37-
sink = any(GolangJwtParseUnverified parseunverified).getACall().getArgument(0)
38-
or
39-
sink = any(GoJoseUnsafeClaims parse).getACall().getReceiver()
37+
sink = any(JwtUnverifiedParse parseUnverified).getTokenNode()
4038
}
4139

4240
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
edges
2-
| golang-jwt-v5/golang-jwt-v5.go:19:14:19:34 | type conversion | golang-jwt-v5/golang-jwt-v5.go:37:9:37:14 | JwtKey |
3-
| golang-jwt-v5/golang-jwt-v5.go:19:21:19:33 | "AllYourBase" | golang-jwt-v5/golang-jwt-v5.go:19:14:19:34 | type conversion |
2+
| go-jose.v3.go:11:14:11:34 | type conversion | go-jose.v3.go:23:32:23:37 | JwtKey |
3+
| go-jose.v3.go:11:21:11:33 | "AllYourBase" | go-jose.v3.go:11:14:11:34 | type conversion |
4+
| golang-jwt-v5.go:19:15:19:35 | type conversion | golang-jwt-v5.go:27:9:27:15 | JwtKey1 |
5+
| golang-jwt-v5.go:19:22:19:34 | "AllYourBase" | golang-jwt-v5.go:19:15:19:35 | type conversion |
46
nodes
5-
| golang-jwt-v5/golang-jwt-v5.go:19:14:19:34 | type conversion | semmle.label | type conversion |
6-
| golang-jwt-v5/golang-jwt-v5.go:19:21:19:33 | "AllYourBase" | semmle.label | "AllYourBase" |
7-
| golang-jwt-v5/golang-jwt-v5.go:37:9:37:14 | JwtKey | semmle.label | JwtKey |
7+
| go-jose.v3.go:11:14:11:34 | type conversion | semmle.label | type conversion |
8+
| go-jose.v3.go:11:21:11:33 | "AllYourBase" | semmle.label | "AllYourBase" |
9+
| go-jose.v3.go:23:32:23:37 | JwtKey | semmle.label | JwtKey |
10+
| golang-jwt-v5.go:19:15:19:35 | type conversion | semmle.label | type conversion |
11+
| golang-jwt-v5.go:19:22:19:34 | "AllYourBase" | semmle.label | "AllYourBase" |
12+
| golang-jwt-v5.go:27:9:27:15 | JwtKey1 | semmle.label | JwtKey1 |
813
subpaths
914
#select
10-
| golang-jwt-v5/golang-jwt-v5.go:37:9:37:14 | JwtKey | golang-jwt-v5/golang-jwt-v5.go:19:21:19:33 | "AllYourBase" | golang-jwt-v5/golang-jwt-v5.go:37:9:37:14 | JwtKey | This $@. | golang-jwt-v5/golang-jwt-v5.go:19:21:19:33 | "AllYourBase" | Constant Key is used as JWT Secret key |
15+
| go-jose.v3.go:23:32:23:37 | JwtKey | go-jose.v3.go:11:21:11:33 | "AllYourBase" | go-jose.v3.go:23:32:23:37 | JwtKey | This $@. | go-jose.v3.go:11:21:11:33 | "AllYourBase" | Constant Key is used as JWT Secret key |
16+
| golang-jwt-v5.go:27:9:27:15 | JwtKey1 | golang-jwt-v5.go:19:22:19:34 | "AllYourBase" | golang-jwt-v5.go:27:9:27:15 | JwtKey1 | This $@. | golang-jwt-v5.go:19:22:19:34 | "AllYourBase" | Constant Key is used as JWT Secret key |
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package jwt
2+
3+
//go:generate depstubber -vendor github.com/go-jose/go-jose/v3/jwt JSONWebToken ParseSigned
4+
5+
import (
6+
"fmt"
7+
"github.com/go-jose/go-jose/v3/jwt"
8+
"net/http"
9+
)
10+
11+
var JwtKey = []byte("AllYourBase")
12+
13+
func main2(r *http.Request) {
14+
// NOT OK
15+
signedToken := r.URL.Query().Get("signedToken")
16+
verifyJWT(signedToken)
17+
}
18+
19+
func verifyJWT(signedToken string) {
20+
fmt.Println("verifying JWT")
21+
DecodedToken, _ := jwt.ParseSigned(signedToken)
22+
out := CustomerInfo{}
23+
if err := DecodedToken.Claims(JwtKey, &out); err != nil {
24+
panic(err)
25+
}
26+
fmt.Printf("%v\n", out)
27+
}

go/ql/test/experimental/CWE-321-V2/go-jose.v3/go-jose.v3.go

Lines changed: 0 additions & 39 deletions
This file was deleted.

go/ql/test/experimental/CWE-321-V2/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module main
22

3-
go 1.18
3+
go 1.21
44

55
require (
66
github.com/gin-gonic/gin v1.9.1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
package main
1+
package jwt
2+
3+
//go:generate depstubber -vendor github.com/golang-jwt/jwt/v5 RegisteredClaims,Parser,Token Parse,ParseWithClaims
24

35
import (
46
"fmt"
5-
"github.com/gin-gonic/gin"
67
"github.com/golang-jwt/jwt/v5"
78
"log"
89
"net/http"
9-
"os"
1010
)
1111

1212
type CustomerInfo struct {
@@ -16,27 +16,18 @@ type CustomerInfo struct {
1616
}
1717

1818
// BAD constant key
19-
var JwtKey = []byte("AllYourBase")
20-
21-
func main() {
22-
router := gin.Default()
23-
router.GET("/ping", func(c *gin.Context) {
24-
// https://pkg.go.dev/github.com/go-jose/go-jose/v3/jwt
25-
var unsignedToken = c.Param("customerName")
26-
signedToken := c.Param("signedToken")
27-
VerifyJWT(signedToken)
19+
var JwtKey1 = []byte("AllYourBase")
2820

29-
c.JSON(http.StatusOK, gin.H{
30-
"message": "pong",
31-
})
32-
})
33-
_ = router.Run()
21+
func main1(r *http.Request) {
22+
signedToken := r.URL.Query().Get("signedToken")
23+
verifyJWT_golangjwt(signedToken)
3424
}
3525

3626
func LoadJwtKey(token *jwt.Token) (interface{}, error) {
37-
return JwtKey, nil
27+
return JwtKey1, nil
3828
}
39-
func verifyJWT(signedToken string) {
29+
30+
func verifyJWT_golangjwt(signedToken string) {
4031
fmt.Println("verifying JWT")
4132
DecodedToken, err := jwt.ParseWithClaims(signedToken, &CustomerInfo{}, LoadJwtKey)
4233
if claims, ok := DecodedToken.Claims.(*CustomerInfo); ok && DecodedToken.Valid {
@@ -45,4 +36,3 @@ func verifyJWT(signedToken string) {
4536
log.Fatal(err)
4637
}
4738
}
48-

0 commit comments

Comments
 (0)