-
Notifications
You must be signed in to change notification settings - Fork 2k
Go: Add JWT Algorithm Confusion and JWT decoding without Signature Verification #14081
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
4f2d15e
fafdee5
7730ad6
8d2bc38
01a28eb
1d6a805
ecbde43
3a2b66e
1370bac
21fa77d
84359fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import go | ||
|
|
||
| module JWT { | ||
|
|
||
| string packageLestrrat() { | ||
| result = | ||
| package("github.com/lestrrat-go/jwx/v2/jwt", "") | ||
|
|
||
| } | ||
| string packagePathModern() { | ||
| result = | ||
| package(["github.com/golang-jwt/jwt/v5", "github.com/golang-jwt/jwt/v4"], "") | ||
| } | ||
| string packagePathOld(){ | ||
| result = | ||
| package( | ||
| "github.com/golang-jwt/jwt", "") | ||
| } | ||
|
|
||
| class NewParser extends Function { | ||
| NewParser() { | ||
| this.hasQualifiedName([packagePathModern()], "NewParser") | ||
| } | ||
| } | ||
|
|
||
| class WithValidMethods extends Function{ | ||
| WithValidMethods() { | ||
| this.hasQualifiedName([packagePathModern()], "WithValidMethods") | ||
|
|
||
| } | ||
| } | ||
|
|
||
| class SafeJWTParserMethod extends Method { | ||
|
|
||
| SafeJWTParserMethod() { | ||
| this.hasQualifiedName(packagePathModern(), "Parser", ["Parse", "ParseWithClaims"]) | ||
| } | ||
| } | ||
|
|
||
| class SafeJWTParserFunc extends Function{ | ||
|
|
||
| SafeJWTParserFunc(){ | ||
| this.hasQualifiedName([packagePathModern(),packagePathOld()], ["Parse", "ParseWithClaims"]) | ||
| } | ||
| } | ||
|
|
||
| class UnafeJWTParserMethod extends Method{ | ||
|
|
||
| UnafeJWTParserMethod(){ | ||
| this.hasQualifiedName(packagePathModern(), "Parser", "ParseUnverified") | ||
| } | ||
| } | ||
| class LestrratParse extends Function{ | ||
| LestrratParse() { | ||
| this.hasQualifiedName(packageLestrrat(), "Parse") | ||
| } | ||
| } | ||
|
Comment on lines
+64
to
+69
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This model isn't used anywhere. Did you mean to use it somewhere? Or did you model it for completeness? Consider deleting - the model can easily be added in future if it is needed. |
||
| class LestrratSafeOptions extends Function{ | ||
| LestrratSafeOptions() { | ||
| this.hasQualifiedName(packageLestrrat(), ["WithKey", "WithKeySet"]) | ||
| } | ||
| } | ||
| class LestrratParseInsecure extends Function{ | ||
| LestrratParseInsecure() { | ||
| this.hasQualifiedName(packageLestrrat(), "ParseInsecure") | ||
| } | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| /** | ||
| * @name JWT Method Check | ||
| * @description Trusting the Method provided by the incoming JWT token may lead to an algorithim confusion | ||
| * @kind problem | ||
| * @problem.severity error | ||
| * @security-severity 8.0 | ||
| * @precision medium | ||
| * @id go/jwt-pitfalls | ||
| * @tags security | ||
| * external/cwe/cwe-347 | ||
| */ | ||
| import go | ||
| import JWT | ||
| import DataFlow | ||
|
|
||
| //Method is checked in second argument | ||
| // from Field f, CallExpr c, SafeJWTParserFunc jpf, UnafeJWTParserMethod upm | ||
| // where (f.hasQualifiedName(packagePathModern(), "Token", "Method") | ||
| // and f.getARead().getRoot() = c.getArgument(1) | ||
| // and c.getTarget() = jpf) | ||
| // or | ||
| // c.getTarget() = upm | ||
| // select f.getARead(), c.getTarget() | ||
| // //Flow from NewParser to Parse | ||
| // from CallExpr c, CallExpr c2, NewParser m, WithValidMethods wvm, SafeJWTParserMethod spm | ||
| // where c.getAnArgument() = wvm.getACall().asExpr() | ||
| // and c.getTarget() = m and c2.getTarget() = spm and | ||
| // DataFlow::localFlow(m.getACall().getAResult(), spm.getACall().getReceiver()) | ||
| // select spm.getACall().getReceiver(), m.getACall().getAResult() | ||
|
|
||
| //lestrrat uses WithKeys or Keysets | ||
| // from LestrratParse lp, LestrratSafeOptions lwk, CallExpr c | ||
| // where c.getTarget() = lp and c.getAnArgument() = lwk.getACall().asExpr() and | ||
| // not(c.getArgument(0) = lwk.getACall().asExpr()) | ||
| // select lp | ||
| //other Library | ||
|
|
||
| //Method is checked in second argument | ||
| // from Field f, CallExpr c, SafeJWTParserFunc jpf | ||
| // where (f.hasQualifiedName(packagePathModern(), "Token", "Method") | ||
| // and f.getARead().getRoot() = c.getArgument(1) | ||
| // and c.getTarget() = jpf) | ||
| // select c.getTarget() | ||
|
|
||
|
|
||
| from CallNode c, NewParser m, WithValidMethods wvm, Function func | ||
| where | ||
| (c.getTarget() = func and | ||
| // //Flow from NewParser to Parse (check that this call to Parse does not use a Parser that sets Valid Methods) | ||
| ((func instanceof SafeJWTParserMethod and not exists( CallNode c2 | c2.getTarget() = m | ||
| and ( c2.getCall().getAnArgument() = wvm.getACall().asExpr() | ||
| and DataFlow::localFlow(c2.getAResult(), c.getReceiver())) | ||
| )) | ||
| //ParserFunc creates a new default Parser on call that accepts all methods | ||
| or func instanceof SafeJWTParserFunc | ||
| ) | ||
| //Check that the Parse(function or method) does not check the Token Method field, which most likely is a check for method type | ||
| and not exists(Field f | | ||
| f.hasQualifiedName(packagePathModern(), "Token", "Method") | ||
| and f.getARead().getRoot() = c.getCall().getAnArgument() | ||
| )) | ||
|
|
||
| select c | ||
|
|
||
| //lestrrat uses WithKeys or Keysets | ||
| // from LestrratParse lp, LestrratSafeOptions lwk, CallExpr c | ||
| // where c.getTarget() = lp and c.getAnArgument() = lwk.getACall().asExpr() and | ||
| // not(c.getArgument(0) = lwk.getACall().asExpr()) | ||
| // select lp |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /** | ||
| * @name JWT Insecure Parsing | ||
| * @description Parsing JWT tokens without checking the signature may result in an authentication bypass | ||
| * @kind problem | ||
| * @problem.severity error | ||
| * @security-severity 8.0 | ||
| * @precision medium | ||
| * @id go/jwt-insecure-signing | ||
| * @tags security | ||
| * external/cwe/cwe-347 | ||
| */ | ||
| import go | ||
| import JWT | ||
| import DataFlow | ||
| from CallNode c, LestrratParse lp | ||
| where c.getTarget() instanceof LestrratParseInsecure or c.getTarget() instanceof UnafeJWTParserMethod | ||
| or (c.getTarget() = lp and not exists(LestrratSafeOptions lwk | c.getCall().getAnArgument() = lwk.getACall().asExpr() | ||
| and not(c.getCall().getArgument(0) = lwk.getACall().asExpr()))) | ||
| select c, "test" | ||
|
|
||
|
|
||
| //lestrrat uses WithKeys or Keysets | ||
| // from LestrratParse lp, LestrratSafeOptions lwk, CallExpr c | ||
| // where c.getTarget() = lp and not (c.getAnArgument() = lwk.getACall().asExpr() | ||
| // and not(c.getArgument(0) = lwk.getACall().asExpr())) | ||
| // select c, "test" | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "time" | ||
|
|
||
| "github.com/lestrrat-go/jwx/v2/jwa" | ||
| "github.com/lestrrat-go/jwx/v2/jwk" | ||
| "github.com/lestrrat-go/jwx/v2/jwt" | ||
| ) | ||
|
|
||
| func ExampleJWT_Parse() { | ||
| jwkSymmetricKey, _ := jwk.FromRaw([]byte(`abracadabra`)) | ||
| tok, err := jwt.NewBuilder(). | ||
| Issuer(`github.com/lestrrat-go/jwx`). | ||
| IssuedAt(time.Now().Add(-5 * time.Minute)). | ||
| Expiration(time.Now().Add(time.Hour)). | ||
| Build() | ||
| v, err := jwt.Sign(tok, jwt.WithKey(jwa.HS256, jwkSymmetricKey)) | ||
| if err != nil { | ||
| return | ||
| } | ||
| jwtSignedWithHS256 := v | ||
| tok, err = jwt.Parse(jwtSignedWithHS256) | ||
| if err != nil { | ||
| return | ||
| } | ||
| tok, err = jwt.Parse(jwtSignedWithHS256, jwt.WithKey(jwa.HS256, jwkSymmetricKey)) | ||
| if err != nil { | ||
| return | ||
| } | ||
| tok, err = jwt.ParseInsecure(jwtSignedWithHS256) | ||
| if err != nil { | ||
| return | ||
| } | ||
| _ = tok | ||
| // OUTPUT: | ||
| } | ||
| func main() { | ||
| ExampleJWT_Parse() | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| JWTParsingAlgorithim.ql |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Security/CWE-347/JWTParsingSignature.ql |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| module hello/hello | ||
|
|
||
| go 1.21.0 | ||
|
|
||
| require github.com/lestrrat-go/jwx/v2 v2.0.12 | ||
|
|
||
| require ( | ||
| github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect | ||
| github.com/goccy/go-json v0.10.2 // indirect | ||
| github.com/lestrrat-go/blackmagic v1.0.1 // indirect | ||
| github.com/lestrrat-go/httpcc v1.0.1 // indirect | ||
| github.com/lestrrat-go/httprc v1.0.4 // indirect | ||
| github.com/lestrrat-go/iter v1.0.2 // indirect | ||
| github.com/lestrrat-go/option v1.0.1 // indirect | ||
| github.com/segmentio/asm v1.2.0 // indirect | ||
| golang.org/x/crypto v0.12.0 // indirect | ||
| golang.org/x/sys v0.11.0 // indirect | ||
| ) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.