Skip to content

Commit db2e27a

Browse files
committed
Add cds utils modelling
and test for model
1 parent 881e066 commit db2e27a

File tree

5 files changed

+198
-1
lines changed

5 files changed

+198
-1
lines changed

javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/CDS.qll

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,3 +1040,81 @@ class CdsQlCall extends CqlClauseParserCall {
10401040
)
10411041
}
10421042
}
1043+
1044+
/**
1045+
* Exported functions from CAP `cds.utils`.
1046+
* Functions described from:
1047+
* https://www.npmjs.com/package/@sap/cds?activeTab=code
1048+
*/
1049+
module CdsUtils {
1050+
/**
1051+
* An access to the `utils` module on a CDS facade.
1052+
*/
1053+
class CdsUtilsModuleAccess extends API::Node {
1054+
CdsUtilsModuleAccess() { exists(CdsFacade cds | this = cds.getMember("utils")) }
1055+
1056+
//additional flow steps
1057+
DataFlow::CallNode getThroughCall() {
1058+
result =
1059+
this.getMember(["decodeURI", "decodeURIComponent", "local", "isdir", "isfile"]).getACall()
1060+
}
1061+
1062+
//sinks
1063+
DataFlow::CallNode getSingleArgCalls() {
1064+
result =
1065+
this.getMember(["find", "stat", "read", "readdir", "mkdirp", "rmdir", "rimraf", "rm"])
1066+
.getACall()
1067+
}
1068+
1069+
DataFlow::CallNode getCopyWriteCall() {
1070+
result = this.getMember(["append", "copy", "write"]).getACall()
1071+
}
1072+
}
1073+
1074+
abstract class UtilsSink extends DataFlow::Node { }
1075+
1076+
abstract class UtilsExtraFlow extends DataFlow::Node { }
1077+
1078+
/**
1079+
* This represents both the data and the filename in calls as follows:
1080+
* ```javascript
1081+
* await write ({foo:'bar'}) .to ('some','file.json')
1082+
* ```
1083+
* sinks in this example are:
1084+
* ```javascript
1085+
* {foo:'bar'}
1086+
* 'some'
1087+
* 'file.json'
1088+
* ```
1089+
*/
1090+
class SrcDstSink extends UtilsSink {
1091+
SrcDstSink() {
1092+
this = copyWriteUtils().(DataFlow::CallNode).getAnArgument() or
1093+
this = copyWriteUtils().getAMemberCall("to").getAnArgument()
1094+
}
1095+
}
1096+
1097+
/**
1098+
* This represents arguments to calls where the argument represents a path. e.g.
1099+
* ```javascript
1100+
* await rimraf('dist','db','data')
1101+
* ```
1102+
*/
1103+
class SimpleSinks extends UtilsSink {
1104+
SimpleSinks() { this = singleArgCallsUtils().(DataFlow::CallNode).getAnArgument() }
1105+
}
1106+
1107+
/**
1108+
* This represents calls where the taint flows through the call. e.g.
1109+
* ```javascript
1110+
* let dir = isdir ('app')
1111+
* ```
1112+
*/
1113+
class SimpleAdditionalFlowStep extends UtilsExtraFlow {
1114+
SimpleAdditionalFlowStep() { this = singleArgAdditionalFlowUtils() }
1115+
1116+
DataFlow::CallNode getOutgoingNode() { result = this }
1117+
1118+
DataFlow::Node getIngoingNode() { result = this.(DataFlow::CallNode).getAnArgument() }
1119+
}
1120+
}

