Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
281 changes: 281 additions & 0 deletions go/ql/lib/semmle/go/security/JWT.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
import go

/**
* A class that contains the following function and method:
*
* func (p *Parser) Parse(tokenString string, keyFunc Keyfunc)
*
* func Parse(tokenString string, keyFunc Keyfunc)
*/
class GolangJwtParse extends Function {
GolangJwtParse() {
exists(DataFlow::Function f |
f.hasQualifiedName([
"github.com/golang-jwt/jwt", "github.com/golang-jwt/jwt/v4",
"github.com/golang-jwt/jwt/v5", "github.com/dgrijalva/jwt-go",
"github.com/dgrijalva/jwt-go/v4",
], "Parse")
|
this = f
)
or
exists(DataFlow::Method f |
f.hasQualifiedName([
"github.com/golang-jwt/jwt.Parser", "github.com/golang-jwt/jwt/v4.Parser",
"github.com/golang-jwt/jwt/v5.Parser", "github.com/dgrijalva/jwt-go.Parser",
"github.com/dgrijalva/jwt-go/v4.Parser"
], "Parse")
|
this = f
)
}

int getKeyFuncArgNum() { result = 1 }

DataFlow::Node getKeyFuncArg() { result = this.getACall().getArgument(this.getKeyFuncArgNum()) }
}
Comment thread
owen-mc marked this conversation as resolved.
Outdated

/**
* A class that contains the following function and method:
*
* func (p *Parser) Parse(tokenString string, keyFunc Keyfunc)
*
* func Parse(tokenString string, keyFunc Keyfunc)
*/
class GolangJwtValidField extends DataFlow::FieldReadNode {
GolangJwtValidField() {
this.getField()
.hasQualifiedName([
"github.com/golang-jwt/jwt", "github.com/golang-jwt/jwt/v4",
"github.com/golang-jwt/jwt/v5", "github.com/dgrijalva/jwt-go",
"github.com/dgrijalva/jwt-go/v4"
] + ".Token", "Valid")
}
}

/**
* A class that contains the following function and method:
*
* func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc)
*
* func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc)
*/
class GolangJwtParseWithClaims extends Function {
GolangJwtParseWithClaims() {
exists(DataFlow::Function f |
f.hasQualifiedName([
"github.com/golang-jwt/jwt", "github.com/golang-jwt/jwt/v4",
"github.com/golang-jwt/jwt/v5", "github.com/dgrijalva/jwt-go",
"github.com/dgrijalva/jwt-go/v4"
], "ParseWithClaims")
|
this = f
)
or
exists(DataFlow::Method f |
f.hasQualifiedName([
"github.com/golang-jwt/jwt.Parser", "github.com/golang-jwt/jwt/v4.Parser",
"github.com/golang-jwt/jwt/v5.Parser", "github.com/dgrijalva/jwt-go.Parser",
"github.com/dgrijalva/jwt-go/v4.Parser"
], "ParseWithClaims")
|
this = f
)
}

int getKeyFuncArgNum() { result = 2 }

DataFlow::Node getKeyFuncArg() { result = this.getACall().getArgument(this.getKeyFuncArgNum()) }
}

/**
* A class that contains the following method:
*
* func (p *Parser) ParseUnverified(tokenString string, claims Claims)
*/
class GolangJwtParseUnverified extends Function {
GolangJwtParseUnverified() {
exists(DataFlow::Method f |
f.hasQualifiedName([
"github.com/golang-jwt/jwt.Parser", "github.com/golang-jwt/jwt/v4.Parser",
"github.com/golang-jwt/jwt/v5.Parser", "github.com/dgrijalva/jwt-go.Parser",
"github.com/dgrijalva/jwt-go/v4.Parser"
], "ParseUnverified")
|
this = f
)
}
}

/**
* A class that contains the following function:
*
* func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc, options ...ParseFromRequestOption)
*/
class GolangJwtParseFromRequest extends Function {
GolangJwtParseFromRequest() {
exists(DataFlow::Function f |
f.hasQualifiedName([
"github.com/golang-jwt/jwt/request", "github.com/golang-jwt/jwt/v4/request",
"github.com/dgrijalva/jwt-go/request", "github.com/dgrijalva/jwt-go/v4/request"
], "ParseFromRequest")
|
this = f
)
}

int getKeyFuncArgNum() { result = 2 }

DataFlow::Node getKeyFuncArg() { result = this.getACall().getArgument(this.getKeyFuncArgNum()) }
}

