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 70 commits into
base: main
Choose a base branch
from

Conversation

jeongsoolee09
Copy link
Contributor

@jeongsoolee09 jeongsoolee09 commented Jun 19, 2025

What this PR contributes

  • Improve CQL Injection Query.

    • A new class CqlQueryRunnerCall now expands and replaces TaintedCqlClause, now covering cds.run, cds.db.run, srv.run, and tx.run.
      • Also, it models CRUD-style "shortcut calls" that translates to running a CQL on a service under the hood.
    • Expand base objects as possible receivers of methods .run and property read entities:
      • EntityEntry is "absorbed" into EntityReference. Plus, EntityReference now covers more examples, namely, cds as a shortcut to cds.db.
    • Make the alert message more useful by making the query itself the primary location which is different from sink (the string concatenation).
  • Add robust test cases (This is the gist of this PR, please take a look for the behavioral summary description of what this PR aims to implement).

    • send11-send17: Service1 runs query on the database service using cds.run and friends.
    • send21-send25: Service1 runs query on itself by await-ing the query.
    • send31-send37: Service1 runs query on itself using this.run and friends.
    • send41-send47: Service1 runs query on Service2 using Service2.run and friends.
    • send5: Service1 runs query on Service2 using CQN parsed with cds.ql.
    • send6: Service1 runs query on the database service using CQN parsed with cds.parse.cql.
    • send7: Service1 runs query on the database service using CQN parsed with global function CQL.
    • send81: Service1 runs query on Service2 using an unparsed CDL string (only valid in old versions of CAP).
    • send91-send97: Service1 runs query on Service2 using Service2.tx( tx => tx.run(...) ) and friends.
    • send101-send107: Service1 runs query on itself using this.tx( tx => tx.run(...) ) and friends.
    • send111-send117: Service1 runs query on the database service using cds.tx( tx => tx.run(...) ) and friends.
    • send121-send127: Service1 runs query on the database service using cds.db.tx( tx => tx.run(...) ) and friends.

Future works

  • The alert message can be more refined: if a sink is in cds.read(...).from(...), only the cds.read(...) part is alerted on, where the entire chained method call is more desirable as a alert location.
  • SensitiveExposure.ql seems to be quite brittle, it needs a rewrite. This PR only updates the query's reference to old definitions that are no longer available.

@jeongsoolee09 jeongsoolee09 marked this pull request as draft June 19, 2025 13:22
Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@jeongsoolee09 jeongsoolee09 marked this pull request as ready for review June 24, 2025 23:08
@jeongsoolee09 jeongsoolee09 requested a review from lcartey June 24, 2025 23:08
jeongsoolee09 and others added 5 commits July 1, 2025 16:19
@jeongsoolee09 jeongsoolee09 requested a review from lcartey July 2, 2025 20:56
Copy link
Contributor

@lcartey lcartey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added some more requested changes related to property writes to object literals used as arguments to entries calls - I don't believe they are vulnerable, and I think we should remove them as sinks.

It also looks like the expected SARIF results need to be updated.

It is unclear at the time being if a string
concatenation written to a property of its argument
is a possible injection vector or not.

Therefore, remove them from the test suite until
the answer to the above question becomes clear.
This is also related to the controversial `entries` call.
@jeongsoolee09 jeongsoolee09 requested a review from lcartey July 3, 2025 18:15
Copy link
Contributor

@lcartey lcartey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two final changes - otherwise I think we will still flag some of the entity cases.

I think we should also resurrect the entity tests to show that we don't currently flag them.

chainedMethodCall = this.getAChainedMethodCall(_)
|
result = chainedMethodCall.getAnArgument() or
result = chainedMethodCall.getAnArgument().(SourceNode).getAPropertyWrite().getRhs()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to remove this case, otherwise it will still be a sink via StringConcatParameterOfCqlShortcutMethodCall.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the below case in 3b073eb.

chainedMethodCall = this.getAChainedMethodCall(_)
|
result = chainedMethodCall.getAnArgument() or
result = chainedMethodCall.getAnArgument().(SourceNode).getAPropertyWrite().getRhs()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to remove this case, otherwise it will still be a sink via StringConcatParameterOfCqlShortcutMethodCall.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the below case in 3b073eb.

chainedMethodCall = this.getAChainedMethodCall(_)
|
result = chainedMethodCall.getAnArgument() or
result = chainedMethodCall.getAnArgument().(SourceNode).getAPropertyWrite().getRhs()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to remove this case, otherwise it will still be a sink via StringConcatParameterOfCqlShortcutMethodCall.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the below case in 3b073eb.

Even for INSERT, UPSERT, or CREATE calls, keep the
tracking granularity low to prevent the CQL injection
query making an alert on those cases with calls to
`entries`.
@jeongsoolee09
Copy link
Contributor Author

jeongsoolee09 commented Jul 3, 2025

Also resurrected test cases with the entries builder call and updated expected results in 4e77ab2 and 7a2db7b, respectively.

Updated the expected result of Code Scanning job in 66ef528.

1. These are now deemed safe (for now), so flip the labels.
2. Some cases are impossible: `entries` call only accepts only
objects (as varargs or an array of them). So, if it can be
fixed, then fix it; otherwise, remove it.
Copy link
Contributor

@lcartey lcartey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, other than the unit test failure.

…s tainted

For example, this example was not alerted on:

``` javascript
  this.on("send00234", async (req) => {
    const { id } = req.data;
    const { Service1Entity } = this.entities;
    await UPDATE.entity(Service1Entity).set("col1 = col1 + " + id).where`ID = ${id}`;  // UNSAFE: direct concatenation with `+`
  });
```
For cases of `.run(...)`, the comment location was
made on where the CQL was assembled. Shift the label
to be where the actual sink, the call to `run`, is.
The previous query marked only the base shortcut call
(srv.read, srv.update, ...) as the primary location.
This is a bit misleading since the "this query" part
would mean the entire chained method call. Therefore,
make the chained method call at the very end as the
primary location, thereby making the entire chained call
including the base shortcut call as the primary location.
The actual "string concatenation" location is found
in the path information.
@jeongsoolee09 jeongsoolee09 requested a review from lcartey July 8, 2025 20:30
@jeongsoolee09
Copy link
Contributor Author

Changes since last review:

  1. Fixed some regressions, especially in INSERT, UPSERT, and DELETE.
  2. Made the query alert on the entire query region for both cases of CqlClause and CqlShortcutMethodCall.
  3. Changed the (non-clickable) part of query message to more accurately describe the problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants