-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
JS: decoding JWT without signature verification #14088
Conversation
… results now, ConstantString is suggested as a better alternative for finding constant sources
No-verification query should be under CWE-347. |
… pkg, create a path query for jsonwebtoken package which is not work correctly
javascript/ql/src/Security/CWE-347-noVerification/JsonWebToken.ql
Outdated
Show resolved
Hide resolved
…th-problem, update jsonwebtoken source and sinks
@JarLob I created one query file for each JWT package, but these all can be merged in some cases where the JWT is decoded in an unsafe way by package1 and then the signature has been validated by package2. it is easy to merge all these together because all have the same template ( only two |
Hi @JarLob |
javascript/ql/src/Security/CWE-347-noVerification/JsonWebToken.ql
Outdated
Show resolved
Hide resolved
javascript/ql/src/Security/CWE-347-noVerification/JsonWebTokenNotWorking.ql
Outdated
Show resolved
Hide resolved
javascript/ql/test/query-tests/Security/CWE-347-noVerification/JsonWebTokenNotWorking.expected
Outdated
Show resolved
Hide resolved
@am0o0 Could you please change the pull request from draft to ready for review, so someone from JavaScript CodeQL team can take a look? |
First you need to move everything into the I'll do a review after you've done that. |
QHelp previews: javascript/ql/src/experimental/Security/CWE-347/decodeJwtWithoutVerification.qhelpJWT missing secret or public key verificationA JSON Web Token (JWT) is used for authenticating and managing users in an application. Only Decoding JWTs without checking if they have a valid signature or not can lead to security vulnerabilities. RecommendationDon't use methods that only decode JWT, Instead use methods that verify the signature of JWT. ExampleIn the following code, you can see the proper usage of the most popular JWT libraries. const express = require('express')
const app = express()
const jwtJsonwebtoken = require('jsonwebtoken');
const jwt_decode = require('jwt-decode');
const jwt_simple = require('jwt-simple');
const jose = require('jose')
const port = 3000
function getSecret() {
return "A Safe generated random key"
}
app.get('/jose1', async (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: with signature verification
await jose.jwtVerify(UserToken, new TextEncoder().encode(getSecret()))
})
app.get('/jose2', async (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: first without signature verification then with signature verification for same UserToken
jose.decodeJwt(UserToken)
await jose.jwtVerify(UserToken, new TextEncoder().encode(getSecret()))
})
app.get('/jwtSimple1', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: first without signature verification then with signature verification for same UserToken
jwt_simple.decode(UserToken, getSecret(), false);
jwt_simple.decode(UserToken, getSecret());
})
app.get('/jwtSimple2', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: with signature verification
jwt_simple.decode(UserToken, getSecret(), true);
jwt_simple.decode(UserToken, getSecret());
})
app.get('/jwtJsonwebtoken1', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: with signature verification
jwtJsonwebtoken.verify(UserToken, getSecret())
})
app.get('/jwtJsonwebtoken2', (req, res) => {
const UserToken = req.headers.authorization;
// GOOD: first without signature verification then with signature verification for same UserToken
jwtJsonwebtoken.decode(UserToken)
jwtJsonwebtoken.verify(UserToken, getSecret())
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
}) In the following code, you can see the improper usage of the most popular JWT libraries. const express = require('express')
const app = express()
const jwtJsonwebtoken = require('jsonwebtoken');
const jwt_decode = require('jwt-decode');
const jwt_simple = require('jwt-simple');
const jose = require('jose')
const port = 3000
function getSecret() {
return "A Safe generated random key"
}
app.get('/jose', (req, res) => {
const UserToken = req.headers.authorization;
// BAD: no signature verification
jose.decodeJwt(UserToken)
})
app.get('/jwtDecode', (req, res) => {
const UserToken = req.headers.authorization;
// BAD: no signature verification
jwt_decode(UserToken)
})
app.get('/jwtSimple', (req, res) => {
const UserToken = req.headers.authorization;
// jwt.decode(token, key, noVerify, algorithm)
// BAD: no signature verification
jwt_simple.decode(UserToken, getSecret(), true);
})
app.get('/jwtJsonwebtoken', (req, res) => {
const UserToken = req.headers.authorization;
// BAD: no signature verification
jwtJsonwebtoken.decode(UserToken)
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
}) References
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A first review round.
Primarily: you shouldn't add one new query for each library you're adding a model for, you should just have one query that models all the libraries.
javascript/ql/src/experimental/Security/CWE-347-noVerification/jwtNoVerification.qhelp
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-347-noVerification/JsonWebToken.ql
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-347-noVerification/JsonWebToken.ql
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-347-noVerification/JsonWebTokenLocalSource.ql
Outdated
Show resolved
Hide resolved
@@ -0,0 +1,66 @@ | |||
/** | |||
* @name This query is for seeing if we can have two taint config within on query file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not look like a file you meant to include?
This look like code you used for testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote this file for more clarification about what I wanted to do but I failed to express what I needed to get my query to the right point which I failed. this is what I want:
the same source has a Path to two different sinks, one is a dangerous sink, and the other is a guard sink. if one source has a path to both sinks then the source code doesn't contain any vulnerability, but if the this source only has a path to the dangerous sink then the source code contains a vulnerability and should detect it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I think I know why.
The DataFlow::PathNode
includes which DataFlow::Configuration
it came from.
So you need to check that the DataFlow::Node
that you get from calling .getNode()
on the source is the same between the two "sources", you can't use the DataFlow::PathNode
from one configuration directly in another.
javascript/ql/src/experimental/Security/CWE-347-noVerification/jose.ql
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-347-noVerification/jwtDecode.ql
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-347-noVerification/jwtSimple.ql
Outdated
Show resolved
Hide resolved
@erik-krogh sorry this PR was one of my first PRs and I didn't apply what I learnt from you and your colleagues to this PR. I hope the changes are acceptable now. |
The |
I forgot to update this branch, the tests are still OK. are you sure about failing? |
Yeah, they're passing now 🤷 The I think changing the from-where-select to the below might fix your issue (it's the fix I described further up): from ConfigurationUnverifiedDecode cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where
cfg.hasFlowPath(source, sink) and
not exists(ConfigurationVerifiedDecode cfg2 |
cfg2.hasFlowPath(any(DataFlow::PathNode p | p.getNode() = source.getNode()), _)
)
select source.getNode(), source, sink, "Decoding JWT $@.", sink.getNode(),
"without signature verification" If that works you can use the new approach in your queries. |
…s and separate the local and remote query tests
Thanks. Everything looks good for a merge into Thanks again for the contribution. |
No description provided.