Skip to content

Commit 65b9774

Browse files
committed
V1
1 parent 9fc28d5 commit 65b9774

File tree

6 files changed

+259
-0
lines changed

6 files changed

+259
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const express = require('express')
2+
const jwtJsonwebtoken = require("jsonwebtoken");
3+
const jose = require("jose");
4+
const jwt_simple = require("jwt-simple");
5+
const app = express()
6+
const port = 3000
7+
8+
function getSecret() {
9+
return "secret"
10+
}
11+
12+
async function startSymmetric(token) {
13+
const {payload, protectedHeader} = await jose.jwtVerify(token, new TextEncoder().encode(getSecret()))
14+
return {
15+
payload, protectedHeader
16+
}
17+
}
18+
19+
async function startRSA(token) {
20+
const spki = `-----BEGIN PUBLIC KEY-----
21+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhYOFK2Ocbbpb/zVypi9
22+
SeKiNUqKQH0zTKN1+6fpCTu6ZalGI82s7XK3tan4dJt90ptUPKD2zvxqTzFNfx4H
23+
HHsrYCf2+FMLn1VTJfQazA2BvJqAwcpW1bqRUEty8tS/Yv4hRvWfQPcc2Gc3+/fQ
24+
OOW57zVy+rNoJc744kb30NjQxdGp03J2S3GLQu7oKtSDDPooQHD38PEMNnITf0pj
25+
+KgDPjymkMGoJlO3aKppsjfbt/AH6GGdRghYRLOUwQU+h+ofWHR3lbYiKtXPn5dN
26+
24kiHy61e3VAQ9/YAZlwXC/99GGtw/NpghFAuM4P1JDn0DppJldy3PGFC0GfBCZA
27+
SwIDAQAB
28+
-----END PUBLIC KEY-----`
29+
const publicKey = await jose.importSPKI(spki, 'RS256')
30+
const {payload, protectedHeader} = await jose.jwtVerify(token, publicKey, {
31+
issuer: 'urn:example:issuer',
32+
audience: 'urn:example:audience',
33+
})
34+
console.log(protectedHeader)
35+
console.log(payload)
36+
}
37+
38+
app.get('/', (req, res) => {
39+
const UserToken = req.headers.authorization;
40+
startSymmetric(UserToken).then()
41+
startRSA(UserToken).then()
42+
jwt_simple.decode(UserToken, getSecret());
43+
jwtJsonwebtoken.verify(UserToken, getSecret())
44+
res.send('Hello World!')
45+
})
46+
47+
app.listen(port, () => {
48+
console.log(`Example app listening on port ${port}`)
49+
})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
A JSON Web Token (JWT) is used for authenticating and managing users in an application.
6+
</p>
7+
<p>
8+
Decoding JWTs with a Constant hardcoded secret key can lead to security vulnerabilities.
9+
</p>
10+
11+
</overview>
12+
<recommendation>
13+
14+
<p>
15+
Generate seceret key in application startup with secure randome generators.
16+
</p>
17+
18+
</recommendation>
19+
<example>
20+
21+
<p>
22+
The following code you can see an Example from a popular Library.
23+
</p>
24+
25+
<sample src="Example.go" />
26+
27+
</example>
28+
29+
</qhelp>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* @name Usage of Constant Secret key for decoding JWT
3+
* @description Hardcoded Secrets leakage can lead to authentication or authorization bypass
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id javascript/jwt-hardcodedkey
8+
* @tags security
9+
* experimental
10+
* external/cwe/CWE-321
11+
*/
12+
13+
import javascript
14+
import DataFlow::PathGraph
15+
import DataFlow
16+
17+
class JWTDecodeConfig extends TaintTracking::Configuration {
18+
JWTDecodeConfig() { this = "JWTConfig" }
19+
20+
override predicate isSource(DataFlow::Node source) {
21+
source.asExpr() instanceof StringLiteral or source.asExpr() instanceof TemplateLiteral
22+
}
23+
24+
override predicate isSink(DataFlow::Node sink) {
25+
// any() or
26+
sink = API::moduleImport("jsonwebtoken").getMember(["sign", "verify"]).getParameter(1).asSink() or
27+
sink = API::moduleImport("jose").getMember("jwtVerify").getParameter(1).asSink() or
28+
sink = API::moduleImport("jwt-simple").getMember("decode").getParameter(1).asSink() or
29+
sink = API::moduleImport("next-auth").getParameter(0).getMember("secret").asSink() or
30+
sink = API::moduleImport("koa-jwt").getParameter(0).getMember("secret").asSink() or
31+
sink =
32+
API::moduleImport("express-jwt")
33+
.getMember("expressjwt")
34+
.getParameter(0)
35+
.getMember("secret")
36+
.asSink() or
37+
sink =
38+
API::moduleImport("passport-jwt")
39+
.getMember("Strategy")
40+
.getParameter(0)
41+
.getMember("secretOrKey")
42+
.asSink()
43+
}
44+
45+
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
46+
exists(DataFlow::CallNode n, DataFlow::NewNode nn |
47+
n.getCalleeName() = "encode" and
48+
nn.flowsTo(n.getReceiver()) and
49+
nn.getCalleeName() = "TextEncoder"
50+
|
51+
pred = n.getArgument(0) and
52+
succ = n
53+
)
54+
or
55+
exists(API::Node n | n = API::moduleImport("jose").getMember("importSPKI") |
56+
pred = n.getACall().getArgument(0) and
57+
succ = n.getReturn().getPromised().asSource()
58+
)
59+
or
60+
exists(API::Node n | n = API::moduleImport("jose").getMember("base64url").getMember("decode") |
61+
pred = n.getACall().getArgument(0) and
62+
succ = n.getACall()
63+
)
64+
or
65+
exists(DataFlow::CallNode n | n = DataFlow::globalVarRef("Buffer").getAMemberCall("from") |
66+
pred = n.getArgument(0) and
67+
succ = [n, n.getAChainedMethodCall(["toString", "toJSON"])]
68+
)
69+
}
70+
}
71+
72+
from JWTDecodeConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink
73+
where cfg.hasFlowPath(source, sink)
74+
select sink.getNode(), source, sink, "this $@. is used as a secret key", source.getNode(),
75+
"Constant"
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const express = require('express')
2+
const app = express()
3+
const jwtJsonwebtoken = require('jsonwebtoken');
4+
const {getSecret} = require('./Config.js');
5+
const jwt_decode = require('jwt-decode');
6+
const jwt_simple = require('jwt-simple');
7+
const jose = require('jose')
8+
const port = 3000
9+
10+
async function startSymmetric(token) {
11+
const {payload, protectedHeader} = await jose.jwtVerify(token, new TextEncoder().encode(getSecret()))
12+
return {
13+
payload, protectedHeader
14+
}
15+
}
16+
17+
app.get('/', (req, res) => {
18+
const UserToken = req.headers.authorization;
19+
// BAD: no verification
20+
jwtJsonwebtoken.decode(UserToken)
21+
// GOOD: use verify alone or use as a check,
22+
// sometimes it seems some coders use both for same token
23+
const UserToken2 = req.headers.authorization;
24+
jwtJsonwebtoken.decode(UserToken2)
25+
jwtJsonwebtoken.verify(UserToken2, getSecret())
26+
// jwt-decode
27+
// BAD: no verification
28+
jwt_decode(UserToken)
29+
// jose
30+
// BAD: no verification
31+
jose.decodeJwt(UserToken)
32+
// GOOD
33+
startSymmetric(UserToken).then(result => console.log(result))
34+
// jwt-simple
35+
// no verification
36+
jwt_simple.decode(UserToken, getSecret(), true);
37+
// GOOD
38+
jwt_simple.decode(UserToken, getSecret(), false);
39+
jwt_simple.decode(UserToken, getSecret());
40+
res.send('Hello World!')
41+
})
42+
43+
app.listen(port, () => {
44+
console.log(`Example app listening on port ${port}`)
45+
})
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
A JSON Web Token (JWT) is used for authenticating and managing users in an application.
6+
</p>
7+
<p>
8+
Only Decoding JWTs without checking if they have a valid signature or not can lead to security vulnerabilities.
9+
</p>
10+
11+
</overview>
12+
<recommendation>
13+
14+
<p>
15+
Don't use methods that only decode JWT, Instead use methods that verify the signature of JWT.
16+
</p>
17+
18+
</recommendation>
19+
<example>
20+
21+
<p>
22+
The following code you can see an Example from a popular Library.
23+
</p>
24+
25+
<sample src="Example.go" />
26+
27+
</example>
28+
<references>
29+
<li>
30+
<a href="https://www.ghostccamm.com/blog/multi_strapi_vulns/#cve-2023-22893-authentication-bypass-for-aws-cognito-login-provider-in-strapi-versions-456">JWT claim had not been verified</a>
31+
</li>
32+
</references>
33+
34+
</qhelp>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* @name JWT missing secret or public key verification
3+
* @description The application does not verify the JWT payload with a cryptographic secret or public key.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @security-severity 7.0
7+
* @precision high
8+
* @id js/jwt-missing-verification
9+
* @tags security
10+
* external/cwe/cwe-347
11+
*/
12+
13+
import javascript
14+
15+
from DataFlow::Node sink
16+
where
17+
sink = API::moduleImport("jsonwebtoken").getMember("decode").getParameter(0).asSink()
18+
or
19+
sink = API::moduleImport("jwt-decode").getParameter(0).asSink()
20+
or
21+
sink = API::moduleImport("jose").getMember("decodeJwt").getParameter(0).asSink()
22+
or
23+
exists(API::Node n | n = API::moduleImport("jwt-simple").getMember("decode") |
24+
n.getParameter(2).asSink().asExpr() = any(BoolLiteral b | b.getBoolValue() = true) and
25+
sink = n.getParameter(0).asSink()
26+
)
27+
select sink, "This Token is Decoded in without signature validatoin"

0 commit comments

Comments
 (0)