Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/javascript.sarif.expected

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@
import javascript
import advanced_security.javascript.frameworks.cap.CDSUtils

abstract class UtilsAccessedPathSink extends DataFlow::Node { }
abstract class UtilsSink extends DataFlow::Node {
abstract string sinkType();
}

abstract class UtilsAccessedPathSink extends UtilsSink {
override string sinkType() { result = "unrestricted file read" }
}

abstract class UtilsControlledDataSink extends DataFlow::Node { }
abstract class UtilsControlledDataSink extends UtilsSink {
override string sinkType() { result = "tainted data being written to a file" }
}

abstract class UtilsControlledPathSink extends DataFlow::Node { }
abstract class UtilsControlledPathSink extends UtilsSink {
override string sinkType() { result = "unrestricted file operations" }
}

abstract class UtilsExtraFlow extends DataFlow::Node { }

Expand Down Expand Up @@ -74,14 +84,16 @@ class ControlledInputPath extends UtilsControlledPathSink {
* let dir = isdir ('app')
* ```
*/
class AdditionalFlowStep extends UtilsExtraFlow {
AdditionalFlowStep() {
exists(PathConverters pc | pc.getPath() = this)
class CDSAdditionalFlowStep extends UtilsExtraFlow {
DataFlow::CallNode outNode;

CDSAdditionalFlowStep() {
exists(PathConverters pc | pc.getPath() = this and outNode = pc)
or
exists(PathPredicates pr | pr.getPath() = this)
exists(PathPredicates pr | pr.getPath() = this and outNode = pr)
}

DataFlow::CallNode getOutgoingNode() { result = this }
DataFlow::CallNode getOutgoingNode() { result = outNode }

DataFlow::Node getIngoingNode() { result = this.(DataFlow::CallNode).getAnArgument() }
DataFlow::Node getIngoingNode() { result = this }
}
51 changes: 51 additions & 0 deletions javascript/frameworks/cap/src/path-traversal/PathInjection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# CAP CDS Utils used with user-controlled sources

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.

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.

## Recommendation

CAP applications using CDS Utils should not use user-provided input without sanitization.

## Examples

This CAP service directly uses user-provided input to construct a path.

``` javascript
const cds = require("@sap/cds");
const { rm } = cds.utils

module.exports = class Service1 extends cds.ApplicationService {

init() {
this.on("send1", async (req) => {
let userinput = req.data
await rm(userinput, 'db', 'data') // Path injection alert
}
}
}
```

This CAP service directly uses user-provided input to add content to a file.

``` javascript
const cds = require("@sap/cds");
const { rm } = cds.utils

module.exports = class Service1 extends cds.ApplicationService {

init() {
this.on("send1", async (req) => {
let userinput = req.data
await write(userinput).to('db/data') // Path injection alert
}
}
}

```

## References

- OWASP 2021: [Injection](https://owasp.org/Top10/A03_2021-Injection/).
- SAP CAP CDS Utils : [Documentation](https://cap.cloud.sap/docs/node.js/cds-utils).
41 changes: 41 additions & 0 deletions javascript/frameworks/cap/src/path-traversal/PathInjection.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @name Use of user controlled input in CAP CDS file system utilies
* @description Using unchecked user controlled values can allow an
* attacker to affect paths constructed and accessed in
* the filesystem.
* @kind path-problem
* @problem.severity warning
* @security-severity 7.5
* @precision medium
* @id js/cap-path-injection
* @tags security
* external/cwe/cwe-020
* external/cwe/cwe-022
*/

import javascript
import advanced_security.javascript.frameworks.cap.CAPPathInjectionQuery
import advanced_security.javascript.frameworks.cap.RemoteFlowSources

module PathInjectionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }

predicate isSink(DataFlow::Node sink) { sink instanceof UtilsSink }

predicate isAdditionalFlowStep(DataFlow::Node nodein, DataFlow::Node nodeout) {
exists(CDSAdditionalFlowStep step |
step.getIngoingNode() = nodein and
step.getOutgoingNode() = nodeout
)
}
}

module PathInjectionConfigFlow = TaintTracking::Global<PathInjectionConfig>;

import PathInjectionConfigFlow::PathGraph

from PathInjectionConfigFlow::PathNode source, PathInjectionConfigFlow::PathNode sink
where PathInjectionConfigFlow::flowPath(source, sink)
select sink, source, sink,
"This CDS utils usage relies on user-provided value and can result in " +
sink.getNode().(UtilsSink).sinkType() + "."
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
edges
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:31:26:31:34 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:33:38:33:46 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:34:24:34:32 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:36:44:36:52 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:38:25:38:33 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:40:26:40:34 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:41:26:41:34 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:43:25:43:33 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:44:25:44:33 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:46:26:46:34 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:47:26:47:34 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:49:22:49:30 | userinput | provenance | |
| pathinjection.js:8:19:8:38 | userinput | pathinjection.js:50:22:50:30 | userinput | provenance | |
| pathinjection.js:8:31:8:38 | req.data | pathinjection.js:8:19:8:38 | userinput | provenance | |
| pathinjection.js:9:19:9:44 | userinputtwo | pathinjection.js:37:25:37:36 | userinputtwo | provenance | |
| pathinjection.js:9:34:9:44 | req.headers | pathinjection.js:9:19:9:44 | userinputtwo | provenance | |
| pathinjection.js:10:19:10:45 | userinputthree | pathinjection.js:12:38:12:51 | userinputthree | provenance | |
| pathinjection.js:10:19:10:45 | userinputthree | pathinjection.js:14:47:14:60 | userinputthree | provenance | |
| pathinjection.js:10:19:10:45 | userinputthree | pathinjection.js:16:34:16:47 | userinputthree | provenance | |
| pathinjection.js:10:19:10:45 | userinputthree | pathinjection.js:18:34:18:47 | userinputthree | provenance | |
| pathinjection.js:10:19:10:45 | userinputthree | pathinjection.js:20:35:20:48 | userinputthree | provenance | |
| pathinjection.js:10:36:10:45 | req.params | pathinjection.js:10:19:10:45 | userinputthree | provenance | |
| pathinjection.js:12:19:12:52 | taint1 | pathinjection.js:22:36:22:41 | taint1 | provenance | |
| pathinjection.js:12:28:12:52 | decodeU ... tthree) | pathinjection.js:12:19:12:52 | taint1 | provenance | |
| pathinjection.js:12:38:12:51 | userinputthree | pathinjection.js:12:28:12:52 | decodeU ... tthree) | provenance | Config |
| pathinjection.js:14:19:14:61 | taint2 | pathinjection.js:24:40:24:45 | taint2 | provenance | |
| pathinjection.js:14:28:14:61 | decodeU ... tthree) | pathinjection.js:14:19:14:61 | taint2 | provenance | |
| pathinjection.js:14:47:14:60 | userinputthree | pathinjection.js:14:28:14:61 | decodeU ... tthree) | provenance | Config |
| pathinjection.js:16:19:16:48 | taint3 | pathinjection.js:26:34:26:39 | taint3 | provenance | |
| pathinjection.js:16:28:16:48 | local(u ... tthree) | pathinjection.js:16:19:16:48 | taint3 | provenance | |
| pathinjection.js:16:34:16:47 | userinputthree | pathinjection.js:16:28:16:48 | local(u ... tthree) | provenance | Config |
| pathinjection.js:18:19:18:48 | taint4 | pathinjection.js:28:34:28:39 | taint4 | provenance | |
| pathinjection.js:18:28:18:48 | isdir(u ... tthree) | pathinjection.js:18:19:18:48 | taint4 | provenance | |
| pathinjection.js:18:34:18:47 | userinputthree | pathinjection.js:18:28:18:48 | isdir(u ... tthree) | provenance | Config |
| pathinjection.js:20:19:20:49 | taint5 | pathinjection.js:30:40:30:45 | taint5 | provenance | |
| pathinjection.js:20:28:20:49 | isfile( ... tthree) | pathinjection.js:20:19:20:49 | taint5 | provenance | |
| pathinjection.js:20:35:20:48 | userinputthree | pathinjection.js:20:28:20:49 | isfile( ... tthree) | provenance | Config |
nodes
| pathinjection.js:8:19:8:38 | userinput | semmle.label | userinput |
| pathinjection.js:8:31:8:38 | req.data | semmle.label | req.data |
| pathinjection.js:9:19:9:44 | userinputtwo | semmle.label | userinputtwo |
| pathinjection.js:9:34:9:44 | req.headers | semmle.label | req.headers |
| pathinjection.js:10:19:10:45 | userinputthree | semmle.label | userinputthree |
| pathinjection.js:10:36:10:45 | req.params | semmle.label | req.params |
| pathinjection.js:12:19:12:52 | taint1 | semmle.label | taint1 |
| pathinjection.js:12:28:12:52 | decodeU ... tthree) | semmle.label | decodeU ... tthree) |
| pathinjection.js:12:38:12:51 | userinputthree | semmle.label | userinputthree |
| pathinjection.js:14:19:14:61 | taint2 | semmle.label | taint2 |
| pathinjection.js:14:28:14:61 | decodeU ... tthree) | semmle.label | decodeU ... tthree) |
| pathinjection.js:14:47:14:60 | userinputthree | semmle.label | userinputthree |
| pathinjection.js:16:19:16:48 | taint3 | semmle.label | taint3 |
| pathinjection.js:16:28:16:48 | local(u ... tthree) | semmle.label | local(u ... tthree) |
| pathinjection.js:16:34:16:47 | userinputthree | semmle.label | userinputthree |
| pathinjection.js:18:19:18:48 | taint4 | semmle.label | taint4 |
| pathinjection.js:18:28:18:48 | isdir(u ... tthree) | semmle.label | isdir(u ... tthree) |
| pathinjection.js:18:34:18:47 | userinputthree | semmle.label | userinputthree |
| pathinjection.js:20:19:20:49 | taint5 | semmle.label | taint5 |
| pathinjection.js:20:28:20:49 | isfile( ... tthree) | semmle.label | isfile( ... tthree) |
| pathinjection.js:20:35:20:48 | userinputthree | semmle.label | userinputthree |
| pathinjection.js:22:36:22:41 | taint1 | semmle.label | taint1 |
| pathinjection.js:24:40:24:45 | taint2 | semmle.label | taint2 |
| pathinjection.js:26:34:26:39 | taint3 | semmle.label | taint3 |
| pathinjection.js:28:34:28:39 | taint4 | semmle.label | taint4 |
| pathinjection.js:30:40:30:45 | taint5 | semmle.label | taint5 |
| pathinjection.js:31:26:31:34 | userinput | semmle.label | userinput |
| pathinjection.js:33:38:33:46 | userinput | semmle.label | userinput |
| pathinjection.js:34:24:34:32 | userinput | semmle.label | userinput |
| pathinjection.js:36:44:36:52 | userinput | semmle.label | userinput |
| pathinjection.js:37:25:37:36 | userinputtwo | semmle.label | userinputtwo |
| pathinjection.js:38:25:38:33 | userinput | semmle.label | userinput |
| pathinjection.js:40:26:40:34 | userinput | semmle.label | userinput |
| pathinjection.js:41:26:41:34 | userinput | semmle.label | userinput |
| pathinjection.js:43:25:43:33 | userinput | semmle.label | userinput |
| pathinjection.js:44:25:44:33 | userinput | semmle.label | userinput |
| pathinjection.js:46:26:46:34 | userinput | semmle.label | userinput |
| pathinjection.js:47:26:47:34 | userinput | semmle.label | userinput |
| pathinjection.js:49:22:49:30 | userinput | semmle.label | userinput |
| pathinjection.js:50:22:50:30 | userinput | semmle.label | userinput |
subpaths
#select
| pathinjection.js:22:36:22:41 | taint1 | pathinjection.js:10:36:10:45 | req.params | pathinjection.js:22:36:22:41 | taint1 | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:24:40:24:45 | taint2 | pathinjection.js:10:36:10:45 | req.params | pathinjection.js:24:40:24:45 | taint2 | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:26:34:26:39 | taint3 | pathinjection.js:10:36:10:45 | req.params | pathinjection.js:26:34:26:39 | taint3 | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:28:34:28:39 | taint4 | pathinjection.js:10:36:10:45 | req.params | pathinjection.js:28:34:28:39 | taint4 | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:30:40:30:45 | taint5 | pathinjection.js:10:36:10:45 | req.params | pathinjection.js:30:40:30:45 | taint5 | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:31:26:31:34 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:31:26:31:34 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:33:38:33:46 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:33:38:33:46 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:34:24:34:32 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:34:24:34:32 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file read. |
| pathinjection.js:36:44:36:52 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:36:44:36:52 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:37:25:37:36 | userinputtwo | pathinjection.js:9:34:9:44 | req.headers | pathinjection.js:37:25:37:36 | userinputtwo | This CDS utils usage relies on user-provided value and can result in tainted data being written to a file. |
| pathinjection.js:38:25:38:33 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:38:25:38:33 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:40:26:40:34 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:40:26:40:34 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:41:26:41:34 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:41:26:41:34 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:43:25:43:33 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:43:25:43:33 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:44:25:44:33 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:44:25:44:33 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:46:26:46:34 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:46:26:46:34 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:47:26:47:34 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:47:26:47:34 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:49:22:49:30 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:49:22:49:30 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
| pathinjection.js:50:22:50:30 | userinput | pathinjection.js:8:31:8:38 | req.data | pathinjection.js:50:22:50:30 | userinput | This CDS utils usage relies on user-provided value and can result in unrestricted file operations. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const cds = require("@sap/cds");
const { decodeURI, decodeURIComponent, local, isdir, isfile, read, readdir, append, write, copy, stat, find, mkdirp, rmdir, rimraf, rm } = cds.utils

module.exports = class Service1 extends cds.ApplicationService {

init() {
this.on("send1", async (req) => {
const userinput = req.data
const userinputtwo = req.headers
const userinputthree = req.params

const taint1 = decodeURI(userinputthree) // taint step

const taint2 = decodeURIComponent(userinputthree) // taint step

const taint3 = local(userinputthree) // taint step

const taint4 = isdir(userinputthree) // taint step

const taint5 = isfile(userinputthree) // taint step

const pkg = await read(taint1) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.

const pdir = await readdir(taint2) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.

const s = await stat(taint3) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.

const f = await find(taint4) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.

await append('db/data').to(taint5) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.
await append(userinput, 'dist/db/data') // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.

await copy('db/data').to(userinput) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.
await copy(userinput, 'dist/db/data') // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file read.

await write({ foo: 'bar' }).to(userinput) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.
await write(userinputtwo).to('db/data') // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in tainted data being written to a file.
await write(userinput, { foo: 'bar' }) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.

await mkdirp(userinput, 'db', 'data') // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.
await mkdirp(userinput) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.

await rmdir(userinput, 'db', 'data') // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.
await rmdir(userinput) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.

await rimraf(userinput, 'db', 'data') // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.
await rimraf(userinput) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.

await rm(userinput, 'db', 'data') // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.
await rm(userinput) // sink

Check failure

Code scanning / CodeQL

Use of user controlled input in CAP CDS file system utilities High test

This CDS utils usage relies on user-provided value and can result in unrestricted file operations.
});

super.init();
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
path-traversal/PathInjection.ql