Skip to content

Commit 1c79d1f

Browse files
authored
Merge pull request #7352 from github/esbena/atm-endpoint-polish
ATM Endpoint filtering improvements
2 parents 81dedfe + 1949a4e commit 1c79d1f

File tree

7 files changed

+149
-4
lines changed

7 files changed

+149
-4
lines changed

javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/CoreKnowledge.qll

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ predicate isOtherModeledArgument(DataFlow::Node n, FilteringReason reason) {
157157
any(LodashUnderscore::Member m).getACall().getAnArgument() = n and
158158
reason instanceof LodashUnderscoreArgumentReason
159159
or
160+
any(JQuery::MethodCall m).getAnArgument() = n and
161+
reason instanceof JQueryArgumentReason
162+
or
160163
exists(ClientRequest r |
161164
r.getAnArgument() = n or n = r.getUrl() or n = r.getHost() or n = r.getADataNode()
162165
) and
@@ -197,12 +200,23 @@ predicate isOtherModeledArgument(DataFlow::Node n, FilteringReason reason) {
197200
or
198201
call instanceof FileSystemAccess and reason instanceof FileSystemAccessReason
199202
or
200-
call instanceof DatabaseAccess and reason instanceof DatabaseAccessReason
203+
// TODO database accesses are less well defined than database query sinks, so this may cover unmodeled sinks on existing database models
204+
[
205+
call, call.getAMethodCall()
206+
/* command pattern where the query is built, and then exec'ed later */ ] instanceof
207+
DatabaseAccess and
208+
reason instanceof DatabaseAccessReason
201209
or
202210
call = DOM::domValueRef() and reason instanceof DOMReason
203211
or
204212
call.getCalleeName() = "next" and
205213
exists(DataFlow::FunctionNode f | call = f.getLastParameter().getACall()) and
206214
reason instanceof NextFunctionCallReason
215+
or
216+
call = DataFlow::globalVarRef("dojo").getAPropertyRead("require").getACall() and
217+
reason instanceof DojoRequireReason
207218
)
219+
or
220+
(exists(Base64::Decode d | n = d.getInput()) or exists(Base64::Encode d | n = d.getInput())) and
221+
reason instanceof Base64ManipulationReason
208222
}

javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/FilteringReasons.qll

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ newtype TFilteringReason =
2929
TArgumentToArrayReason() or
3030
TArgumentToBuiltinGlobalVarRefReason() or
3131
TConstantReceiverReason() or
32-
TBuiltinCallNameReason()
32+
TBuiltinCallNameReason() or
33+
TBase64ManipulationReason() or
34+
TJQueryArgumentReason() or
35+
TDojoRequireReason()
3336

3437
/** A reason why a particular endpoint was filtered out by the endpoint filters. */
3538
abstract class FilteringReason extends TFilteringReason {
@@ -194,3 +197,21 @@ class BuiltinCallNameReason extends NotASinkReason, TBuiltinCallNameReason {
194197

195198
override int getEncoding() { result = 27 }
196199
}
200+
201+
class Base64ManipulationReason extends NotASinkReason, TBase64ManipulationReason {
202+
override string getDescription() { result = "Base64Manipulation" }
203+
204+
override int getEncoding() { result = 28 }
205+
}
206+
207+
class JQueryArgumentReason extends NotASinkReason, TJQueryArgumentReason {
208+
override string getDescription() { result = "JQueryArgument" }
209+
210+
override int getEncoding() { result = 29 }
211+
}
212+
213+
class DojoRequireReason extends NotASinkReason, TDojoRequireReason {
214+
override string getDescription() { result = "DojoRequire" }
215+
216+
override int getEncoding() { result = 30 }
217+
}

javascript/ql/lib/semmle/javascript/frameworks/ExpressModules.qll

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,30 @@ module ExpressLibraries {
226226
predicate producesUserControlledObjects() { isJson() or isExtendedUrlEncoded() }
227227
}
228228
}
229+
230+
/**
231+
* Provides classes for working with the `express-fileupload` package (https://github.com/richardgirges/express-fileupload);
232+
*/
233+
module FileUpload {
234+
/** Gets a data flow node referring to `req.files`. */
235+
private DataFlow::SourceNode filesRef(Express::RequestSource req, DataFlow::TypeTracker t) {
236+
t.start() and
237+
result = req.ref().getAPropertyRead("files")
238+
or
239+
exists(DataFlow::TypeTracker t2 | result = filesRef(req, t2).track(t2, t))
240+
}
241+
242+
/**
243+
* A call to `req.files.<name>.mv`
244+
*/
245+
class Move extends FileSystemWriteAccess, DataFlow::MethodCallNode {
246+
Move() {
247+
exists(DataFlow::moduleImport("express-fileupload")) and
248+
this = filesRef(_, DataFlow::TypeTracker::end()).getAPropertyRead().getAMethodCall("mv")
249+
}
250+
251+
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
252+
253+
override DataFlow::Node getADataNode() { none() }
254+
}
255+
}

javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,14 +618,30 @@ private module Minimongo {
618618
* Provides classes modeling the MarsDB library.
619619
*/
620620
private module MarsDB {
621+
private class MarsDBAccess extends DatabaseAccess {
622+
string method;
623+
624+
MarsDBAccess() {
625+
this =
626+
API::moduleImport("marsdb")
627+
.getMember("Collection")
628+
.getInstance()
629+
.getMember(method)
630+
.getACall()
631+
}
632+
633+
string getMethod() { result = method }
634+
635+
override DataFlow::Node getAQueryArgument() { none() }
636+
}
637+
621638
/** A call to a MarsDB query method. */
622639
private class QueryCall extends DatabaseAccess, API::CallNode {
623640
int queryArgIdx;
624641

625642
QueryCall() {
626643
exists(string m |
627-
this =
628-
API::moduleImport("marsdb").getMember("Collection").getInstance().getMember(m).getACall() and
644+
this.(MarsDBAccess).getMethod() = m and
629645
// implements parts of the Minimongo interface
630646
Minimongo::CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
631647
)
@@ -733,4 +749,37 @@ private module Redis {
733749
)
734750
}
735751
}
752+
753+
/**
754+
* An access to a database through redis
755+
*/
756+
class RedisDatabaseAccess extends DatabaseAccess {
757+
RedisDatabaseAccess() { this = redis().getMember(_).getACall() }
758+
759+
override DataFlow::Node getAQueryArgument() { none() }
760+
}
761+
}
762+
763+
/**
764+
* Provides classes modeling the `ioredis` library.
765+
*
766+
* ```
767+
* import Redis from 'ioredis'
768+
* let client = new Redis(...)
769+
* ```
770+
*/
771+
private module IoRedis {
772+
/**
773+
* Gets an `ioredis` client.
774+
*/
775+
API::Node ioredis() { result = API::moduleImport("ioredis").getInstance() }
776+
777+
/**
778+
* An access to a database through ioredis
779+
*/
780+
class IoRedisDatabaseAccess extends DatabaseAccess {
781+
IoRedisDatabaseAccess() { this = ioredis().getMember(_).getACall() }
782+
783+
override DataFlow::Node getAQueryArgument() { none() }
784+
}
736785
}

javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,12 @@ nodes
15351535
| TaintedPath.js:214:35:214:38 | path |
15361536
| TaintedPath.js:214:35:214:38 | path |
15371537
| TaintedPath.js:214:35:214:38 | path |
1538+
| express.js:8:20:8:32 | req.query.bar |
1539+
| express.js:8:20:8:32 | req.query.bar |
1540+
| express.js:8:20:8:32 | req.query.bar |
1541+
| express.js:8:20:8:32 | req.query.bar |
1542+
| express.js:8:20:8:32 | req.query.bar |
1543+
| express.js:8:20:8:32 | req.query.bar |
15381544
| normalizedPaths.js:11:7:11:27 | path |
15391545
| normalizedPaths.js:11:7:11:27 | path |
15401546
| normalizedPaths.js:11:7:11:27 | path |
@@ -6321,6 +6327,7 @@ edges
63216327
| TaintedPath.js:211:24:211:30 | req.url | TaintedPath.js:211:14:211:37 | url.par ... , true) |
63226328
| TaintedPath.js:211:24:211:30 | req.url | TaintedPath.js:211:14:211:37 | url.par ... , true) |
63236329
| TaintedPath.js:211:24:211:30 | req.url | TaintedPath.js:211:14:211:37 | url.par ... , true) |
6330+
| express.js:8:20:8:32 | req.query.bar | express.js:8:20:8:32 | req.query.bar |
63246331
| normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path |
63256332
| normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path |
63266333
| normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path |
@@ -9638,6 +9645,7 @@ edges
96389645
| TaintedPath.js:212:31:212:34 | path | TaintedPath.js:211:24:211:30 | req.url | TaintedPath.js:212:31:212:34 | path | This path depends on $@. | TaintedPath.js:211:24:211:30 | req.url | a user-provided value |
96399646
| TaintedPath.js:213:45:213:48 | path | TaintedPath.js:211:24:211:30 | req.url | TaintedPath.js:213:45:213:48 | path | This path depends on $@. | TaintedPath.js:211:24:211:30 | req.url | a user-provided value |
96409647
| TaintedPath.js:214:35:214:38 | path | TaintedPath.js:211:24:211:30 | req.url | TaintedPath.js:214:35:214:38 | path | This path depends on $@. | TaintedPath.js:211:24:211:30 | req.url | a user-provided value |
9648+
| express.js:8:20:8:32 | req.query.bar | express.js:8:20:8:32 | req.query.bar | express.js:8:20:8:32 | req.query.bar | This path depends on $@. | express.js:8:20:8:32 | req.query.bar | a user-provided value |
96419649
| normalizedPaths.js:13:19:13:22 | path | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:13:19:13:22 | path | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value |
96429650
| normalizedPaths.js:14:19:14:29 | './' + path | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:14:19:14:29 | './' + path | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value |
96439651
| normalizedPaths.js:15:19:15:38 | path + '/index.html' | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:15:19:15:38 | path + '/index.html' | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value |
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
var express = require("express"),
2+
fileUpload = require("express-fileupload");
3+
4+
let app = express();
5+
app.use(fileUpload());
6+
7+
app.get("/some/path", function (req, res) {
8+
req.files.foo.mv(req.query.bar);
9+
});

