Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Add query for detecting empty JWT token secrets#458

Closed
mrthankyou wants to merge 5 commits into
github:mainfrom
mrthankyou:jwt-weak-secret-query
Closed

Add query for detecting empty JWT token secrets#458
mrthankyou wants to merge 5 commits into
github:mainfrom
mrthankyou:jwt-weak-secret-query

Conversation

@mrthankyou
Copy link
Copy Markdown

@mrthankyou mrthankyou commented Jan 20, 2021

What Does This PR Do

When signing JWT tokens, it's important to have strong secrets. This "secret" is used to encrypt the sensitive data encoded into the JWT token. With a strong secret, JWT tokens can't easily be forged. However, if the secret is an empty string, this makes it trivially easy for a third-party actor to forget JWT tokens that will be accepted by the application as real JWT tokens.

This PR introduces a query that looks for instances of passing an empty string into jwt-go's SignedString function. If an empty string is passed into SignedString, then a JWT token will be created that can be easily forged by a third-party actor.

Anything else we should know

  • This will hopefully be the first query reviewed by the CodeQL team so thank you for your patience.
  • I want to write tests for this, even though I know this is going into experimental section. However I don't know how to write tests yet. Once this query has been reviewed and I'll cross that bridge then.
  • I have not looked to see if any real-world projects have this vulnerability. I'm still new to CodeQL so please bear with me on that.
  • There has been some effort by the security research community to document known JWT secrets. That can be found here. I don't think this is pertinent for a CodeQL query (or is it?) so I've left that out of this query.
  • I've looked through a variety of Go JWT libraries and can't find any other libraries that are as popular as jwt-go. For now this query focuses on just jwt-go.
  • Finally, the query checks for empty strings. I'm wondering if the query should extend itself to check for minimum length in a secret. For now I've left that out.

@mrthankyou mrthankyou changed the title Go: Add query for detecting empty JWT token secrets [CWE-347] Add query for detecting empty JWT token secrets Jan 20, 2021
@mrthankyou
Copy link
Copy Markdown
Author

This is not a CWE-347 vulnerability. I need to research the appropriate CWE for this PR. Will update this PR as soon as I find the right CWE.

@mrthankyou mrthankyou changed the title [CWE-347] Add query for detecting empty JWT token secrets Add query for detecting empty JWT token secrets Jan 20, 2021
@mrthankyou
Copy link
Copy Markdown
Author

I believe this is a CWE-327 vulnerability. If that's what your team agrees with I'll go ahead and make the change in the PR. No need to do that just yet if proposed updated CWE designation is incorrect. Sorry for the confusion.

@mrthankyou
Copy link
Copy Markdown
Author

mrthankyou commented Jan 21, 2021

In my haste I have misunderstood this vulnerability. I'm going to update the qhelp file and PR description.

The misunderstanding is that when a secret that is an empty string, manipulated JWT tokens can be easily forged. This has nothing to do with keeping the JWT contents secret. Again my apologizes.

UPDATE: I've updated both the PR description and qhelp file.

Copy link
Copy Markdown
Contributor

@smowton smowton left a comment

Choose a reason for hiding this comment

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

Notes:

  • Sink should be an argument, not the function itself
  • Source should be the empty string expression itself, not some intermediate point on the flow path.

Comment on lines +6 to +9
exists(DataFlow::CallNode call |
call.getTarget().getPackage().getPath() = "github.com/dgrijalva/jwt-go" and
this.(DataFlow::MethodCallNode).getCalleeName() = "SignedString"
)
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.

Suggested change
exists(DataFlow::CallNode call |
call.getTarget().getPackage().getPath() = "github.com/dgrijalva/jwt-go" and
this.(DataFlow::MethodCallNode).getCalleeName() = "SignedString"
)
exists(DataFlow::MethodCallNode call |
call.getTarget().hasQualifiedName("github.com/dgrijalva/jwt-go", "Token", "SignedString") |
this = call.getArgument(0)
)


class JWTWeakSecretKeyVerificationSource extends DataFlow::Node {
JWTWeakSecretKeyVerificationSource() {
this.(DataFlow::MethodCallNode).getArgument(0).getAPredecessor*().getStringValue().length() = 0
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.

Suggested change
this.(DataFlow::MethodCallNode).getArgument(0).getAPredecessor*().getStringValue().length() = 0
this.getStringValue().length() = 0

@smowton
Copy link
Copy Markdown
Contributor

smowton commented Jan 21, 2021

CWEs: this is kind of CWE-330 (insufficiently-random value), in that it's fixed when it should be generated from a cryptographically secure RNG!

To get started writing tests: add a corresponding ql/test/experimental/... directory, create a .qlref file pointing to this .ql file (see other .qlref files to see how they look), use https://github.com/github/depstubber to create a stub of jwt-go, write .go files with examples that should be broken and clean, run the tests to generate .actual files showing the real results of the query, and once they seem about right rename them to .expected, indicating the intended outcome of the test.

@mrthankyou
Copy link
Copy Markdown
Author

Sorry for the delay here. It's still going to take some time for me to get the testing working on my end.

I have not been able to find any repositories yet that are affected by this vulnerability.

@mrthankyou
Copy link
Copy Markdown
Author

I am sorry for dropping the ball on this. I am starting work on this now.

@intrigus-lgtm
Copy link
Copy Markdown
Contributor

This query is "unfortunately" only useful, because the go library did NOT follow the spec.
(If the library had implemented the spec correctly, this query would not have been necessary)

Per the JWT spec, these keys should NOT even be accepted...
E.g. see https://tools.ietf.org/html/rfc7518#section-3.2

A key of the same size as the hash output (for instance, 256 bits for
"HS256") or larger MUST be used with this algorithm.

or this issue in a JWT library for Java:
jwtk/jjwt#334

So ideally one should report this to the authors of the go library.
But looking at the library it seems to have been abandoned so ¯_(ツ)_/¯

@mrthankyou
Copy link
Copy Markdown
Author

@smowton,

I have the stub file generated but am unsure of where I should place the stub file. query-tests/Security makes the most sense but because it's an experimental query, should it go here or somewhere else?

@gagliardetto
Copy link
Copy Markdown
Contributor

gagliardetto commented Apr 28, 2021 via email

@sauyon
Copy link
Copy Markdown
Contributor

sauyon commented Aug 12, 2021

Closing this for now due to lack of activity; feel free to reopen if you want to get back to this.

@sauyon sauyon closed this Aug 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants