Skip to content

Commit 56378eb

Browse files
committed
Refactor utils lib modelling
1 parent dde31f1 commit 56378eb

File tree

5 files changed

+239
-115
lines changed

5 files changed

+239
-115
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/**
2+
* Exported functions from CAP `cds.utils`.
3+
* Functions described from:
4+
* https://www.npmjs.com/package/@sap/cds?activeTab=code
5+
*/
6+
7+
import javascript
8+
import advanced_security.javascript.frameworks.cap.CDS
9+
10+
/**
11+
* An access to the `utils` module on a CDS facade.
12+
*/
13+
class CdsUtilsModuleAccess extends API::Node {
14+
CdsUtilsModuleAccess() { exists(CdsFacade cds | this = cds.getMember("utils")) }
15+
}
16+
17+
class PathConverters extends DataFlow::Node {
18+
PathConverters() {
19+
exists(CdsUtilsModuleAccess utils |
20+
utils.getMember(["decodeURI", "decodeURIComponent", "local"]).getACall() = this
21+
)
22+
}
23+
24+
SourceNode pathConvertersUtils(TypeTracker t) {
25+
t.start() and
26+
result = this
27+
or
28+
exists(TypeTracker t2 | result = pathConvertersUtils(t2).track(t2, t))
29+
}
30+
31+
SourceNode pathConvertersUtils() { result = pathConvertersUtils(TypeTracker::end()) }
32+
33+
DataFlow::Node getPath() { pathConvertersUtils().(DataFlow::CallNode).getAnArgument() = result }
34+
}
35+
36+
class PathPredicates extends DataFlow::Node {
37+
PathPredicates() {
38+
exists(CdsUtilsModuleAccess utils | utils.getMember(["isdir", "isfile"]).getACall() = this)
39+
}
40+
41+
SourceNode pathPredicateUtils(TypeTracker t) {
42+
t.start() and
43+
result = this
44+
or
45+
exists(TypeTracker t2 | result = pathPredicateUtils(t2).track(t2, t))
46+
}
47+
48+
SourceNode pathPredicateUtils() { result = pathPredicateUtils(TypeTracker::end()) }
49+
50+
DataFlow::Node getPath() { pathPredicateUtils().(DataFlow::CallNode).getAnArgument() = result }
51+
}
52+
53+
class DirectoryReaders extends DataFlow::Node {
54+
DirectoryReaders() {
55+
exists(CdsUtilsModuleAccess utils |
56+
utils.getMember(["find", "stat", "readdir"]).getACall() = this
57+
)
58+
}
59+
60+
SourceNode directoryReaderUtils(TypeTracker t) {
61+
t.start() and
62+
result = this
63+
or
64+
exists(TypeTracker t2 | result = directoryReaderUtils(t2).track(t2, t))
65+
}
66+
67+
SourceNode directoryReaderUtils() { result = directoryReaderUtils(TypeTracker::end()) }
68+
69+
DataFlow::Node getPath() { directoryReaderUtils().(DataFlow::CallNode).getAnArgument() = result }
70+
}
71+
72+
class DirectoryWriters extends DataFlow::Node {
73+
DirectoryWriters() {
74+
exists(CdsUtilsModuleAccess utils |
75+
utils.getMember(["mkdirp", "rmdir", "rimraf", "rm"]).getACall() = this
76+
)
77+
}
78+
79+
SourceNode directoryWriterUtils(TypeTracker t) {
80+
t.start() and
81+
result = this
82+
or
83+
exists(TypeTracker t2 | result = directoryWriterUtils(t2).track(t2, t))
84+
}
85+
86+
SourceNode directoryWriterUtils() { result = directoryWriterUtils(TypeTracker::end()) }
87+
88+
DataFlow::Node getPath() { directoryWriterUtils().(DataFlow::CallNode).getAnArgument() = result }
89+
}
90+
91+
class FileReaders extends DataFlow::Node {
92+
FileReaders() { exists(CdsUtilsModuleAccess utils | utils.getMember(["read"]).getACall() = this) }
93+
94+
SourceNode fileReaderUtils(TypeTracker t) {
95+
t.start() and
96+
result = this
97+
or
98+
exists(TypeTracker t2 | result = fileReaderUtils(t2).track(t2, t))
99+
}
100+
101+
SourceNode fileReaderUtils() { result = fileReaderUtils(TypeTracker::end()) }
102+
103+
DataFlow::Node getPath() { fileReaderUtils().(DataFlow::CallNode).getArgument(0) = result }
104+
}
105+
106+
class FileWriters extends DataFlow::Node {
107+
FileWriters() {
108+
exists(CdsUtilsModuleAccess utils | utils.getMember(["append", "write"]).getACall() = this)
109+
}
110+
111+
SourceNode fileWriterUtils(TypeTracker t) {
112+
t.start() and
113+
result = this
114+
or
115+
exists(TypeTracker t2 | result = fileWriterUtils(t2).track(t2, t))
116+
}
117+
118+
SourceNode fileWriterUtils() { result = fileWriterUtils(TypeTracker::end()) }
119+
120+
DataFlow::Node getData() {
121+
exists(DataFlow::CallNode write |
122+
write = fileWriterUtils() and
123+
(
124+
write.getNumArgument() = 1 and
125+
write.getArgument(0) = result
126+
or
127+
write.getNumArgument() = 2 and
128+
write.getArgument(1) = result
129+
)
130+
)
131+
}
132+
133+
DataFlow::Node getPath() {
134+
exists(DataFlow::CallNode write |
135+
write = fileWriterUtils() and
136+
(
137+
write.getAMemberCall("to").getAnArgument() = result
138+
or
139+
write.getNumArgument() = 2 and
140+
write.getArgument(0) = result
141+
)
142+
)
143+
}
144+
}
145+
146+
class FileReaderWriters extends DataFlow::Node {
147+
FileReaderWriters() {
148+
exists(CdsUtilsModuleAccess utils | utils.getMember(["copy"]).getACall() = this)
149+
}
150+
151+
SourceNode fileReaderWriterUtils(TypeTracker t) {
152+
t.start() and
153+
result = this
154+
or
155+
exists(TypeTracker t2 | result = fileReaderWriterUtils(t2).track(t2, t))
156+
}
157+
158+
SourceNode fileReaderWriterUtils() { result = fileReaderWriterUtils(TypeTracker::end()) }
159+
160+
DataFlow::Node getPath() {
161+
exists(DataFlow::CallNode copy |
162+
copy = fileReaderWriterUtils() and
163+
(
164+
copy.getAMemberCall("to").getArgument(_) = result
165+
or
166+
copy.getArgument(_) = result
167+
)
168+
)
169+
}
170+
}
171+
172+
abstract class UtilsSink extends DataFlow::Node { }
173+
174+
abstract class UtilsExtraFlow extends DataFlow::Node { }
175+
176+
/**
177+
* This represents the data in calls as follows:
178+
* ```javascript
179+
* await write ({foo:'bar'}) .to ('some','file.json')
180+
* ```
181+
* sinks in this example are:
182+
* ```javascript
183+
* {foo:'bar'}
184+
* ```
185+
*/
186+
class WrittenData extends UtilsSink {
187+
WrittenData() { exists(FileWriters fw | fw.getData() = this) }
188+
}
189+
190+
/**
191+
* This represents the filepath in calls as follows:
192+
* ```javascript
193+
* await write ({foo:'bar'}) .to ('some','file.json')
194+
* ```
195+
* sinks in this example are:
196+
* ```javascript
197+
* 'some'
198+
* 'file.json'
199+
* ```
200+
*/
201+
class WrittenPath extends UtilsSink {
202+
WrittenPath() {
203+
exists(FileReaders fw | fw.getPath() = this)
204+
or
205+
exists(FileReaderWriters fw | fw.getPath() = this)
206+
or
207+
exists(FileWriters fw | fw.getPath() = this)
208+
or
209+
exists(DirectoryWriters dw | dw.getPath() = this)
210+
or
211+
exists(DirectoryReaders dr | dr.getPath() = this)
212+
}
213+
}
214+
215+
/**
216+
* This represents calls where the taint flows through the call. e.g.
217+
* ```javascript
218+
* let dir = isdir ('app')
219+
* ```
220+
*/
221+
class AdditionalFlowStep extends UtilsExtraFlow {
222+
AdditionalFlowStep() {
223+
exists(PathConverters pc | pc.getPath() = this)
224+
or
225+
exists(PathPredicates pr | pr.getPath() = this)
226+
}
227+
228+
DataFlow::CallNode getOutgoingNode() { result = this }
229+
230+
DataFlow::Node getIngoingNode() { result = this.(DataFlow::CallNode).getAnArgument() }
231+
}

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

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,81 +1040,3 @@ 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: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,32 +39,3 @@ private SourceNode cdsApplicationServiceInstantiation(TypeTracker t) {
3939
SourceNode cdsApplicationServiceInstantiation() {
4040
result = cdsApplicationServiceInstantiation(TypeTracker::end())
4141
}
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-
}

