Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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
28 changes: 28 additions & 0 deletions go/ql/src/experimental/CWE-321-V2/ExampleBad.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"fmt"
"log"

"github.com/go-jose/go-jose/v3/jwt"
)

var JwtKey = []byte("AllYourBase")

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

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)
}
}
41 changes: 41 additions & 0 deletions go/ql/src/experimental/CWE-321-V2/HardCodedKeys.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!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>
Using a hard-coded secret key for parsing JWT tokens in open source projects
can leave the application using the token vulnerable to authentication bypasses.
</p>

<p>
A JWT token is safe for enforcing authentication and access control as long as it can't be forged by a malicious actor. However, when a project exposes this secret publicly, these seemingly unforgeable tokens can now be easily forged.
Since the authentication as well as access control is typically enforced through these JWT tokens, an attacker armed with the secret can create a valid authentication token for any user and may even gain access to other privileged parts of the application.
</p>

</overview>
<recommendation>

<p>
Generating a cryptographically secure secret key during application initialization and using this generated key for future JWT parsing requests can prevent this vulnerability.
</p>

</recommendation>
<example>

<p>
The following code uses a hard-coded string as a secret for parsing user provided JWTs. In this case, an attacker can very easily forge a token by using the hard-coded secret.
</p>

<sample src="ExampleGood.go" />

</example>
<references>
<li>
CVE-2022-0664:
<a href="https://nvd.nist.gov/vuln/detail/CVE-2022-0664">Use of Hard-coded Cryptographic Key in Go github.com/gravitl/netmaker prior to 0.8.5,0.9.4,0.10.0,0.10.1. </a>
</li>
</references>

</qhelp>
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 Secret with a Constant value lead to authentication or authorization bypass
* @kind path-problem
* @problem.severity error
* @id go/parse-jwt-with-hardcoded-key
* @tags security
* experimental
* external/cwe/cwe-321
*/

import go
import experimental.frameworks.JWT

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

predicate isSink(DataFlow::Node sink) {
// first part is the JWT Parsing Functions that get a func type as an argument
// Find a node that has flow to a key Function argument
// then find the first result node of this Function which is the secret key
exists(FuncDef fd, DataFlow::Node n, DataFlow::ResultNode rn |
GolangJwtKeyFunc::flow(n, _) and
sink = rn and
fd = n.asExpr() and
rn.getRoot() = fd and
rn.getIndex() = 0
)
or
exists(Function f, DataFlow::ResultNode rn |
GolangJwtKeyFunc::flow(f.getARead(), _) and
// 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() = f.getFuncDecl() and
rn.getIndex() = 0
)
or
// second part is the JWT Parsing Functions that get a string or byte as an argument
sink = any(JwtParse jp).getKeyArg()
}
}

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(JwtParseWithKeyFunction parseJwt).getKeyFuncArg()
}
}

module JwtParseWithConstantKey = TaintTracking::Global<JwtParseWithConstantKeyConfig>;

module GolangJwtKeyFunc = TaintTracking::Global<GolangJwtKeyFuncConfig>;

import JwtParseWithConstantKey::PathGraph

from JwtParseWithConstantKey::PathNode source, JwtParseWithConstantKey::PathNode sink
where JwtParseWithConstantKey::flowPath(source, sink)
select sink.getNode(), source, sink, "This $@.", source.getNode(),
"Constant Key is used as JWT Secret key"
39 changes: 39 additions & 0 deletions go/ql/src/experimental/CWE-347/Example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"fmt"
"log"

"github.com/golang-jwt/jwt/v5"
)

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

// GOOD: decode with verification or verify 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)
}
}
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>
In 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>
56 changes: 56 additions & 0 deletions go/ql/src/experimental/CWE-347/ParseJWTWithoutVerification.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @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/parse-jwt-without-verification
* @tags security
* experimental
* external/cwe/cwe-321
*/

import go
import experimental.frameworks.JWT

module WithValidationConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }

predicate isSink(DataFlow::Node sink) {
sink = any(JwtParse jwtParse).getTokenArg() or
sink = any(JwtParseWithKeyFunction jwtParseWithKeyFunction).getTokenArg()
}

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(JwtUnverifiedParse parseUnverified).getTokenArg()
}

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 JWT is parsed without verification and received from $@.",
source.getNode(), "this user-controlled source"
Loading