/**
* A class that contains the following function:
*
* func ParseFromRequestWithClaims(req *http.Request, extractor Extractor, claims jwt.Claims, keyFunc jwt.Keyfunc)
*/
class GolangJwtParseFromRequestWithClaims extends Function {
GolangJwtParseFromRequestWithClaims() {
exists(DataFlow::Function f |
f.hasQualifiedName([
"github.com/golang-jwt/jwt/request", "github.com/golang-jwt/jwt/v4/request",
"github.com/dgrijalva/jwt-go/request", "github.com/dgrijalva/jwt-go/v4/request"
], "ParseFromRequestWithClaims")
|
this = f
)
}

int getKeyFuncArgNum() { result = 3 }

DataFlow::Node getKeyFuncArg() { result = this.getACall().getArgument(this.getKeyFuncArgNum()) }
}

/**
* A class that contains the following method:
*
*func (t *JSONWebToken) Claims(key interface{}, dest ...interface{})
*/
class GoJoseClaims extends Function {
GoJoseClaims() {
exists(DataFlow::Method f |
f.hasQualifiedName([
"gopkg.in/square/go-jose/jwt.JSONWebToken", "gopkg.in/square/go-jose.v2/jwt.JSONWebToken",
"gopkg.in/square/go-jose.v3/jwt.JSONWebToken",
"github.com/go-jose/go-jose/jwt.JSONWebToken",
"github.com/go-jose/go-jose/v3/jwt.JSONWebToken"
], "Claims")
|
this = f
)
}

int getKeyFuncArgNum() { result = 1 }

DataFlow::Node getKeyFuncArg() { result = this.getACall().getArgument(this.getKeyFuncArgNum()) }
}

/**
* A class that contains the following method:
*
* func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{})
*/
class GoJoseUnsafeClaims extends Function {
GoJoseUnsafeClaims() {
exists(DataFlow::Method f |
f.hasQualifiedName([
"gopkg.in/square/go-jose/jwt.JSONWebToken", "gopkg.in/square/go-jose.v2/jwt.JSONWebToken",
"gopkg.in/square/go-jose.v3/jwt.JSONWebToken",
"github.com/go-jose/go-jose/jwt.JSONWebToken",
"github.com/go-jose/go-jose/v3/jwt.JSONWebToken"
], "UnsafeClaimsWithoutVerification")
|
this = f
)
}
}

/**
* Holds if there are additioanl steps related to parsing the secret keys
*/
predicate golangJwtIsAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(DataFlow::Function f, DataFlow::CallNode call |
f.hasQualifiedName([
"github.com/golang-jwt/jwt", "github.com/golang-jwt/jwt/v4", "github.com/golang-jwt/jwt/v5"
],
[
"ParseECPrivateKeyFromPEM", "ParseECPublicKeyFromPEM", "ParseEdPrivateKeyFromPEM",
"ParseEdPublicKeyFromPEM", "ParseRSAPrivateKeyFromPEM", "ParseRSAPublicKeyFromPEM",
"RegisterSigningMethod"
]) or
f.hasQualifiedName(["github.com/dgrijalva/jwt-go", "github.com/dgrijalva/jwt-go/v4"],
[
"ParseECPrivateKeyFromPEM", "ParseECPublicKeyFromPEM", "ParseRSAPrivateKeyFromPEM",
"ParseRSAPrivateKeyFromPEMWithPassword", "ParseRSAPublicKeyFromPEM"
])
Comment thread
am0o0 marked this conversation as resolved.
Outdated
|
call = f.getACall() and
nodeFrom = call.getArgument(0) and
nodeTo = call
Comment thread
am0o0 marked this conversation as resolved.
Outdated
)
or
exists(DataFlow::Function f, DataFlow::CallNode call |
f instanceof GolangJwtParse
or
f instanceof GolangJwtParseWithClaims
|
call = f.getACall() and
nodeFrom = call.getArgument(0) and
nodeTo = call
)
or
exists(DataFlow::FieldReadNode f | f instanceof GolangJwtValidField |
nodeFrom = f.getBase() and
nodeTo = f
)
}

