Skip to content

Commit 1f93e3b

Browse files
Merge branch 'main' into jeongsoolee09/port-UI5PathGraph
2 parents 807e6fb + 78e9d85 commit 1f93e3b

File tree

32 files changed

+629
-199
lines changed

32 files changed

+629
-199
lines changed

.github/workflows/javascript.sarif.expected

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
library: true
33
name: advanced-security/javascript-sap-cap-models
4-
version: 2.0.0
4+
version: 2.1.0
55
extensionTargets:
66
codeql/javascript-all: "^2.4.0"

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ class CdsLogger extends MethodCallNode {
2222
string getName() { result = name }
2323
}
2424

25+
/**
26+
* A template literal that is not interpolated. It is basically a string literal
27+
* defined in backticks (\`\`).
28+
*/
2529
class ConstantOnlyTemplateLiteral extends TemplateLiteral {
2630
ConstantOnlyTemplateLiteral() {
2731
forall(Expr e | e = this.getAnElement() | e instanceof TemplateElement)
@@ -51,9 +55,26 @@ module CAPLogInjectionConfiguration implements DataFlow::ConfigSig {
5155
}
5256

5357
predicate isBarrier(DataFlow::Node node) {
58+
/*
59+
* This predicate includes cases such as:
60+
* 1. A CDS entity element lacking a type annotation.
61+
* - Possibly because it relies on a common aspect.
62+
* 2. A CDS entity element annotated with a non-string type listed above.
63+
*
64+
* Therefore, the data held by the handler parameter data (e.g. `req.data.X`)
65+
* has to be EXPLICITLY annotated as `String` or `LargeString` to be excluded
66+
* from the next condition.
67+
*/
68+
5469
exists(HandlerParameterData handlerParameterData |
5570
node = handlerParameterData and
56-
not handlerParameterData.getType() = ["cds.String", "cds.LargeString"]
71+
/* Note the use of `.. != ..` instead of `not .. = ..` below. */
72+
exists(string handlerParameterDataType |
73+
handlerParameterDataType = handlerParameterData.getType()
74+
|
75+
handlerParameterDataType != "cds.String" and
76+
handlerParameterDataType != "cds.LargeString"
77+
)
5778
)
5879
}
5980

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

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@
77
import javascript
88
import advanced_security.javascript.frameworks.cap.CDSUtils
99

10-
abstract class UtilsAccessedPathSink extends DataFlow::Node { }
10+
abstract class UtilsSink extends DataFlow::Node {
11+
abstract string sinkType();
12+
}
1113

12-
abstract class UtilsControlledDataSink extends DataFlow::Node { }
14+
abstract class UtilsAccessedPathSink extends UtilsSink {
15+
override string sinkType() { result = "unrestricted file read" }
16+
}
1317

14-
abstract class UtilsControlledPathSink extends DataFlow::Node { }
18+
abstract class UtilsControlledDataSink extends UtilsSink {
19+
override string sinkType() { result = "tainted data being written to a file" }
20+
}
1521

16-
abstract class UtilsExtraFlow extends DataFlow::Node { }
22+
abstract class UtilsControlledPathSink extends UtilsSink {
23+
override string sinkType() { result = "unrestricted file operations" }
24+
}
1725

1826
/**
1927
* This represents the data in calls as follows:
@@ -67,21 +75,3 @@ class ControlledInputPath extends UtilsControlledPathSink {
6775
exists(DirectoryReaders dr | dr.getPath() = this)
6876
}
6977
}
70-
71-
/**
72-
* This represents calls where the taint flows through the call. e.g.
73-
* ```javascript
74-
* let dir = isdir ('app')
75-
* ```
76-
*/
77-
class AdditionalFlowStep extends UtilsExtraFlow {
78-
AdditionalFlowStep() {
79-
exists(PathConverters pc | pc.getPath() = this)
80-
or
81-
exists(PathPredicates pr | pr.getPath() = this)
82-
}
83-
84-
DataFlow::CallNode getOutgoingNode() { result = this }
85-
86-
DataFlow::Node getIngoingNode() { result = this.(DataFlow::CallNode).getAnArgument() }
87-
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,12 @@ class ServiceInstanceFromConstructor extends ServiceInstance {
184184
* const cds = require("@sap/cds");
185185
* module.exports = class SomeService extends cds.ApplicationService {
186186
* init() {
187-
* this.on("SomeEvent", (req) => { ... } )
187+
* this.on("SomeEvent", (req) => { ... } )
188188
* }
189189
* }
190190
* ```
191191
* This class captures the access to the `this` variable as in `this.on(...)`.
192-
*
192+
*
193193
* e.g.2. Given this code:
194194
* ``` javascript
195195
* const cds = require('@sap/cds');
@@ -424,13 +424,14 @@ class HandlerRegistration extends MethodCallNode {
424424
* this.after("SomeEvent", "SomeEntity", (req, next) => { ... });
425425
* }
426426
* ```
427-
* All parameters named `req` above are captured. Also see `HandlerParameterOfExposedService`
427+
* All parameters named `req` above are captured. Also see
428+
* `RemoteflowSources::HandlerParameterOfExposedService`
428429
* for a subset of this class that is only about handlers exposed to some protocol.
429430
*/
430431
class HandlerParameter extends ParameterNode {
431432
Handler handler;
432433

433-
HandlerParameter() { this = handler.getParameter(0) }
434+
HandlerParameter() { this = isHandlerParameter(handler) }
434435

435436
Handler getHandler() { result = handler }
436437
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,12 @@ private SourceNode cdsApplicationServiceInstantiation(TypeTracker t) {
3939
SourceNode cdsApplicationServiceInstantiation() {
4040
result = cdsApplicationServiceInstantiation(TypeTracker::end())
4141
}
42+
43+
private SourceNode isHandlerParameter(TypeTracker t, Handler handler) {
44+
result = handler.getParameter(0) or
45+
exists(TypeTracker t2 | result = isHandlerParameter(t, handler).track(t2, t))
46+
}
47+
48+
SourceNode isHandlerParameter(Handler handler) {
49+
result = isHandlerParameter(TypeTracker::end(), handler)
50+
}

javascript/frameworks/cap/lib/qlpack.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
library: true
33
name: advanced-security/javascript-sap-cap-all
4-
version: 2.0.0
4+
version: 2.1.0
55
suites: codeql-suites
66
extractor: javascript
77
dependencies:
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# CAP CDS Utils used with user-controlled sources
2+
3+
If a path is constructed from user-provided input without sufficient sanitization, a malicious user may be able to manipulate the contents of the filesystem without proper authorization.
4+
5+
Additionally if user-provided input is used to create file contents this can also result in a malicious user manipulating the filesystem in an unchecked way.
6+
7+
## Recommendation
8+
9+
CAP applications using CDS Utils should not use user-provided input without sanitization.
10+
11+
The sanitization stragety can vary depending on what types of paths are satisfactory as user-provided input. A simple approach to sanitization is to check user-provided input against an allow list. Other potential approaches include checking components of paths or normalizing them to make sure that the path does not escape the expected root folder.
12+
13+
Normalization techniques should be carefully considered and simple naive replacement strategies will not be sufficient, for example replacing any match of a parent directory reference (`../`) in the sample `.../...//` will still result in the path `../` being used which could escape the intended directory.
14+
15+
## Examples
16+
17+
This CAP service directly uses user-provided input to construct a path.
18+
19+
``` javascript
20+
const cds = require("@sap/cds");
21+
const { rm } = cds.utils
22+
23+
module.exports = class Service1 extends cds.ApplicationService {
24+
25+
init() {
26+
this.on("send1", async (req) => {
27+
let userinput = req.data
28+
await rm(userinput, 'db', 'data') // Path injection alert
29+
}
30+
}
31+
}
32+
```
33+
34+
This CAP service directly uses user-provided input to add content to a file.
35+
36+
``` javascript
37+
const cds = require("@sap/cds");
38+
const { rm } = cds.utils
39+
40+
module.exports = class Service1 extends cds.ApplicationService {
41+
init() {
42+
this.on("send1", async (req) => {
43+
let userinput = req.data
44+
await write(userinput).to('db/data') // Path injection alert
45+
46+
// GOOD: the path can not be controlled by an attacker
47+
let allowedDirectories = [
48+
'this-is-a-safe-directory'
49+
];
50+
if (allowedDirectories.includes(userinput)) {
51+
await rm(userinput) // sanitized - No Path injection alert
52+
}
53+
}
54+
}
55+
}
56+
```
57+
58+
## References
59+
60+
- OWASP 2021: [Injection](https://owasp.org/Top10/A03_2021-Injection/).
61+
- SAP CAP CDS Utils : [Documentation](https://cap.cloud.sap/docs/node.js/cds-utils).
62+
- Common Weakness Enumeration: [CWE-020](https://cwe.mitre.org/data/definitions/20.html).
63+
- Common Weakness Enumeration: [CWE-022](https://cwe.mitre.org/data/definitions/22.html).
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @name Use of user controlled input in CAP CDS file system utilities
3+
* @description Using unchecked user controlled values can allow an
4+
* attacker to affect paths constructed and accessed in
5+
* the filesystem.
6+
* @kind path-problem
7+
* @problem.severity warning
8+
* @security-severity 7.5
9+
* @precision medium
10+
* @id js/cap-path-injection
11+
* @tags security
12+
* external/cwe/cwe-020
13+
* external/cwe/cwe-022
14+
*/
15+
16+
import javascript
17+
import advanced_security.javascript.frameworks.cap.CAPPathInjectionQuery
18+
import advanced_security.javascript.frameworks.cap.RemoteFlowSources
19+
private import semmle.javascript.security.dataflow.TaintedPathCustomizations
20+
private import semmle.javascript.security.dataflow.TaintedPathQuery as tq
21+
22+
module PathInjectionConfig implements DataFlow::ConfigSig {
23+
predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
24+
25+
predicate isSink(DataFlow::Node sink) { sink instanceof UtilsSink }
26+
27+
predicate isAdditionalFlowStep(DataFlow::Node nodein, DataFlow::Node nodeout) {
28+
exists(PathConverters pc | pc.getPath() = nodein and nodeout = pc)
29+
or
30+
exists(PathPredicates pr | pr.getPath() = nodein and nodeout = pr)
31+
}
32+
33+
predicate isBarrier(DataFlow::Node node) {
34+
node instanceof TaintedPath::Sanitizer
35+
or
36+
tq::TaintedPathConfig::isBarrier(node)
37+
}
38+
}
39+
40+
module PathInjectionConfigFlow = TaintTracking::Global<PathInjectionConfig>;
41+
42+
import PathInjectionConfigFlow::PathGraph
43+
44+
from PathInjectionConfigFlow::PathNode source, PathInjectionConfigFlow::PathNode sink
45+
where PathInjectionConfigFlow::flowPath(source, sink)
46+
select sink, source, sink,
47+
"This CDS utils usage relies on user-provided value and can result in " +
48+
sink.getNode().(UtilsSink).sinkType() + "."
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
---
22
library: false
33
name: advanced-security/javascript-sap-cap-queries
4-
version: 2.0.0
4+
version: 2.1.0
55
suites: codeql-suites
66
extractor: javascript
77
dependencies:
88
codeql/javascript-all: "^2.4.0"
9-
advanced-security/javascript-sap-cap-models: "^2.0.0"
10-
advanced-security/javascript-sap-cap-all: "^2.0.0"
9+
advanced-security/javascript-sap-cap-models: "^2.1.0"
10+
advanced-security/javascript-sap-cap-all: "^2.1.0"
1111
default-suite-file: codeql-suites/javascript-code-scanning.qls

0 commit comments

Comments
 (0)