javascript/ql/test/query-tests/Security/CWE-089/untyped/DatabaseAccesses.expected

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,23 @@
6363
| pg-promise.js:60:14:60:25 | t.one(query) |
6464
| pg-promise.js:63:17:63:28 | t.one(query) |
6565
| pg-promise.js:64:10:64:21 | t.one(query) |
66+
| redis.js:10:5:10:37 | client. ... value") |
67+
| redis.js:14:9:14:32 | client. ... value") |
68+
| redis.js:15:9:15:36 | client. ... alue"]) |
69+
| redis.js:18:5:18:28 | client. ... value") |
70+
| redis.js:19:5:19:56 | client. ... alue2") |
71+
| redis.js:22:5:23:16 | client\\n ... multi() |
72+
| redis.js:22:5:24:33 | client\\n ... value") |
73+
| redis.js:22:5:25:26 | client\\n ... value") |
74+
| redis.js:22:5:26:17 | client\\n ... et(key) |
75+
| redis.js:22:5:27:42 | client\\n ... s) { }) |
76+
| redis.js:29:5:31:6 | client. ... \\n }) |
77+
| redis.js:30:9:30:35 | newClie ... value") |
78+
| redis.js:32:5:32:22 | client.duplicate() |
79+
| redis.js:32:5:32:40 | client. ... value") |
80+
| redis.js:39:5:39:28 | client. ... value") |
81+
| redis.js:46:18:46:46 | client. ... value") |
82+
| redis.js:49:18:49:47 | client. ... value") |
6683
| socketio.js:11:5:11:54 | db.run( ... ndle}`) |
6784
| tst2.js:7:3:7:62 | sql.que ... ms.id}` |
6885
| tst2.js:9:3:9:85 | new sql ... + "'") |

0 commit comments

Comments
 (0)