-
Notifications
You must be signed in to change notification settings - Fork 2
Add CDS Utils path injection query #224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jeongsoolee09
merged 10 commits into
main
from
knewbury01/cds-util-path-traversal-query
Aug 27, 2025
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
5253def
Add path injection query and patch path injection taint model
knewbury01 4472828
Update expected results of code scanning
knewbury01 ff4834c
Address review feedback
knewbury01 3f63cb3
Add barrier to PathInjection query
knewbury01 d4743c2
Merge branch 'main' into knewbury01/cds-util-path-traversal-query
knewbury01 234884e
Merge branch 'main' into knewbury01/cds-util-path-traversal-query
jeongsoolee09 f35d885
Improve recommendation PathInjection
knewbury01 73a9c5b
Merge branch 'knewbury01/cds-util-path-traversal-query' of https://gi…
knewbury01 1ddb097
Fix utils unit test for previous rm of taint step as class
knewbury01 4c3f564
Add missing expected file update utils
knewbury01 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
javascript/frameworks/cap/src/path-traversal/PathInjection.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# 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. | ||
|
||
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. | ||
|
||
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. | ||
|
||
## 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 | ||
|
||
// GOOD: the path can not be controlled by an attacker | ||
let allowedDirectories = [ | ||
'this-is-a-safe-directory' | ||
]; | ||
if (allowedDirectories.includes(userinput)) { | ||
await rm(userinput) // sanitized - No Path injection alert | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## References | ||
knewbury01 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
- OWASP 2021: [Injection](https://owasp.org/Top10/A03_2021-Injection/). | ||
- SAP CAP CDS Utils : [Documentation](https://cap.cloud.sap/docs/node.js/cds-utils). | ||
- Common Weakness Enumeration: [CWE-020](https://cwe.mitre.org/data/definitions/20.html). | ||
- Common Weakness Enumeration: [CWE-022](https://cwe.mitre.org/data/definitions/22.html). |
48 changes: 48 additions & 0 deletions
48
javascript/frameworks/cap/src/path-traversal/PathInjection.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* @name Use of user controlled input in CAP CDS file system utilities | ||
* @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 | ||
private import semmle.javascript.security.dataflow.TaintedPathCustomizations | ||
private import semmle.javascript.security.dataflow.TaintedPathQuery as tq | ||
|
||
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(PathConverters pc | pc.getPath() = nodein and nodeout = pc) | ||
or | ||
exists(PathPredicates pr | pr.getPath() = nodein and nodeout = pr) | ||
} | ||
|
||
predicate isBarrier(DataFlow::Node node) { | ||
node instanceof TaintedPath::Sanitizer | ||
or | ||
tq::TaintedPathConfig::isBarrier(node) | ||
} | ||
} | ||
|
||
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() + "." |
71 changes: 33 additions & 38 deletions
71
javascript/frameworks/cap/test/models/cds/utils/utils.expected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,33 @@ | ||
| utils.js:5:21:5:30 | "%E0%A4%A" | "%E0%A4%A": additional flow step | | ||
| utils.js:7:31:7:40 | "%E0%A4%A" | "%E0%A4%A": additional flow step | | ||
| utils.js:9:18:9:27 | "%E0%A4%A" | "%E0%A4%A": additional flow step | | ||
| utils.js:13:17:13:21 | 'app' | 'app': additional flow step | | ||
| utils.js:15:19:15:32 | 'package.json' | 'package.json': additional flow step | | ||
| utils.js:17:22:17:35 | 'package.json' | 'package.json': controlled path sink | | ||
| utils.js:19:26:19:39 | 'package.json' | 'package.json': controlled path sink | | ||
| utils.js:21:20:21:33 | 'package.json' | 'package.json': controlled path sink | | ||
| utils.js:23:20:23:33 | 'package.json' | 'package.json': controlled path sink | | ||
| utils.js:25:14:25:22 | 'db/data' | 'db/data': controlled data sink | | ||
| utils.js:25:28:25:41 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:26:14:26:22 | 'db/data' | 'db/data': controlled path sink | | ||
| utils.js:26:25:26:38 | 'dist/db/data' | 'dist/db/data': controlled data sink | | ||
| utils.js:28:12:28:20 | 'db/data' | 'db/data': accessed path sink | | ||
| utils.js:28:26:28:39 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:29:12:29:20 | 'db/data' | 'db/data': accessed path sink | | ||
| utils.js:29:23:29:36 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:31:13:31:26 | { foo: 'bar' } | { foo: 'bar' }: controlled data sink | | ||
| utils.js:31:32:31:47 | 'some/file.json' | 'some/file.json': controlled path sink | | ||
| utils.js:32:13:32:28 | 'some/file.json' | 'some/file.json': controlled path sink | | ||
| utils.js:32:31:32:44 | { foo: 'bar' } | { foo: 'bar' }: controlled data sink | | ||
| utils.js:34:14:34:19 | 'dist' | 'dist': controlled path sink | | ||
| utils.js:34:22:34:25 | 'db' | 'db': controlled path sink | | ||
| utils.js:34:28:34:33 | 'data' | 'data': controlled path sink | | ||
| utils.js:35:14:35:27 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:37:13:37:18 | 'dist' | 'dist': controlled path sink | | ||
| utils.js:37:21:37:24 | 'db' | 'db': controlled path sink | | ||
| utils.js:37:27:37:32 | 'data' | 'data': controlled path sink | | ||
| utils.js:38:13:38:26 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:40:14:40:19 | 'dist' | 'dist': controlled path sink | | ||
| utils.js:40:22:40:25 | 'db' | 'db': controlled path sink | | ||
| utils.js:40:28:40:33 | 'data' | 'data': controlled path sink | | ||
| utils.js:41:14:41:27 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:43:10:43:15 | 'dist' | 'dist': controlled path sink | | ||
| utils.js:43:18:43:21 | 'db' | 'db': controlled path sink | | ||
| utils.js:43:24:43:29 | 'data' | 'data': controlled path sink | | ||
| utils.js:44:10:44:23 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:52:20:52:28 | 'db/data' | 'db/data': controlled data sink | | ||
| utils.js:5:22:5:35 | 'package.json' | 'package.json': controlled path sink | | ||
| utils.js:7:26:7:39 | 'package.json' | 'package.json': controlled path sink | | ||
| utils.js:9:20:9:33 | 'package.json' | 'package.json': controlled path sink | | ||
| utils.js:11:20:11:33 | 'package.json' | 'package.json': controlled path sink | | ||
| utils.js:13:14:13:22 | 'db/data' | 'db/data': controlled data sink | | ||
| utils.js:13:28:13:41 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:14:14:14:22 | 'db/data' | 'db/data': controlled path sink | | ||
| utils.js:14:25:14:38 | 'dist/db/data' | 'dist/db/data': controlled data sink | | ||
| utils.js:16:12:16:20 | 'db/data' | 'db/data': accessed path sink | | ||
| utils.js:16:26:16:39 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:17:12:17:20 | 'db/data' | 'db/data': accessed path sink | | ||
| utils.js:17:23:17:36 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:19:13:19:26 | { foo: 'bar' } | { foo: 'bar' }: controlled data sink | | ||
| utils.js:19:32:19:47 | 'some/file.json' | 'some/file.json': controlled path sink | | ||
| utils.js:20:13:20:28 | 'some/file.json' | 'some/file.json': controlled path sink | | ||
| utils.js:20:31:20:44 | { foo: 'bar' } | { foo: 'bar' }: controlled data sink | | ||
| utils.js:22:14:22:19 | 'dist' | 'dist': controlled path sink | | ||
| utils.js:22:22:22:25 | 'db' | 'db': controlled path sink | | ||
| utils.js:22:28:22:33 | 'data' | 'data': controlled path sink | | ||
| utils.js:23:14:23:27 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:25:13:25:18 | 'dist' | 'dist': controlled path sink | | ||
| utils.js:25:21:25:24 | 'db' | 'db': controlled path sink | | ||
| utils.js:25:27:25:32 | 'data' | 'data': controlled path sink | | ||
| utils.js:26:13:26:26 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:28:14:28:19 | 'dist' | 'dist': controlled path sink | | ||
| utils.js:28:22:28:25 | 'db' | 'db': controlled path sink | | ||
| utils.js:28:28:28:33 | 'data' | 'data': controlled path sink | | ||
| utils.js:29:14:29:27 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:31:10:31:15 | 'dist' | 'dist': controlled path sink | | ||
| utils.js:31:18:31:21 | 'db' | 'db': controlled path sink | | ||
| utils.js:31:24:31:29 | 'data' | 'data': controlled path sink | | ||
| utils.js:32:10:32:23 | 'dist/db/data' | 'dist/db/data': controlled path sink | | ||
| utils.js:40:20:40:28 | 'db/data' | 'db/data': controlled data sink | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.