Skip to content

Commit 5269933

Browse files
committed
JS: Port missing rate limiting query
1 parent 389a3c9 commit 5269933

File tree

4 files changed

+42
-21
lines changed

4 files changed

+42
-21
lines changed

javascript/ql/lib/semmle/javascript/security/dataflow/MissingRateLimiting.qll

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ private import semmle.javascript.frameworks.ConnectExpressShared::ConnectExpress
3030
* A route handler that should be rate-limited.
3131
*/
3232
abstract class ExpensiveRouteHandler extends DataFlow::Node {
33-
Express::RouteHandler impl;
34-
35-
ExpensiveRouteHandler() { this = impl }
36-
3733
/**
3834
* Holds if `explanation` is a string explaining why this route handler should be rate-limited.
3935
*
@@ -45,9 +41,15 @@ abstract class ExpensiveRouteHandler extends DataFlow::Node {
4541
}
4642

4743
/**
48-
* A route handler expression that is rate limited.
44+
* DEPRECATED. Use `RateLimitingMiddleware` instead.
45+
*
46+
* A route handler expression that is guarded by a rate limiter.
4947
*/
50-
abstract class RateLimitedRouteHandlerExpr extends Express::RouteHandlerExpr { }
48+
deprecated class RateLimitedRouteHandlerExpr extends Express::RouteHandlerExpr {
49+
RateLimitedRouteHandlerExpr() {
50+
Routing::getNode(flow()).isGuardedBy(any(RateLimitingMiddleware m))
51+
}
52+
}
5153

5254
// default implementations
5355
/**
@@ -106,11 +108,13 @@ class DatabaseAccessAsExpensiveAction extends ExpensiveAction {
106108
}
107109

108110
/**
111+
* DEPRECATED. Use the `Routing::Node` API instead.
112+
*
109113
* A route handler expression that is rate-limited by a rate-limiting middleware.
110114
*/
111-
class RouteHandlerExpressionWithRateLimiter extends RateLimitedRouteHandlerExpr {
115+
deprecated class RouteHandlerExpressionWithRateLimiter extends Expr {
112116
RouteHandlerExpressionWithRateLimiter() {
113-
any(RateLimitingMiddleware m).ref().flowsToExpr(this.getAMatchingAncestor())
117+
Routing::getNode(this.flow()).isGuardedBy(any(RateLimitingMiddleware m))
114118
}
115119
}
116120

@@ -139,6 +143,9 @@ abstract class RateLimitingMiddleware extends DataFlow::SourceNode {
139143

140144
/** Gets a data flow node referring to this middleware. */
141145
DataFlow::SourceNode ref() { result = this.ref(DataFlow::TypeTracker::end()) }
146+
147+
/** Gets a routing node corresponding to this middleware function. */
148+
Routing::Node getRoutingNode() { result = Routing::getNode(this) }
142149
}
143150

144151
/**
@@ -160,12 +167,21 @@ class BruteForceRateLimit extends RateLimitingMiddleware {
160167
}
161168

162169
/**
163-
* A route handler expression that is rate-limited by the `express-limiter` package.
170+
* A rate limiter constructed using the `express-limiter` package.
171+
*
172+
* Note that the `express-limiter` package is unusual in that it may optionally install itself as a middleware.
173+
* That aspect is handled by the Express core model.
164174
*/
165-
class RouteHandlerLimitedByExpressLimiter extends RateLimitedRouteHandlerExpr {
175+
class RouteHandlerLimitedByExpressLimiter extends RateLimitingMiddleware {
166176
RouteHandlerLimitedByExpressLimiter() {
167-
API::moduleImport("express-limiter").getParameter(0).getARhs().getALocalSource().asExpr() =
168-
this.getSetup().getRouter()
177+
this = API::moduleImport("express-limiter").getReturn().getReturn().getAnImmediateUse()
178+
}
179+
180+
override Routing::Node getRoutingNode() {
181+
result = super.getRoutingNode()
182+
or
183+
// express-limiter can perform its own route setup
184+
result = Routing::getRouteSetupNode(this)
169185
}
170186
}
171187

@@ -206,3 +222,7 @@ class RateLimiterFlexibleRateLimiter extends DataFlow::FunctionNode {
206222
class RouteHandlerLimitedByRateLimiterFlexible extends RateLimitingMiddleware {
207223
RouteHandlerLimitedByRateLimiterFlexible() { this instanceof RateLimiterFlexibleRateLimiter }
208224
}
225+
226+
private class FastifyRateLimiter extends RateLimitingMiddleware {
227+
FastifyRateLimiter() { this = DataFlow::moduleImport("fastify-rate-limit") }
228+
}

javascript/ql/src/Security/CWE-770/MissingRateLimiting.ql

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* restricting the rate at which operations can be carried out is vulnerable
55
* to denial-of-service attacks.
66
* @kind problem
7-
* @problem.severity error
7+
* @problem.severity warning
88
* @security-severity 7.5
99
* @precision high
1010
* @id js/missing-rate-limiting
@@ -18,12 +18,13 @@ import javascript
1818
import semmle.javascript.security.dataflow.MissingRateLimiting
1919
import semmle.javascript.RestrictedLocations
2020

21+
// TODO: Previously we used (FirstLineOf) here, but that doesn't work anymore.
2122
from
22-
ExpensiveRouteHandler r, Express::RouteHandlerExpr rhe, string explanation,
23-
DataFlow::Node reference, string referenceLabel
23+
Routing::Node useSite, ExpensiveRouteHandler r, string explanation, DataFlow::Node reference,
24+
string referenceLabel
2425
where
25-
r = rhe.getBody() and
26+
useSite = Routing::getNode(r).getAUseSite() and
2627
r.explain(explanation, reference, referenceLabel) and
27-
not rhe instanceof RateLimitedRouteHandlerExpr
28-
select rhe.(FirstLineOf), "This route handler " + explanation + ", but is not rate-limited.",
29-
reference, referenceLabel
28+
not useSite.isGuardedByNode(any(RateLimitingMiddleware m).getRoutingNode())
29+
select useSite, "This route handler " + explanation + ", but is not rate-limited.", reference,
30+
referenceLabel

javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimiting.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
| MissingRateLimiting.js:4:19:4:38 | functio ... ath);\\n} | This route handler performs $@, but is not rate-limited. | MissingRateLimiting.js:7:5:7:22 | res.sendFile(path) | a file system access |
1+
| MissingRateLimiting.js:4:19:8:1 | functio ... ath);\\n} | This route handler performs $@, but is not rate-limited. | MissingRateLimiting.js:7:5:7:22 | res.sendFile(path) | a file system access |
22
| MissingRateLimiting.js:25:19:25:20 | f1 | This route handler performs $@, but is not rate-limited. | MissingRateLimiting.js:13:5:13:22 | res.sendFile(path) | a file system access |
33
| MissingRateLimiting.js:25:27:25:28 | f3 | This route handler performs $@, but is not rate-limited. | MissingRateLimiting.js:22:5:22:22 | res.sendFile(path) | a file system access |
44
| tst.js:22:24:22:40 | expensiveHandler1 | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |

javascript/ql/test/query-tests/Security/CWE-770/tst.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ app2.get('/:path', bruteforce.prevent, expensiveHandler1); // OK
5858

5959
// rate limiting using express-limiter
6060
var app3 = express();
61-
var limiter = require('express-limiter')(app3);
61+
require('express-limiter')(app3)({ method: 'get', path: '/' });
6262
app3.get('/:path', expensiveHandler1); // OK
6363

6464
express().get('/:path', function(req, res) { verifyUser(req); }); // NOT OK

0 commit comments

Comments
 (0)