javascript/frameworks/cap/test/models/cds/utils/utils.expected

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
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 |
1+
| utils.js:5:21:5:30 | "%E0%A4%A" | "%E0%A4%A": additional flow step |
2+
| utils.js:7:31:7:40 | "%E0%A4%A" | "%E0%A4%A": additional flow step |
3+
| utils.js:9:18:9:27 | "%E0%A4%A" | "%E0%A4%A": additional flow step |
4+
| utils.js:13:17:13:21 | 'app' | 'app': additional flow step |
5+
| utils.js:15:19:15:32 | 'package.json' | 'package.json': additional flow step |
66
| utils.js:17:22:17:35 | 'package.json' | 'package.json': sink |
77
| utils.js:19:26:19:39 | 'package.json' | 'package.json': sink |
88
| utils.js:21:20:21:33 | 'package.json' | 'package.json': sink |
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import javascript
2-
import advanced_security.javascript.frameworks.cap.CDS
2+
import advanced_security.javascript.frameworks.cap.CAPPathInjectionQuery
33

44
from DataFlow::Node node, string str, string strfull
55
where
6-
node.(CdsUtils::UtilsSink).toString() = str and strfull = str + ": sink"
6+
node.(UtilsSink).toString() = str and strfull = str + ": sink"
77
or
8-
node.(CdsUtils::UtilsExtraFlow).toString() = str and strfull = str + ": additional flow step"
8+
node.(UtilsExtraFlow).toString() = str and strfull = str + ": additional flow step"
99
select node, strfull

0 commit comments

Comments
 (0)