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

Skip to content
Merged
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
V1
  • Loading branch information
am0o0 committed Aug 28, 2023
commit 68392e7ae7b0e0e30875acd6a59f238423c8f051
263 changes: 263 additions & 0 deletions go/ql/lib/semmle/go/security/JWT.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import go

/**
* func (p *Parser) Parse(tokenString string, keyFunc Keyfunc)
* func Parse(tokenString string, keyFunc Keyfunc)
*/
Comment thread Fixed
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

/**
* func (p *Parser) Parse(tokenString string, keyFunc Keyfunc)
* func Parse(tokenString string, keyFunc Keyfunc)
*/
Comment thread Fixed
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")
}
}

/**
* func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc)
* func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc)
*/
Comment thread Fixed
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()) }
}

/**
* func (p *Parser) ParseUnverified(tokenString string, claims Claims)
*/
Comment thread Fixed
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
)
}
}

/**
* func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc, options ...ParseFromRequestOption)
*/
Comment thread Fixed
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/golang-jwt/jwt/v4/request",
"github.com/dgrijalva/jwt-go/v5/request"
], "ParseFromRequest")
|
this = f
)
}

int getKeyFuncArgNum() { result = 2 }

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

/**
* func ParseFromRequestWithClaims(req *http.Request, extractor Extractor, claims jwt.Claims, keyFunc jwt.Keyfunc)
*/
Comment thread Fixed
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/golang-jwt/jwt/v4/request",
"github.com/dgrijalva/jwt-go/v5/request"
], "ParseFromRequestWithClaims")
|
this = f
)
}

int getKeyFuncArgNum() { result = 3 }

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

/**
*func (t *JSONWebToken) Claims(key interface{}, dest ...interface{})
*/
Comment thread Fixed
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()) }
}

/**
* func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{})
*/
Comment thread Fixed
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
)
}
}

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",
"github.com/dgrijalva/jwt-go", "github.com/dgrijalva/jwt-go/v4"
],
[
"ParseECPrivateKeyFromPEM", "ParseECPublicKeyFromPEM", "ParseEdPrivateKeyFromPEM",
"ParseEdPublicKeyFromPEM", "ParseRSAPrivateKeyFromPEM", "ParseRSAPublicKeyFromPEM",
"RegisterSigningMethod"
])
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
)
}

predicate test(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",]) and
call = f.getACall().getArgument(0)
}

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.NestedJSONWebTokent"
], "Decrypt")
|
call = f.getACall() and
nodeFrom = call.getReceiver() and
nodeTo = call
)
Comment thread
am0o0 marked this conversation as resolved.
}
33 changes: 33 additions & 0 deletions go/ql/src/experimental/CWE-321-NoVerification/Example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@



func main() {
// BAD: only decode jwt without verification
notVerifyJWT(token)

// GOOD: decode with verification or verifiy plus decode
notVerifyJWT(token)
VerifyJWT(token)
}

func notVerifyJWT(signedToken string) {
fmt.Println("only decoding JWT")
DecodedToken, _, err := jwt.NewParser().ParseUnverified(signedToken, &CustomerInfo{})
if claims, ok := DecodedToken.Claims.(*CustomerInfo); ok {
fmt.Printf("DecodedToken:%v\n", claims)
} else {
log.Fatal("error", err)
}
}
func LoadJwtKey(token *jwt.Token) (interface{}, error) {
return ARandomJwtKey, 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)
}
}
34 changes: 34 additions & 0 deletions go/ql/src/experimental/CWE-321-NoVerification/NoVerification.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
A JSON Web Token (JWT) is used for authenticating and managing users in an application.
</p>
<p>
Only Decoding JWTs without checking if they have a valid signature or not can lead to security vulnerabilities.
</p>

</overview>
<recommendation>

<p>
Don't use methods that only decode JWT, Instead use methods that verify the signature of JWT.
</p>

</recommendation>
<example>

<p>
The following code you can see an Example from a popular Library.
</p>

<sample src="Example.go" />

</example>
<references>
<li>
<a href="https://github.com/argoproj/argo-cd/security/advisories/GHSA-q9hr-j4rf-8fjc">JWT audience claim is not verified</a>
</li>
</references>

</qhelp>
57 changes: 57 additions & 0 deletions go/ql/src/experimental/CWE-321-NoVerification/NoVerification.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @name Use of JWT Methods that only decode user provided Token
* @description Using JWT methods without verification can cause to authorization or authentication bypass
* @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 WithValidationConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }

predicate isSink(DataFlow::Node sink) {
sink = any(GolangJwtValidField parse) or
sink = any(GoJoseClaims parse).getACall().getReceiver()
}

predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
golangJwtIsAdditionalFlowStep(nodeFrom, nodeTo)
or
goJoseIsAdditionalFlowStep(nodeFrom, nodeTo)
}
}

module NoValidationConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source instanceof UntrustedFlowSource and
not WithValidation::flow(source, _)
}

predicate isSink(DataFlow::Node sink) {
sink = any(GolangJwtParseUnverified parseunverified).getACall().getArgument(0)
or
sink = any(GoJoseUnsafeClaims parse).getACall().getReceiver()
}

predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
golangJwtIsAdditionalFlowStep(nodeFrom, nodeTo)
or
goJoseIsAdditionalFlowStep(nodeFrom, nodeTo)
}
}

module WithValidation = TaintTracking::Global<WithValidationConfig>;

module NoValidation = TaintTracking::Global<NoValidationConfig>;

import NoValidation::PathGraph

from NoValidation::PathNode source, NoValidation::PathNode sink
where NoValidation::flowPath(source, sink)
select sink.getNode(), source, sink, "This $@.", source.getNode(), "decode"
Loading