Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Next Next commit
Initial JWT Library
  • Loading branch information
Kwstubbs committed Aug 25, 2023
commit 4f2d15ea21c613b3b2248e69e72f74fd51e84f5a
1 change: 1 addition & 0 deletions go/ql/lib/go.qll
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import semmle.go.frameworks.Glog
import semmle.go.frameworks.GoMicro
import semmle.go.frameworks.GoRestfulHttp
import semmle.go.frameworks.Gqlgen
import semmle.go.frameworks.JWT
import semmle.go.frameworks.K8sIoApimachineryPkgRuntime
import semmle.go.frameworks.K8sIoApiCoreV1
import semmle.go.frameworks.K8sIoClientGo
Expand Down
65 changes: 65 additions & 0 deletions go/ql/lib/semmle/go/frameworks/JWT.qll
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")
Comment thread Fixed
}
}

class WithValidMethods extends Function{
WithValidMethods() {
this.hasQualifiedName([packagePathModern()], "WithValidMethods")
Comment thread Fixed
}
}

class SafeJWTParserMethod extends Method {
Comment thread Fixed
SafeJWTParserMethod() {
this.hasQualifiedName(packagePathModern(), "Parser", ["Parse", "ParseWithClaims"])
}
}

class SafeJWTParserFunc extends Function{
Comment thread Fixed
SafeJWTParserFunc(){
this.hasQualifiedName([packagePathModern(),packagePathOld()], ["Parse", "ParseWithClaims"])
}
}

class UnafeJWTParserMethod extends Method{
Comment thread Fixed
UnafeJWTParserMethod(){
this.hasQualifiedName(packagePathModern(), "Parser", "ParseUnverified")
}
}
class LestrratParse extends Function{
LestrratParse() {
this.hasQualifiedName(packageLestrrat(), "Parse")
}
}
Comment on lines +64 to +69
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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")
}
}

}
Empty file.
Empty file.
69 changes: 69 additions & 0 deletions go/ql/src/Security/CWE-347/JWTParsingAlgorithim.ql
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
27 changes: 27 additions & 0 deletions go/ql/src/Security/CWE-347/JWTParsingSignature.ql
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()
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
JWTParsingAlgorithim.ql
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Security/CWE-347/JWTParsingSignature.ql
18 changes: 18 additions & 0 deletions go/ql/test/query-tests/Security/CWE-347/go.mod
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.

Loading