javascript/frameworks/cap/lib/advanced_security/javascript/frameworks/cap/TypeTrackers.qll

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,33 @@ private SourceNode cdsApplicationServiceInstantiation(TypeTracker t) {
3838

3939
SourceNode cdsApplicationServiceInstantiation() {
4040
result = cdsApplicationServiceInstantiation(TypeTracker::end())
41-
}
41+
}
42+
43+
SourceNode copyWriteUtils(TypeTracker t) {
44+
t.start() and
45+
exists(CdsUtils::CdsUtilsModuleAccess mod | result = mod.getCopyWriteCall())
46+
or
47+
exists(TypeTracker t2 | result = copyWriteUtils(t2).track(t2, t))
48+
}
49+
50+
SourceNode copyWriteUtils() { result = copyWriteUtils(TypeTracker::end()) }
51+
52+
SourceNode singleArgCallsUtils(TypeTracker t) {
53+
t.start() and
54+
exists(CdsUtils::CdsUtilsModuleAccess mod | result = mod.getSingleArgCalls())
55+
or
56+
exists(TypeTracker t2 | result = singleArgCallsUtils(t2).track(t2, t))
57+
}
58+
59+
SourceNode singleArgCallsUtils() { result = singleArgCallsUtils(TypeTracker::end()) }
60+
61+
SourceNode singleArgAdditionalFlowUtils(TypeTracker t) {
62+
t.start() and
63+
exists(CdsUtils::CdsUtilsModuleAccess mod | result = mod.getThroughCall())
64+
or
65+
exists(TypeTracker t2 | result = singleArgAdditionalFlowUtils(t2).track(t2, t))
66+
}
67+
68+
SourceNode singleArgAdditionalFlowUtils() {
69+
result = singleArgAdditionalFlowUtils(TypeTracker::end())
70+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
| utils.js:5:11:5:31 | decodeU ... %A4%A") | decodeU ... %A4%A"): additional flow step |
2+
| utils.js:7:12:7:41 | decodeU ... %A4%A") | decodeU ... %A4%A"): additional flow step |
3+
| utils.js:9:12:9:28 | local("%E0%A4%A") | local("%E0%A4%A"): additional flow step |
4+
| utils.js:13:11:13:22 | isdir('app') | isdir('app'): additional flow step |
5+
| utils.js:15:12:15:33 | isfile( ... .json') | isfile( ... .json'): additional flow step |
6+
| utils.js:17:22:17:35 | 'package.json' | 'package.json': sink |
7+
| utils.js:19:26:19:39 | 'package.json' | 'package.json': sink |
8+
| utils.js:21:20:21:33 | 'package.json' | 'package.json': sink |
9+
| utils.js:23:20:23:33 | 'package.json' | 'package.json': sink |
10+
| utils.js:25:14:25:22 | 'db/data' | 'db/data': sink |
11+
| utils.js:25:28:25:41 | 'dist/db/data' | 'dist/db/data': sink |
12+
| utils.js:26:14:26:22 | 'db/data' | 'db/data': sink |
13+
| utils.js:26:25:26:38 | 'dist/db/data' | 'dist/db/data': sink |
14+
| utils.js:28:12:28:20 | 'db/data' | 'db/data': sink |
15+
| utils.js:28:26:28:39 | 'dist/db/data' | 'dist/db/data': sink |
16+
| utils.js:29:12:29:20 | 'db/data' | 'db/data': sink |
17+
| utils.js:29:23:29:36 | 'dist/db/data' | 'dist/db/data': sink |
18+
| utils.js:31:13:31:26 | { foo: 'bar' } | { foo: 'bar' }: sink |
19+
| utils.js:31:32:31:47 | 'some/file.json' | 'some/file.json': sink |
20+
| utils.js:32:13:32:28 | 'some/file.json' | 'some/file.json': sink |
21+
| utils.js:32:31:32:44 | { foo: 'bar' } | { foo: 'bar' }: sink |
22+
| utils.js:34:14:34:19 | 'dist' | 'dist': sink |
23+
| utils.js:34:22:34:25 | 'db' | 'db': sink |
24+
| utils.js:34:28:34:33 | 'data' | 'data': sink |
25+
| utils.js:35:14:35:27 | 'dist/db/data' | 'dist/db/data': sink |
26+
| utils.js:37:13:37:18 | 'dist' | 'dist': sink |
27+
| utils.js:37:21:37:24 | 'db' | 'db': sink |
28+
| utils.js:37:27:37:32 | 'data' | 'data': sink |
29+
| utils.js:38:13:38:26 | 'dist/db/data' | 'dist/db/data': sink |
30+
| utils.js:40:14:40:19 | 'dist' | 'dist': sink |
31+
| utils.js:40:22:40:25 | 'db' | 'db': sink |
32+
| utils.js:40:28:40:33 | 'data' | 'data': sink |
33+
| utils.js:41:14:41:27 | 'dist/db/data' | 'dist/db/data': sink |
34+
| utils.js:43:10:43:15 | 'dist' | 'dist': sink |
35+
| utils.js:43:18:43:21 | 'db' | 'db': sink |
36+
| utils.js:43:24:43:29 | 'data' | 'data': sink |
37+
| utils.js:44:10:44:23 | 'dist/db/data' | 'dist/db/data': sink |
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const cds = require("@sap/cds");
2+
3+
const { decodeURI, decodeURIComponent, local, exists, isdir, isfile, read, readdir, append, write, copy, stat, find, mkdirp, rmdir, rimraf, rm } = cds.utils
4+
5+
let uri = decodeURI("%E0%A4%A") // taint step
6+
7+
let uri2 = decodeURIComponent("%E0%A4%A") // taint step
8+
9+
let uri3 = local("%E0%A4%A") // taint step
10+
11+
let uri4 = exists("%E0%A4%A") // NOT a taint step - returns a boolean
12+
13+
let dir = isdir('app') // taint step
14+
15+
let file = isfile('package.json') // taint step
16+
17+
let pkg = await read('package.json') // sink
18+
19+
let pdir = await readdir('package.json') // sink
20+
21+
let s = await stat('package.json') // sink
22+
23+
let f = await find('package.json') // sink
24+
25+
await append('db/data').to('dist/db/data') // sink
26+
await append('db/data', 'dist/db/data') // sink
27+
28+
await copy('db/data').to('dist/db/data') // sink
29+
await copy('db/data', 'dist/db/data') // sink
30+
31+
await write({ foo: 'bar' }).to('some/file.json') // sink
32+
await write('some/file.json', { foo: 'bar' }) // sink
33+
34+
await mkdirp('dist', 'db', 'data') // sink
35+
await mkdirp('dist/db/data') // sink
36+
37+
await rmdir('dist', 'db', 'data') // sink
38+
await rmdir('dist/db/data') // sink
39+
40+
await rimraf('dist', 'db', 'data') // sink
41+
await rimraf('dist/db/data') // sink
42+
43+
await rm('dist', 'db', 'data') // sink
44+
await rm('dist/db/data') // sink
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import javascript
2+
import advanced_security.javascript.frameworks.cap.CDS
3+
4+
from DataFlow::Node node, string str, string strfull
5+
where
6+
node.(CdsUtils::UtilsSink).toString() = str and strfull = str + ": sink"
7+
or
8+
node.(CdsUtils::UtilsExtraFlow).toString() = str and strfull = str + ": additional flow step"
9+
select node, strfull

0 commit comments

Comments
 (0)