Skip to content
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

Go: Add JWT Algorithm Confusion Query #14534

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

Kwstubbs
Copy link
Contributor

@Kwstubbs Kwstubbs commented Oct 18, 2023

JWT Algorithm Confusion Portion of #14081

@Kwstubbs Kwstubbs requested a review from a team as a code owner October 18, 2023 06:55
@github-actions
Copy link
Contributor

github-actions bot commented Oct 18, 2023

QHelp previews:

go/ql/src/experimental/CWE-347/JWTParsingAlgorithm.qhelp

JWT Method Check

Not verifying the JWT algorithim present in a JWT token when verifying its signature may result in algorithim confusion.

Recommendation

Before presenting a key to verify a signature, ensure that the key corresponds to the algorithim present in the JWT token.

Example

The following code uses the asymmetric public key to verify the JWT token and assumes that the algorithim is RSA. By not checking the signature, an attacker can specify the HMAC option and use the same public key, which is assumed to be public and often at endpoint /jwt/jwks.jso, to sign the JWT token and bypass authentication.

package main

import (
	"fmt"

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

type JWT struct {
	privateKey []byte
	publicKey  []byte
}

func (j JWT) Validate(token string) (interface{}, error) {
	key, err := jwt.ParseRSAPublicKeyFromPEM(j.publicKey)
	if err != nil {
		return "", fmt.Errorf("validate: parse key: %w", err)
	}

	tok, err := jwt.Parse(token, func(jwtToken *jwt.Token) (interface{}, error) {

		return key, nil
	})
	if err != nil {
		return nil, fmt.Errorf("validate: %w", err)
	}

	claims, ok := tok.Claims.(jwt.MapClaims)
	if !ok || !tok.Valid {
		return nil, fmt.Errorf("validate: invalid")
	}

	return claims["dat"], nil
}

References

@Kwstubbs Kwstubbs changed the title JWT Alg Confusion Query Go: Add JWT Algorithm Confusion Query Oct 18, 2023
@owen-mc
Copy link
Contributor

owen-mc commented Oct 18, 2023

482 tests passed; 2 tests failed:
FAILED: /home/runner/work/codeql/codeql/go/ql/test/experimental/CWE-347/CONSISTENCY/UnexpectedFrontendErrors.ql
FAILED: /home/runner/work/codeql/codeql/go/ql/test/experimental/CWE-347/ParseJWTWithoutVerification.qlref

The first one means there are build failures in a .go file in that directory.

//Check that the Parse(function or method) does not check the Token Method field, which most likely is a check for method type
not exists(Field f |
f.hasQualifiedName(golangJwtModern(), "Token", "Method") and
f.getARead().getRoot() = c.getCall().getAnArgument()
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to specify which argument?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, I think this only works if the function literal is specified in place. If it's assigned to a variable and then that variable is used as an argument, would this catch it?

Copy link
Contributor Author

@Kwstubbs Kwstubbs Oct 19, 2023

Choose a reason for hiding this comment

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

@owen-mc having a bit of trouble coming up with a good way to see if the function name that is passed into the argument has a field read of the Method or not. If you can find another way that avoids strings altogether please let me know.

@Kwstubbs Kwstubbs requested a review from owen-mc October 23, 2023 15:45
// //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, WithValidMethods wvm, NewParser m, int i |

Check warning

Code scanning / CodeQL

Omittable 'exists' variable

This exists variable can be omitted by using a don't-care expression [in this argument](1).
Copy link
Contributor Author

Choose a reason for hiding this comment

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

will change to getASyntacticArgument

or
exists(FunctionName fn |
c.getCall().getArgument(1) = fn and
fn.toString() = f.getARead().asExpr().getEnclosingFunction().getName()

Check warning

Code scanning / CodeQL

Using 'toString' in query logic

Query logic depends on implementation of 'toString'.
Comment on lines +40 to +46
not exists(CallNode c2, WithValidMethods wvm, NewParser m, int i |
c2.getTarget() = m and
(
c2.getSyntacticArgument(i) = wvm.getACall() and
DataFlow::localFlow(c2.getResult(0), c.getReceiver())
)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for the brackets either.

Suggested change
not exists(CallNode c2, WithValidMethods wvm, NewParser m, int i |
c2.getTarget() = m and
(
c2.getSyntacticArgument(i) = wvm.getACall() and
DataFlow::localFlow(c2.getResult(0), c.getReceiver())
)
)
not exists(CallNode c2, WithValidMethods wvm, NewParser m |
c2.getTarget() = m and
c2.getASyntacticArgument() = wvm.getACall() and
DataFlow::localFlow(c2.getResult(0), c.getReceiver())
)

Comment on lines +56 to +60
or
exists(FunctionName fn |
c.getCall().getArgument(1) = fn and
fn.toString() = f.getARead().asExpr().getEnclosingFunction().getName()
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Please update your tests so that something fails when these lines are commented out. (Or, if these lines aren't working at the moment, update the tests so that they fail and will pass when these lines work.) I can then easily see what they're meant to be targeting and suggest how to implement them without using toString.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants