Skip to content

Improve CQL Injection Query #200

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

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ac0265e
Minor readability
jeongsoolee09 Jun 17, 2025
6fd6b2a
Make definition of `ImplMethodCallApplicationServiceDefinition` more …
jeongsoolee09 Jun 17, 2025
439ad43
Add `srv.entities` as part of `EntityEntry`
jeongsoolee09 Jun 17, 2025
3b7d741
Put CQL query-relevant definitions in a query library
jeongsoolee09 Jun 17, 2025
440712f
Do some refactoring
jeongsoolee09 Jun 19, 2025
dfa8c08
Sort out EntityReference
jeongsoolee09 Jun 19, 2025
571b316
Create a new CQL injection test project and move the old one to a folder
jeongsoolee09 Jun 20, 2025
1262260
Minor numbering
jeongsoolee09 Jun 20, 2025
908a572
install `@sap/cds-dk` and bump version of `@sap/cds`
jeongsoolee09 Jun 20, 2025
a8a0bb4
Move existing CQL Injection test case to `old/`
jeongsoolee09 Jun 23, 2025
5e39be5
Refine test cases and add more cases
jeongsoolee09 Jun 23, 2025
25450a8
Finalize working draft on current CqlInjection test case
jeongsoolee09 Jun 23, 2025
0f95069
Finalize CqlInjection
jeongsoolee09 Jun 23, 2025
1e83f5a
Fix test case and code
jeongsoolee09 Jun 24, 2025
9a2e99b
Appease compiler to make SensitiveExposure pass
jeongsoolee09 Jun 24, 2025
c5c9740
Debug `isSink`
jeongsoolee09 Jun 24, 2025
89522d0
Cover all cases in the current CQL injection test
jeongsoolee09 Jun 24, 2025
43738d5
Get the query depending on the type of sink
jeongsoolee09 Jun 24, 2025
13691d4
Minor formatting and tidying up
jeongsoolee09 Jun 24, 2025
468a780
Docstrings and comments
jeongsoolee09 Jun 24, 2025
a391d34
Fix a regression in `taintedclause`
jeongsoolee09 Jun 24, 2025
39b2397
Update expected test outputs of old cqlinjection.js
jeongsoolee09 Jun 24, 2025
fd0d13a
Fix a regression in applicationserviceinstance
jeongsoolee09 Jun 24, 2025
621a319
Fix regression in SensitiveExposure
jeongsoolee09 Jun 24, 2025
a0f6d9a
Remove unneeded code
jeongsoolee09 Jun 24, 2025
7652149
Update expected results of sensitive-exposure
jeongsoolee09 Jun 24, 2025
2e56ae0
Fix numbering and remove duplicate case
jeongsoolee09 Jun 24, 2025
a13a825
Make the new CQL injection test project a proper test case and remove…
jeongsoolee09 Jun 24, 2025
8a99661
Remove unneeded comment
jeongsoolee09 Jun 24, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import javascript
import semmle.javascript.security.dataflow.SqlInjectionCustomizations
import advanced_security.javascript.frameworks.cap.CQL
import advanced_security.javascript.frameworks.cap.RemoteFlowSources
import advanced_security.javascript.frameworks.cap.dataflow.FlowSteps

/**
* A CQL clause parameterized with a string concatentation expression.
*/
class CqlClauseWithStringConcatParameter instanceof CqlClause {
CqlClauseWithStringConcatParameter() {
exists(DataFlow::Node queryParameter |
(
if this instanceof CqlInsertClause or this instanceof CqlUpsertClause
then
queryParameter = this.getArgument().flow() or
queryParameter = this.getArgument().flow().(SourceNode).getAPropertyWrite().getRhs()
else queryParameter = this.getArgument().flow()
) and
exists(StringConcatenation::getAnOperand(queryParameter))
)
}

Location getLocation() { result = super.getLocation() }

string toString() { result = super.toString() }
}

/**
* A CQL shortcut method call (`read`, `create`, ...) parameterized with a string
* concatenation expression.
*/
class CqlShortcutMethodCallWithStringConcat instanceof CqlShortcutMethodCall {
CqlShortcutMethodCallWithStringConcat() {
exists(StringConcatenation::getAnOperand(super.getAQueryParameter()))
}

Location getLocation() { result = super.getLocation() }

string toString() { result = super.toString() }
}

/**
* A CQL parser call (`cds.ql`, `cds.parse.cql`, ...) parameterized with a string
* conatenation expression.
*/
class CqlClauseParserCallWithStringConcat instanceof CqlClauseParserCall {
CqlClauseParserCallWithStringConcat() {
not this.getCdlString().(StringOps::Concatenation).asExpr() instanceof TemplateLiteral and
exists(StringConcatenation::getAnOperand(this.getCdlString()))
}

Location getLocation() { result = super.getLocation() }

string toString() { result = super.toString() }
}

class CqlInjectionConfiguration extends TaintTracking::Configuration {
CqlInjectionConfiguration() { this = "CQL injection from untrusted data" }

override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }

override predicate isSink(DataFlow::Node node) {
exists(CqlRunMethodCall cqlRunMethodCall |
node = cqlRunMethodCall.(CqlRunMethodCall).getAQueryParameter()
)
or
exists(CqlShortcutMethodCallWithStringConcat queryRunnerCall |
node = queryRunnerCall.(CqlQueryRunnerCall).getAQueryParameter()
)
or
exists(AwaitExpr await, CqlClauseWithStringConcatParameter cqlClauseWithStringConcat |
node = await.flow() and
await.getOperand() = cqlClauseWithStringConcat.(CqlClause).asExpr()
)
}

override predicate isSanitizer(DataFlow::Node node) { node instanceof SqlInjection::Sanitizer }

override predicate isAdditionalTaintStep(DataFlow::Node start, DataFlow::Node end) {
/*
* 1. Given a call to a CQL parser, jump from the argument to the parser call itself.
*/

exists(CqlClauseParserCall cqlParserCall |
start = cqlParserCall.(CqlClauseParserCall).getAnArgument() and
end = cqlParserCall
)
or
/*
* 2. Jump from a query parameter to the CQL query clause itself. e.g. Given below code:
*
* ``` javascript
* await SELECT.from(Service1Entity).where("ID=" + id);
* ```
*
* This step jumps from `id` in the call to `where` to the entire SELECT clause.
*/

exists(CqlClause cqlClause |
start = cqlClause.getArgument().flow() and
end = cqlClause.flow()
)
or
/*
* 3. In case of INSERT and UPSERT, jump from an object write to a query parameter to the argument itself.
* e.g. Given below code:
*
* ``` javascript
* await INSERT.into(Service1Entity).entries({ id: "" + id });
* ```
*
* This step jumps from `id` in the property value expression to the enclosing object `{ id: "" + id }`.
* This in conjunction with the above step 2 will make the taint tracker jump from `id` to the entire
* INSERT clause.
*/

exists(CqlClause cqlClause, PropWrite propWrite |
(cqlClause instanceof CqlInsertClause or cqlClause instanceof CqlUpsertClause) and
cqlClause.getArgument().flow() = propWrite.getBase() and
start = propWrite.getRhs() and
end = propWrite.getBase()
)
}
}
Loading
Loading