/**
* Holds if there are additioanl steps related to parsing the secret keys
*/
predicate goJoseIsAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(DataFlow::Function f, DataFlow::CallNode call |
f.hasQualifiedName([
"gopkg.in/square/go-jose/jwt", "gopkg.in/square/go-jose.v2/jwt",
"gopkg.in/square/go-jose.v3/jwt", "github.com/go-jose/go-jose/jwt",
"github.com/go-jose/go-jose/v3/jwt"
], ["ParseEncrypted", "ParseSigned",])
|
call = f.getACall() and
nodeFrom = call.getArgument(0) and
nodeTo = call
)
or
exists(DataFlow::Function f, DataFlow::CallNode call |
f.hasQualifiedName([
"gopkg.in/square/go-jose/jwt.NestedJSONWebToken",
"gopkg.in/square/go-jose.v2/jwt.NestedJSONWebToken",
"gopkg.in/square/go-jose.v3/jwt.NestedJSONWebToken",
"github.com/go-jose/go-jose/jwt.NestedJSONWebToken",
"github.com/go-jose/go-jose/v3/jw.NestedJSONWebTokent"
], "ParseSignedAndEncrypted")
|
call = f.getACall() and
nodeFrom = call.getArgument(0) and
nodeTo = call
)
or
exists(DataFlow::Method f, DataFlow::CallNode call |
f.hasQualifiedName([
"gopkg.in/square/go-jose/jwt.NestedJSONWebToken",
"gopkg.in/square/go-jose.v2/jwt.NestedJSONWebToken",
"gopkg.in/square/go-jose.v3/jwt.NestedJSONWebToken",
"github.com/go-jose/go-jose/jwt.NestedJSONWebToken",
"github.com/go-jose/go-jose/v3/jw.NestedJSONWebToken"
], "Decrypt")
|
call = f.getACall() and
nodeFrom = call.getReceiver() and
nodeTo = call
)
Comment thread
am0o0 marked this conversation as resolved.
}
21 changes: 21 additions & 0 deletions go/ql/src/experimental/CWE-321-V2/Example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@


var JwtKey = []byte("AllYourBase")

func main() {
// BAD: usage of a harcoded Key
verifyJWT(token)
}

func LoadJwtKey(token *jwt.Token) (interface{}, error) {
return JwtKey, nil
}
func verifyJWT(signedToken string) {
fmt.Println("verifying JWT")
DecodedToken, err := jwt.ParseWithClaims(signedToken, &CustomerInfo{}, LoadJwtKey)
if claims, ok := DecodedToken.Claims.(*CustomerInfo); ok && DecodedToken.Valid {
fmt.Printf("NAME:%v ,ID:%v\n", claims.Name, claims.ID)
} else {
log.Fatal(err)
}
}
65 changes: 65 additions & 0 deletions go/ql/src/experimental/CWE-321-V2/HardCodedKeys.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* @name Decoding JWT with hardcoded key
* @description Decoding JWT Secrect with a Constant value lead to authentication or authorization bypass
Comment thread
owen-mc marked this conversation as resolved.
Outdated
* @kind path-problem
* @problem.severity error
* @id go/hardcoded-key
* @tags security
* experimental
* external/cwe/cwe-321
*/

import go
import semmle.go.security.JWT

module JwtConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof StringLit }

predicate isSink(DataFlow::Node sink) {
exists(FuncDef fd, DataFlow::Node n, DataFlow::ResultNode rn |
GolangJwtKeyFunc::flow(n, _) and fd = n.asExpr()
|
rn.getRoot() = fd and
rn.getIndex() = 0 and
sink = rn
)
or
exists(Function fd, DataFlow::ResultNode rn | GolangJwtKeyFunc::flow(fd.getARead(), _) |
Comment thread
am0o0 marked this conversation as resolved.
Outdated
// sink is result of a method
sink = rn and
// the method is belong to a function in which is used as a JWT function key
rn.getRoot() = fd.getFuncDecl() and
rn.getIndex() = 0
)
Comment on lines +22 to +31
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.

These two exists have a huge amount in common. It would be better to combine them into one exists.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think If I merge them it can be much longer that it is.

}
}

module GolangJwtKeyFuncConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source = any(Function f).getARead()
or
source.asExpr() = any(FuncDef fd)
}

predicate isSink(DataFlow::Node sink) {
sink =
[
any(GolangJwtParse parseWithClaims).getKeyFuncArg(),
any(GolangJwtParseWithClaims parseWithClaims).getKeyFuncArg(),
any(GolangJwtParseFromRequest parseWithClaims).getKeyFuncArg(),
any(GolangJwtParseFromRequestWithClaims parseWithClaims).getKeyFuncArg(),
any(GoJoseClaims parseWithClaims).getKeyFuncArg(),
]
Comment thread
owen-mc marked this conversation as resolved.
Outdated
}
}

module Jwt = TaintTracking::Global<JwtConfig>;

module GolangJwtKeyFunc = TaintTracking::Global<GolangJwtKeyFuncConfig>;

import Jwt::PathGraph

from Jwt::PathNode source, Jwt::PathNode sink
where Jwt::flowPath(source, sink)
select sink.getNode(), source, sink, "This $@.", source.getNode(),
"Constant Key is used as JWT Secret key"
Loading