Skip to content

Commit 68392e7

Browse files
committed
V1
1 parent 7723dbc commit 68392e7

File tree

2,704 files changed

+1257578
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

2,704 files changed

+1257578
-0
lines changed

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

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import go
2+
3+
/**
4+
* func (p *Parser) Parse(tokenString string, keyFunc Keyfunc)
5+
* func Parse(tokenString string, keyFunc Keyfunc)
6+
*/
7+
class GolangJwtParse extends Function {
8+
GolangJwtParse() {
9+
exists(DataFlow::Function f |
10+
f.hasQualifiedName([
11+
"github.com/golang-jwt/jwt", "github.com/golang-jwt/jwt/v4",
12+
"github.com/golang-jwt/jwt/v5", "github.com/dgrijalva/jwt-go",
13+
"github.com/dgrijalva/jwt-go/v4",
14+
], "Parse")
15+
|
16+
this = f
17+
)
18+
or
19+
exists(DataFlow::Method f |
20+
f.hasQualifiedName([
21+
"github.com/golang-jwt/jwt.Parser", "github.com/golang-jwt/jwt/v4.Parser",
22+
"github.com/golang-jwt/jwt/v5.Parser", "github.com/dgrijalva/jwt-go.Parser",
23+
"github.com/dgrijalva/jwt-go/v4.Parser"
24+
], "Parse")
25+
|
26+
this = f
27+
)
28+
}
29+
30+
int getKeyFuncArgNum() { result = 1 }
31+
32+
DataFlow::Node getKeyFuncArg() { result = this.getACall().getArgument(this.getKeyFuncArgNum()) }
33+
}
34+
35+
/**
36+
* func (p *Parser) Parse(tokenString string, keyFunc Keyfunc)
37+
* func Parse(tokenString string, keyFunc Keyfunc)
38+
*/
39+
class GolangJwtValidField extends DataFlow::FieldReadNode {
40+
GolangJwtValidField() {
41+
this.getField()
42+
.hasQualifiedName([
43+
"github.com/golang-jwt/jwt", "github.com/golang-jwt/jwt/v4",
44+
"github.com/golang-jwt/jwt/v5", "github.com/dgrijalva/jwt-go",
45+
"github.com/dgrijalva/jwt-go/v4"
46+
] + ".Token", "Valid")
47+
}
48+
}
49+
50+
/**
51+
* func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc)
52+
* func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc)
53+
*/
54+
class GolangJwtParseWithClaims extends Function {
55+
GolangJwtParseWithClaims() {
56+
exists(DataFlow::Function f |
57+
f.hasQualifiedName([
58+
"github.com/golang-jwt/jwt", "github.com/golang-jwt/jwt/v4",
59+
"github.com/golang-jwt/jwt/v5", "github.com/dgrijalva/jwt-go",
60+
"github.com/dgrijalva/jwt-go/v4"
61+
], "ParseWithClaims")
62+
|
63+
this = f
64+
)
65+
or
66+
exists(DataFlow::Method f |
67+
f.hasQualifiedName([
68+
"github.com/golang-jwt/jwt.Parser", "github.com/golang-jwt/jwt/v4.Parser",
69+
"github.com/golang-jwt/jwt/v5.Parser", "github.com/dgrijalva/jwt-go.Parser",
70+
"github.com/dgrijalva/jwt-go/v4.Parser"
71+
], "ParseWithClaims")
72+
|
73+
this = f
74+
)
75+
}
76+
77+
int getKeyFuncArgNum() { result = 2 }
78+
79+
DataFlow::Node getKeyFuncArg() { result = this.getACall().getArgument(this.getKeyFuncArgNum()) }
80+
}
81+
82+
/**
83+
* func (p *Parser) ParseUnverified(tokenString string, claims Claims)
84+
*/
85+
class GolangJwtParseUnverified extends Function {
86+
GolangJwtParseUnverified() {
87+
exists(DataFlow::Method f |
88+
f.hasQualifiedName([
89+
"github.com/golang-jwt/jwt.Parser", "github.com/golang-jwt/jwt/v4.Parser",
90+
"github.com/golang-jwt/jwt/v5.Parser", "github.com/dgrijalva/jwt-go.Parser",
91+
"github.com/dgrijalva/jwt-go/v4.Parser"
92+
], "ParseUnverified")
93+
|
94+
this = f
95+
)
96+
}
97+
}
98+
99+
/**
100+
* func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc, options ...ParseFromRequestOption)
101+
*/
102+
class GolangJwtParseFromRequest extends Function {
103+
GolangJwtParseFromRequest() {
104+
exists(DataFlow::Function f |
105+
f.hasQualifiedName([
106+
"github.com/golang-jwt/jwt/request", "github.com/golang-jwt/jwt/v4/request",
107+
"github.com/dgrijalva/jwt-go/request", "github.com/golang-jwt/jwt/v4/request",
108+
"github.com/dgrijalva/jwt-go/v5/request"
109+
], "ParseFromRequest")
110+
|
111+
this = f
112+
)
113+
}
114+
115+
int getKeyFuncArgNum() { result = 2 }
116+
117+
DataFlow::Node getKeyFuncArg() { result = this.getACall().getArgument(this.getKeyFuncArgNum()) }
118+
}
119+
120+
/**
121+
* func ParseFromRequestWithClaims(req *http.Request, extractor Extractor, claims jwt.Claims, keyFunc jwt.Keyfunc)
122+
*/
123+
class GolangJwtParseFromRequestWithClaims extends Function {
124+
GolangJwtParseFromRequestWithClaims() {
125+
exists(DataFlow::Function f |
126+
f.hasQualifiedName([
127+
"github.com/golang-jwt/jwt/request", "github.com/golang-jwt/jwt/v4/request",
128+
"github.com/dgrijalva/jwt-go/request", "github.com/golang-jwt/jwt/v4/request",
129+
"github.com/dgrijalva/jwt-go/v5/request"
130+
], "ParseFromRequestWithClaims")
131+
|
132+
this = f
133+
)
134+
}
135+
136+
int getKeyFuncArgNum() { result = 3 }
137+
138+
DataFlow::Node getKeyFuncArg() { result = this.getACall().getArgument(this.getKeyFuncArgNum()) }
139+
}
140+
141+
/**
142+
*func (t *JSONWebToken) Claims(key interface{}, dest ...interface{})
143+
*/
144+
class GoJoseClaims extends Function {
145+
GoJoseClaims() {
146+
exists(DataFlow::Method f |
147+
f.hasQualifiedName([
148+
"gopkg.in/square/go-jose/jwt.JSONWebToken", "gopkg.in/square/go-jose.v2/jwt.JSONWebToken",
149+
"gopkg.in/square/go-jose.v3/jwt.JSONWebToken",
150+
"github.com/go-jose/go-jose/jwt.JSONWebToken",
151+
"github.com/go-jose/go-jose/v3/jwt.JSONWebToken"
152+
], "Claims")
153+
|
154+
this = f
155+
)
156+
}
157+
158+
int getKeyFuncArgNum() { result = 1 }
159+
160+
DataFlow::Node getKeyFuncArg() { result = this.getACall().getArgument(this.getKeyFuncArgNum()) }
161+
}
162+
163+
/**
164+
* func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{})
165+
*/
166+
class GoJoseUnsafeClaims extends Function {
167+
GoJoseUnsafeClaims() {
168+
exists(DataFlow::Method f |
169+
f.hasQualifiedName([
170+
"gopkg.in/square/go-jose/jwt.JSONWebToken", "gopkg.in/square/go-jose.v2/jwt.JSONWebToken",
171+
"gopkg.in/square/go-jose.v3/jwt.JSONWebToken",
172+
"github.com/go-jose/go-jose/jwt.JSONWebToken",
173+
"github.com/go-jose/go-jose/v3/jwt.JSONWebToken"
174+
], "UnsafeClaimsWithoutVerification")
175+
|
176+
this = f
177+
)
178+
}
179+
}
180+
181+
predicate golangJwtIsAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
182+
exists(DataFlow::Function f, DataFlow::CallNode call |
183+
f.hasQualifiedName([
184+
"github.com/golang-jwt/jwt", "github.com/golang-jwt/jwt/v4", "github.com/golang-jwt/jwt/v5",
185+
"github.com/dgrijalva/jwt-go", "github.com/dgrijalva/jwt-go/v4"
186+
],
187+
[
188+
"ParseECPrivateKeyFromPEM", "ParseECPublicKeyFromPEM", "ParseEdPrivateKeyFromPEM",
189+
"ParseEdPublicKeyFromPEM", "ParseRSAPrivateKeyFromPEM", "ParseRSAPublicKeyFromPEM",
190+
"RegisterSigningMethod"
191+
])
192+
|
193+
call = f.getACall() and
194+
nodeFrom = call.getArgument(0) and
195+
nodeTo = call
196+
)
197+
or
198+
exists(DataFlow::Function f, DataFlow::CallNode call |
199+
f instanceof GolangJwtParse
200+
or
201+
f instanceof GolangJwtParseWithClaims
202+
|
203+
call = f.getACall() and
204+
nodeFrom = call.getArgument(0) and
205+
nodeTo = call
206+
)
207+
or
208+
exists(DataFlow::FieldReadNode f | f instanceof GolangJwtValidField |
209+
nodeFrom = f.getBase() and
210+
nodeTo = f
211+
)
212+
}
213+
214+
predicate test(DataFlow::Function f, DataFlow::CallNode call) {
215+
f.hasQualifiedName([
216+
"gopkg.in/square/go-jose/jwt", "gopkg.in/square/go-jose.v2/jwt",
217+
"gopkg.in/square/go-jose.v3/jwt", "github.com/go-jose/go-jose/jwt",
218+
"github.com/go-jose/go-jose/v3/jwt"
219+
], ["ParseEncrypted", "ParseSigned",]) and
220+
call = f.getACall().getArgument(0)
221+
}
222+
223+
predicate goJoseIsAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
224+
exists(DataFlow::Function f, DataFlow::CallNode call |
225+
f.hasQualifiedName([
226+
"gopkg.in/square/go-jose/jwt", "gopkg.in/square/go-jose.v2/jwt",
227+
"gopkg.in/square/go-jose.v3/jwt", "github.com/go-jose/go-jose/jwt",
228+
"github.com/go-jose/go-jose/v3/jwt"
229+
], ["ParseEncrypted", "ParseSigned",])
230+
|
231+
call = f.getACall() and
232+
nodeFrom = call.getArgument(0) and
233+
nodeTo = call
234+
)
235+
or
236+
exists(DataFlow::Function f, DataFlow::CallNode call |
237+
f.hasQualifiedName([
238+
"gopkg.in/square/go-jose/jwt.NestedJSONWebToken",
239+
"gopkg.in/square/go-jose.v2/jwt.NestedJSONWebToken",
240+
"gopkg.in/square/go-jose.v3/jwt.NestedJSONWebToken",
241+
"github.com/go-jose/go-jose/jwt.NestedJSONWebToken",
242+
"github.com/go-jose/go-jose/v3/jw.NestedJSONWebTokent"
243+
], "ParseSignedAndEncrypted")
244+
|
245+
call = f.getACall() and
246+
nodeFrom = call.getArgument(0) and
247+
nodeTo = call
248+
)
249+
or
250+
exists(DataFlow::Method f, DataFlow::CallNode call |
251+
f.hasQualifiedName([
252+
"gopkg.in/square/go-jose/jwt.NestedJSONWebToken",
253+
"gopkg.in/square/go-jose.v2/jwt.NestedJSONWebToken",
254+
"gopkg.in/square/go-jose.v3/jwt.NestedJSONWebToken",
255+
"github.com/go-jose/go-jose/jwt.NestedJSONWebToken",
256+
"github.com/go-jose/go-jose/v3/jw.NestedJSONWebTokent"
257+
], "Decrypt")
258+
|
259+
call = f.getACall() and
260+
nodeFrom = call.getReceiver() and
261+
nodeTo = call
262+
)
263+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
3+
4+
func main() {
5+
// BAD: only decode jwt without verification
6+
notVerifyJWT(token)
7+
8+
// GOOD: decode with verification or verifiy plus decode
9+
notVerifyJWT(token)
10+
VerifyJWT(token)
11+
}
12+
13+
func notVerifyJWT(signedToken string) {
14+
fmt.Println("only decoding JWT")
15+
DecodedToken, _, err := jwt.NewParser().ParseUnverified(signedToken, &CustomerInfo{})
16+
if claims, ok := DecodedToken.Claims.(*CustomerInfo); ok {
17+
fmt.Printf("DecodedToken:%v\n", claims)
18+
} else {
19+
log.Fatal("error", err)
20+
}
21+
}
22+
func LoadJwtKey(token *jwt.Token) (interface{}, error) {
23+
return ARandomJwtKey, nil
24+
}
25+
func verifyJWT(signedToken string) {
26+
fmt.Println("verifying JWT")
27+
DecodedToken, err := jwt.ParseWithClaims(signedToken, &CustomerInfo{}, LoadJwtKey)
28+
if claims, ok := DecodedToken.Claims.(*CustomerInfo); ok && DecodedToken.Valid {
29+
fmt.Printf("NAME:%v ,ID:%v\n", claims.Name, claims.ID)
30+
} else {
31+
log.Fatal(err)
32+
}
33+
}
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+
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/hardcoded-key
7+
* @tags security
8+
* experimental
9+
* external/cwe/cwe-321
10+
*/
11+
12+
import go
13+
import semmle.go.security.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(GolangJwtValidField parse) or
20+
sink = any(GoJoseClaims parse).getACall().getReceiver()
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(GolangJwtParseUnverified parseunverified).getACall().getArgument(0)
38+
or
39+
sink = any(GoJoseUnsafeClaims parse).getACall().getReceiver()
40+
}
41+
42+
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
43+
golangJwtIsAdditionalFlowStep(nodeFrom, nodeTo)
44+
or
45+
goJoseIsAdditionalFlowStep(nodeFrom, nodeTo)
46+
}
47+
}
48+
49+
module WithValidation = TaintTracking::Global<WithValidationConfig>;
50+
51+
module NoValidation = TaintTracking::Global<NoValidationConfig>;
52+
53+
import NoValidation::PathGraph
54+
55+
from NoValidation::PathNode source, NoValidation::PathNode sink
56+
where NoValidation::flowPath(source, sink)
57+
select sink.getNode(), source, sink, "This $@.", source.getNode(), "decode"

0 commit comments

Comments
 (0)