mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +02:00
Merge branch 'main' into koa
This commit is contained in:
@@ -27,7 +27,7 @@ private DataFlow::Node remoteFlow(DataFlow::TypeTracker t) {
|
||||
exists(DataFlow::TypeTracker t2, DataFlow::Node prev | prev = remoteFlow(t2) |
|
||||
t2 = t.smallstep(prev, result)
|
||||
or
|
||||
any(TaintTracking::AdditionalTaintStep dts).step(prev, result) and
|
||||
TaintTracking::sharedTaintStep(prev, result) and
|
||||
t = t2
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
|
||||
<p>
|
||||
|
||||
Using user-controlled input in GitHub Actions may lead to
|
||||
code injection in contexts like <i>run:</i> or <i>script:</i>.
|
||||
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
|
||||
The best practice to avoid code injection vulnerabilities
|
||||
in GitHub workflows is to set the untrusted input value of the expression
|
||||
to an intermediate environment variable.
|
||||
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
<p>
|
||||
|
||||
The following example lets a user inject an arbitrary shell command:
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/comment_issue_bad.yml" />
|
||||
|
||||
<p>
|
||||
|
||||
The following example uses shell syntax to read
|
||||
the environment variable and will prevent the attack:
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/comment_issue_good.yml" />
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>GitHub Security Lab Research: <a href="https://securitylab.github.com/research/github-actions-untrusted-input">Keeping your GitHub Actions and workflows secure: Untrusted input</a>.</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @name Expression injection in Actions
|
||||
* @description Using user-controlled GitHub Actions contexts like `run:` or `script:` may allow a malicious
|
||||
* user to inject code into the GitHub action.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/actions/injection
|
||||
* @tags actions
|
||||
* security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import experimental.semmle.javascript.Actions
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledIssue(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*issue\\s*\\.\\s*title\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*issue\\s*\\.\\s*body\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledPullRequest(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*title\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*body\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*label\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*default_branch\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*ref\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledReview(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review\\s*\\.\\s*body\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review_comment\\s*\\.\\s*body\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledComment(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*comment\\s*\\.\\s*body\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledGollum(string context) {
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pages(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*page_name\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledCommit(string context) {
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*message\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*message\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*email\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*name\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*email\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*name\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*head_ref\\b")
|
||||
}
|
||||
|
||||
from Actions::Run run, string context, Actions::On on
|
||||
where
|
||||
run.getAReferencedExpression() = context and
|
||||
run.getStep().getJob().getWorkflow().getOn() = on and
|
||||
(
|
||||
exists(on.getNode("issues")) and
|
||||
isExternalUserControlledIssue(context)
|
||||
or
|
||||
exists(on.getNode("pull_request_target")) and
|
||||
isExternalUserControlledPullRequest(context)
|
||||
or
|
||||
(exists(on.getNode("pull_request_review_comment")) or exists(on.getNode("pull_request_review"))) and
|
||||
isExternalUserControlledReview(context)
|
||||
or
|
||||
(exists(on.getNode("issue_comment")) or exists(on.getNode("pull_request_target"))) and
|
||||
isExternalUserControlledComment(context)
|
||||
or
|
||||
exists(on.getNode("gollum")) and
|
||||
isExternalUserControlledGollum(context)
|
||||
or
|
||||
exists(on.getNode("pull_request_target")) and
|
||||
isExternalUserControlledCommit(context)
|
||||
)
|
||||
select run,
|
||||
"Potential injection from the " + context +
|
||||
" context, which may be controlled by an external user."
|
||||
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
|
||||
<p>
|
||||
|
||||
Combining <i>pull_request_target</i> workflow trigger with an explicit checkout
|
||||
of an untrusted pull request is a dangerous practice
|
||||
that may lead to repository compromise.
|
||||
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
|
||||
The best practice is to handle the potentially untrusted pull request
|
||||
via the <i>pull_request</i> trigger so that it is isolated in
|
||||
an unprivileged environment. The workflow processing the pull request
|
||||
should then store any results like code coverage or failed/passed tests
|
||||
in artifacts and exit. The following workflow then starts on <i>workflow_run</i>
|
||||
where it is granted write permission to the target repository and access to
|
||||
repository secrets, so that it can download the artifacts and make
|
||||
any necessary modifications to the repository or interact with third party services
|
||||
that require repository secrets (e.g. API tokens).
|
||||
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
<p>
|
||||
|
||||
The following example allows unauthorized repository modification
|
||||
and secrets exfiltration:
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/pull_request_target_bad.yml" />
|
||||
|
||||
<p>
|
||||
|
||||
The following example uses two workflows to handle potentially untrusted
|
||||
pull request in a secure manner. The receive_pr.yml is triggered first:
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/receive_pr.yml" />
|
||||
<p>The comment_pr.yml is triggered after receive_pr.yml completes:</p>
|
||||
<sample src="examples/comment_pr.yml" />
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>GitHub Security Lab Research: <a href="https://securitylab.github.com/research/github-actions-preventing-pwn-requests">Keeping your GitHub Actions and workflows secure: Preventing pwn requests</a>.</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* @name Checkout of untrusted code in trusted context
|
||||
* @description Workflows triggered on `pull_request_target` have read/write access to the base repository and access to secrets.
|
||||
* By explicitly checking out and running the build script from a fork the untrusted code is running in an environment
|
||||
* that is able to push to the base repository and to access secrets.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision low
|
||||
* @id js/actions/pull-request-target
|
||||
* @tags actions
|
||||
* security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import experimental.semmle.javascript.Actions
|
||||
|
||||
/**
|
||||
* Action step that doesn't contain `actor` or `label` check in `if:` or
|
||||
* the check requires manual analysis.
|
||||
*/
|
||||
class ProbableStep extends Actions::Step {
|
||||
// some simplistic checks to eleminate likely false positives:
|
||||
ProbableStep() {
|
||||
// no if at all
|
||||
not exists(this.getIf().getValue())
|
||||
or
|
||||
// needs manual analysis if there is OR
|
||||
this.getIf().getValue().matches("%||%")
|
||||
or
|
||||
// labels can be assigned by owners only
|
||||
not exists(
|
||||
this.getIf()
|
||||
.getValue()
|
||||
.regexpFind("\\bcontains\\s*\\(\\s*github\\s*\\.\\s*event\\s*\\.\\s*(?:issue|pull_request)\\s*\\.\\s*labels\\b",
|
||||
_, _)
|
||||
) and
|
||||
not exists(
|
||||
this.getIf()
|
||||
.getValue()
|
||||
.regexpFind("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*label\\s*\\.\\s*name\\s*==", _, _)
|
||||
) and
|
||||
// actor check means only the user is able to run it
|
||||
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action job that doesn't contain `actor` or `label` check in `if:` or
|
||||
* the check requires manual analysis.
|
||||
*/
|
||||
class ProbableJob extends Actions::Job {
|
||||
// some simplistic checks to eleminate likely false positives:
|
||||
ProbableJob() {
|
||||
// no if at all
|
||||
not exists(this.getIf().getValue())
|
||||
or
|
||||
// needs manual analysis if there is OR
|
||||
this.getIf().getValue().matches("%||%")
|
||||
or
|
||||
// labels can be assigned by owners only
|
||||
not exists(
|
||||
this.getIf()
|
||||
.getValue()
|
||||
.regexpFind("\\bcontains\\s*\\(\\s*github\\s*\\.\\s*event\\s*\\.\\s*(?:issue|pull_request)\\s*\\.\\s*labels\\b",
|
||||
_, _)
|
||||
) and
|
||||
not exists(
|
||||
this.getIf()
|
||||
.getValue()
|
||||
.regexpFind("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*label\\s*\\.\\s*name\\s*==", _, _)
|
||||
) and
|
||||
// actor check means only the user is able to run it
|
||||
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action step that doesn't contain `actor` or `label` check in `if:` or
|
||||
*/
|
||||
class ProbablePullRequestTarget extends Actions::On, Actions::MappingOrSequenceOrScalar {
|
||||
ProbablePullRequestTarget() {
|
||||
exists(YAMLNode prtNode |
|
||||
// The `on:` is triggered on `pull_request_target`
|
||||
this.getNode("pull_request_target") = prtNode and
|
||||
(
|
||||
// and either doesn't contain `types` filter
|
||||
not exists(prtNode.getAChild())
|
||||
or
|
||||
// or has the filter, that is something else than just [labeled]
|
||||
exists(Actions::MappingOrSequenceOrScalar prt, Actions::MappingOrSequenceOrScalar types |
|
||||
types = prt.getNode("types") and
|
||||
prtNode = prt and
|
||||
(
|
||||
not types.getElementCount() = 1 or
|
||||
not exists(types.getNode("labeled"))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
Actions::Ref ref, Actions::Uses uses, Actions::Step step, Actions::Job job,
|
||||
ProbablePullRequestTarget pullRequestTarget
|
||||
where
|
||||
pullRequestTarget.getWorkflow() = job.getWorkflow() and
|
||||
uses.getStep() = step and
|
||||
ref.getWith().getStep() = step and
|
||||
step.getJob() = job and
|
||||
uses.getGitHubRepository() = "actions/checkout" and
|
||||
(
|
||||
ref.getValue().matches("%github.event.pull_request.head.ref%") or
|
||||
ref.getValue().matches("%github.event.pull_request.head.sha%") or
|
||||
ref.getValue().matches("%github.event.pull_request.number%") or
|
||||
ref.getValue().matches("%github.event.number%") or
|
||||
ref.getValue().matches("%github.head_ref%")
|
||||
) and
|
||||
step instanceof ProbableStep and
|
||||
job instanceof ProbableJob
|
||||
select step, "Potential unsafe checkout of untrusted pull request on `pull_request_target`"
|
||||
@@ -0,0 +1,8 @@
|
||||
on: issue_comment
|
||||
|
||||
jobs:
|
||||
echo-body:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: |
|
||||
echo '${{ github.event.comment.body }}'
|
||||
@@ -0,0 +1,10 @@
|
||||
on: issue_comment
|
||||
|
||||
jobs:
|
||||
echo-body:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- env:
|
||||
BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
echo '$BODY'
|
||||
@@ -0,0 +1,52 @@
|
||||
name: Comment on the pull request
|
||||
|
||||
# read-write repo token
|
||||
# access to secrets
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Receive PR"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
${{ github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: 'Download artifact'
|
||||
uses: actions/github-script@v3.1.0
|
||||
with:
|
||||
script: |
|
||||
var artifacts = await github.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "pr"
|
||||
})[0];
|
||||
var download = await github.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
|
||||
- run: unzip pr.zip
|
||||
|
||||
- name: 'Comment on PR'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
var fs = require('fs');
|
||||
var issue_number = Number(fs.readFileSync('./NR'));
|
||||
await github.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue_number,
|
||||
body: 'Everything is OK. Thank you for the PR!'
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
on:
|
||||
pull_request_target
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
- run: |
|
||||
npm install
|
||||
npm build
|
||||
|
||||
- uses: completely/fakeaction@v2
|
||||
with:
|
||||
arg1: ${{ secrets.supersecret }}
|
||||
|
||||
- uses: fakerepo/comment-on-pr@v1
|
||||
with:
|
||||
message: |
|
||||
Thank you!
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Receive PR
|
||||
|
||||
# read-only repo token
|
||||
# no access to secrets
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# imitation of a build process
|
||||
- name: Build
|
||||
run: /bin/bash ./build.sh
|
||||
|
||||
- name: Save PR number
|
||||
run: |
|
||||
mkdir -p ./pr
|
||||
echo ${{ github.event.number }} > ./pr/NR
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: pr
|
||||
path: pr/
|
||||
316
javascript/ql/src/experimental/semmle/javascript/Actions.qll
Normal file
316
javascript/ql/src/experimental/semmle/javascript/Actions.qll
Normal file
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* Libraries for modelling GitHub Actions workflow files written in YAML.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Libraries for modelling GitHub Actions workflow files written in YAML.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||
*/
|
||||
module Actions {
|
||||
/** A YAML node in a GitHub Actions workflow file. */
|
||||
private class Node extends YAMLNode {
|
||||
Node() {
|
||||
this.getLocation()
|
||||
.getFile()
|
||||
.getRelativePath()
|
||||
.matches(["experimental/Security/CWE-094/.github/workflows/%", ".github/workflows/%"])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions are quite flexible in parsing YAML.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* on: pull_request
|
||||
* ```
|
||||
* and
|
||||
* ```
|
||||
* on: [pull_request]
|
||||
* ```
|
||||
* and
|
||||
* ```
|
||||
* on:
|
||||
* pull_request:
|
||||
* ```
|
||||
*
|
||||
* are equivalent.
|
||||
*/
|
||||
class MappingOrSequenceOrScalar extends YAMLNode {
|
||||
MappingOrSequenceOrScalar() {
|
||||
this instanceof YAMLMapping
|
||||
or
|
||||
this instanceof YAMLSequence
|
||||
or
|
||||
this instanceof YAMLScalar
|
||||
}
|
||||
|
||||
YAMLNode getNode(string name) {
|
||||
exists(YAMLMapping mapping |
|
||||
mapping = this and
|
||||
result = mapping.lookup(name)
|
||||
)
|
||||
or
|
||||
exists(YAMLSequence sequence, YAMLNode node |
|
||||
sequence = this and
|
||||
sequence.getAChildNode() = node and
|
||||
node.eval().toString() = name and
|
||||
result = node
|
||||
)
|
||||
or
|
||||
exists(YAMLScalar scalar |
|
||||
scalar = this and
|
||||
scalar.getValue() = name and
|
||||
result = scalar
|
||||
)
|
||||
}
|
||||
|
||||
int getElementCount() {
|
||||
exists(YAMLMapping mapping |
|
||||
mapping = this and
|
||||
result = mapping.getNumChild() / 2
|
||||
)
|
||||
or
|
||||
exists(YAMLSequence sequence |
|
||||
sequence = this and
|
||||
result = sequence.getNumChild()
|
||||
)
|
||||
or
|
||||
exists(YAMLScalar scalar |
|
||||
scalar = this and
|
||||
result = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An Actions workflow. This is a mapping at the top level of an Actions YAML workflow file.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||
*/
|
||||
class Workflow extends Node, YAMLDocument, YAMLMapping {
|
||||
/** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */
|
||||
YAMLMapping getJobs() { result = this.lookup("jobs") }
|
||||
|
||||
/** Gets the name of the workflow file. */
|
||||
string getFileName() { result = this.getFile().getBaseName() }
|
||||
|
||||
/** Gets the `on:` in this workflow. */
|
||||
On getOn() { result = this.lookup("on") }
|
||||
|
||||
/** Gets the job within this workflow with the given job ID. */
|
||||
Job getJob(string jobId) { result.getWorkflow() = this and result.getId() = jobId }
|
||||
}
|
||||
|
||||
/**
|
||||
* An Actions On trigger within a workflow.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#on.
|
||||
*/
|
||||
class On extends YAMLNode, MappingOrSequenceOrScalar {
|
||||
Workflow workflow;
|
||||
|
||||
On() { workflow.lookup("on") = this }
|
||||
|
||||
Workflow getWorkflow() { result = workflow }
|
||||
}
|
||||
|
||||
/**
|
||||
* An Actions job within a workflow.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobs.
|
||||
*/
|
||||
class Job extends YAMLNode, YAMLMapping {
|
||||
string jobId;
|
||||
Workflow workflow;
|
||||
|
||||
Job() { this = workflow.getJobs().lookup(jobId) }
|
||||
|
||||
/**
|
||||
* Gets the ID of this job, as a string.
|
||||
* This is the job's key within the `jobs` mapping.
|
||||
*/
|
||||
string getId() { result = jobId }
|
||||
|
||||
/**
|
||||
* Gets the ID of this job, as a YAML scalar node.
|
||||
* This is the job's key within the `jobs` mapping.
|
||||
*/
|
||||
YAMLString getIdNode() { workflow.getJobs().maps(result, this) }
|
||||
|
||||
/** Gets the human-readable name of this job, if any, as a string. */
|
||||
string getName() { result = this.getNameNode().getValue() }
|
||||
|
||||
/** Gets the human-readable name of this job, if any, as a YAML scalar node. */
|
||||
YAMLString getNameNode() { result = this.lookup("name") }
|
||||
|
||||
/** Gets the step at the given index within this job. */
|
||||
Step getStep(int index) { result.getJob() = this and result.getIndex() = index }
|
||||
|
||||
/** Gets the sequence of `steps` within this job. */
|
||||
YAMLSequence getSteps() { result = this.lookup("steps") }
|
||||
|
||||
/** Gets the workflow this job belongs to. */
|
||||
Workflow getWorkflow() { result = workflow }
|
||||
|
||||
/** Gets the value of the `if` field in this job, if any. */
|
||||
JobIf getIf() { result.getJob() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* An `if` within a job.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif.
|
||||
*/
|
||||
class JobIf extends YAMLNode, YAMLScalar {
|
||||
Job job;
|
||||
|
||||
JobIf() { job.lookup("if") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
Job getJob() { result = job }
|
||||
}
|
||||
|
||||
/**
|
||||
* A step within an Actions job.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idsteps.
|
||||
*/
|
||||
class Step extends YAMLNode, YAMLMapping {
|
||||
int index;
|
||||
Job job;
|
||||
|
||||
Step() { this = job.getSteps().getElement(index) }
|
||||
|
||||
/** Gets the 0-based position of this step within the sequence of `steps`. */
|
||||
int getIndex() { result = index }
|
||||
|
||||
/** Gets the job this step belongs to. */
|
||||
Job getJob() { result = job }
|
||||
|
||||
/** Gets the value of the `uses` field in this step, if any. */
|
||||
Uses getUses() { result.getStep() = this }
|
||||
|
||||
/** Gets the value of the `run` field in this step, if any. */
|
||||
Run getRun() { result.getStep() = this }
|
||||
|
||||
/** Gets the value of the `if` field in this step, if any. */
|
||||
StepIf getIf() { result.getStep() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* An `if` within a step.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsif.
|
||||
*/
|
||||
class StepIf extends YAMLNode, YAMLScalar {
|
||||
Step step;
|
||||
|
||||
StepIf() { step.lookup("if") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
Step getStep() { result = step }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `uses` field within an Actions job step, which references an action as a reusable unit of code.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* uses: actions/checkout@v2
|
||||
* ```
|
||||
* TODO: Does not currently handle local repository references, e.g. `.github/actions/action-name`.
|
||||
*/
|
||||
class Uses extends YAMLNode, YAMLScalar {
|
||||
Step step;
|
||||
/** The owner of the repository where the Action comes from, e.g. `actions` in `actions/checkout@v2`. */
|
||||
string repositoryOwner;
|
||||
/** The name of the repository where the Action comes from, e.g. `checkout` in `actions/checkout@v2`. */
|
||||
string repositoryName;
|
||||
/** The version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`. */
|
||||
string version;
|
||||
|
||||
Uses() {
|
||||
step.lookup("uses") = this and
|
||||
// Simple regular expression to split up an Action reference `owner/repo@version` into its components.
|
||||
exists(string regexp | regexp = "([^/]+)/([^/@]+)@(.+)" |
|
||||
repositoryOwner = this.getValue().regexpCapture(regexp, 1) and
|
||||
repositoryName = this.getValue().regexpCapture(regexp, 2) and
|
||||
version = this.getValue().regexpCapture(regexp, 3)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
Step getStep() { result = step }
|
||||
|
||||
/** Gets the owner and name of the repository where the Action comes from, e.g. `actions/checkout` in `actions/checkout@v2`. */
|
||||
string getGitHubRepository() { result = repositoryOwner + "/" + repositoryName }
|
||||
|
||||
/** Gets the version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`. */
|
||||
string getVersion() { result = version }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `with` field within an Actions job step, which references an action as a reusable unit of code.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepswith.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* with:
|
||||
* arg1: 1
|
||||
* arg2: abc
|
||||
* ```
|
||||
*/
|
||||
class With extends YAMLNode, YAMLMapping {
|
||||
Step step;
|
||||
|
||||
With() { step.lookup("with") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
Step getStep() { result = step }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ref:` field within an Actions `with:` specific to `actions/checkout` action.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* uses: actions/checkout@v2
|
||||
* with:
|
||||
* ref: ${{ github.event.pull_request.head.sha }}
|
||||
* ```
|
||||
*/
|
||||
class Ref extends YAMLNode, YAMLString {
|
||||
With with;
|
||||
|
||||
Ref() { with.lookup("ref") = this }
|
||||
|
||||
With getWith() { result = with }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `run` field within an Actions job step, which runs command-line programs using an operating system shell.
|
||||
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
|
||||
*/
|
||||
class Run extends YAMLNode, YAMLString {
|
||||
Step step;
|
||||
|
||||
Run() { step.lookup("run") = this }
|
||||
|
||||
/** Gets the step that executes this `run` command. */
|
||||
Step getStep() { result = step }
|
||||
|
||||
/**
|
||||
* Holds if `${{ e }}` is a GitHub Actions expression evaluated within this `run` command.
|
||||
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions.
|
||||
*/
|
||||
string getAReferencedExpression() {
|
||||
// We use `regexpFind` to obtain *all* matches of `${{...}}`,
|
||||
// not just the last (greedy match) or first (reluctant match).
|
||||
// TODO: This only handles expression strings that refer to contexts.
|
||||
// It does not handle operators within the expression.
|
||||
result =
|
||||
this.getValue()
|
||||
.regexpFind("\\$\\{\\{\\s*[A-Za-z0-9_\\.\\-]+\\s*\\}\\}", _, _)
|
||||
.regexpCapture("\\$\\{\\{\\s*([A-Za-z0-9_\\.\\-]+)\\s*\\}\\}", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ module ResourceExhaustion {
|
||||
}
|
||||
|
||||
predicate isRestrictedAdditionalTaintStep(DataFlow::Node src, DataFlow::Node dst) {
|
||||
any(TaintTracking::AdditionalTaintStep dts).step(src, dst) and
|
||||
TaintTracking::sharedTaintStep(src, dst) and
|
||||
not dst.asExpr() instanceof AddExpr and
|
||||
not dst.(DataFlow::MethodCallNode).calls(src, "toString")
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ import semmle.javascript.frameworks.Next
|
||||
import semmle.javascript.frameworks.NoSQL
|
||||
import semmle.javascript.frameworks.PkgCloud
|
||||
import semmle.javascript.frameworks.PropertyProjection
|
||||
import semmle.javascript.frameworks.Puppeteer
|
||||
import semmle.javascript.frameworks.React
|
||||
import semmle.javascript.frameworks.ReactNative
|
||||
import semmle.javascript.frameworks.Request
|
||||
|
||||
@@ -13,7 +13,7 @@ import CallGraphQuality
|
||||
|
||||
predicate relevantStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
(
|
||||
any(TaintTracking::AdditionalTaintStep dts).step(pred, succ)
|
||||
TaintTracking::sharedTaintStep(pred, succ)
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep cfg).step(pred, succ)
|
||||
or
|
||||
|
||||
@@ -313,10 +313,7 @@ module API {
|
||||
module Node {
|
||||
/** Gets a node whose type has the given qualified name. */
|
||||
Node ofType(string moduleName, string exportedName) {
|
||||
exists(TypeName tn |
|
||||
tn.hasQualifiedName(moduleName, exportedName) and
|
||||
result = Impl::MkCanonicalNameUse(tn).(Node).getInstance()
|
||||
)
|
||||
result = Impl::MkHasUnderlyingType(moduleName, exportedName).(Node).getInstance()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,6 +381,8 @@ module API {
|
||||
imports(_, m)
|
||||
or
|
||||
m = any(CanonicalName n | isUsed(n)).getExternalModuleName()
|
||||
or
|
||||
any(TypeAnnotation n).hasQualifiedName(m, _)
|
||||
} or
|
||||
MkClassInstance(DataFlow::ClassNode cls) { cls = trackDefNode(_) and hasSemantics(cls) } or
|
||||
MkAsyncFuncResult(DataFlow::FunctionNode f) {
|
||||
@@ -392,26 +391,13 @@ module API {
|
||||
MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or
|
||||
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
|
||||
/**
|
||||
* A TypeScript canonical name that is defined somewhere, and that isn't a module root.
|
||||
* (Module roots are represented by `MkModuleExport` nodes instead.)
|
||||
*
|
||||
* For most purposes, you probably want to use the `mkCanonicalNameDef` predicate instead of
|
||||
* this constructor.
|
||||
* A TypeScript type, identified by name of the type-annotation.
|
||||
* This API node is exclusively used by `API::Node::ofType`.
|
||||
*/
|
||||
MkCanonicalNameDef(CanonicalName n) {
|
||||
not n.isRoot() and
|
||||
isDefined(n)
|
||||
} or
|
||||
/**
|
||||
* A TypeScript canonical name that is used somewhere, and that isn't a module root.
|
||||
* (Module roots are represented by `MkModuleImport` nodes instead.)
|
||||
*
|
||||
* For most purposes, you probably want to use the `mkCanonicalNameUse` predicate instead of
|
||||
* this constructor.
|
||||
*/
|
||||
MkCanonicalNameUse(CanonicalName n) {
|
||||
not n.isRoot() and
|
||||
isUsed(n)
|
||||
MkHasUnderlyingType(string moduleName, string exportName) {
|
||||
any(TypeAnnotation n).hasQualifiedName(moduleName, exportName)
|
||||
or
|
||||
any(Type t).hasUnderlyingType(moduleName, exportName)
|
||||
} or
|
||||
MkSyntheticCallbackArg(DataFlow::Node src, int bound, DataFlow::InvokeNode nd) {
|
||||
trackUseNode(src, true, bound).flowsTo(nd.getCalleeNode())
|
||||
@@ -420,10 +406,9 @@ module API {
|
||||
class TDef = MkModuleDef or TNonModuleDef;
|
||||
|
||||
class TNonModuleDef =
|
||||
MkModuleExport or MkClassInstance or MkAsyncFuncResult or MkDef or MkCanonicalNameDef or
|
||||
MkSyntheticCallbackArg;
|
||||
MkModuleExport or MkClassInstance or MkAsyncFuncResult or MkDef or MkSyntheticCallbackArg;
|
||||
|
||||
class TUse = MkModuleUse or MkModuleImport or MkUse or MkCanonicalNameUse;
|
||||
class TUse = MkModuleUse or MkModuleImport or MkUse or MkHasUnderlyingType;
|
||||
|
||||
private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() }
|
||||
|
||||
@@ -460,20 +445,6 @@ module API {
|
||||
)
|
||||
}
|
||||
|
||||
/** An API-graph node representing definitions of the canonical name `cn`. */
|
||||
private TApiNode mkCanonicalNameDef(CanonicalName cn) {
|
||||
if cn.isModuleRoot()
|
||||
then result = MkModuleExport(cn.getExternalModuleName())
|
||||
else result = MkCanonicalNameDef(cn)
|
||||
}
|
||||
|
||||
/** An API-graph node representing uses of the canonical name `cn`. */
|
||||
private TApiNode mkCanonicalNameUse(CanonicalName cn) {
|
||||
if cn.isModuleRoot()
|
||||
then result = MkModuleImport(cn.getExternalModuleName())
|
||||
else result = MkCanonicalNameUse(cn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rhs` is the right-hand side of a definition of a node that should have an
|
||||
* incoming edge from `base` labeled `lbl` in the API graph.
|
||||
@@ -577,11 +548,6 @@ module API {
|
||||
exists(string m | nd = MkModuleExport(m) | exports(m, rhs))
|
||||
or
|
||||
nd = MkDef(rhs)
|
||||
or
|
||||
exists(CanonicalName n | nd = MkCanonicalNameDef(n) |
|
||||
rhs = n.(Namespace).getADefinition().flow() or
|
||||
rhs = n.(CanonicalFunctionName).getADefinition().flow()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,10 +599,10 @@ module API {
|
||||
ref = cls.getConstructor().getParameter(i)
|
||||
)
|
||||
or
|
||||
exists(TypeName tn |
|
||||
base = MkCanonicalNameUse(tn) and
|
||||
exists(string moduleName, string exportName |
|
||||
base = MkHasUnderlyingType(moduleName, exportName) and
|
||||
lbl = Label::instance() and
|
||||
ref = getANodeWithType(tn)
|
||||
ref.(DataFlow::SourceNode).hasUnderlyingType(moduleName, exportName)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::InvokeNode call |
|
||||
@@ -676,8 +642,6 @@ module API {
|
||||
)
|
||||
or
|
||||
nd = MkUse(ref)
|
||||
or
|
||||
exists(CanonicalName n | nd = MkCanonicalNameUse(n) | ref.asExpr() = n.getAnAccess())
|
||||
}
|
||||
|
||||
/** Holds if module `m` exports `rhs`. */
|
||||
@@ -832,13 +796,6 @@ module API {
|
||||
result = awaited(call, DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode getANodeWithType(TypeName tn) {
|
||||
exists(string moduleName, string typeName |
|
||||
tn.hasQualifiedName(moduleName, typeName) and
|
||||
result.hasUnderlyingType(moduleName, typeName)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
|
||||
*/
|
||||
@@ -879,11 +836,10 @@ module API {
|
||||
succ = MkClassInstance(trackDefNode(def))
|
||||
)
|
||||
or
|
||||
exists(CanonicalName cn1, string n, CanonicalName cn2 |
|
||||
pred in [mkCanonicalNameDef(cn1), mkCanonicalNameUse(cn1)] and
|
||||
cn2 = cn1.getChild(n) and
|
||||
lbl = Label::member(n) and
|
||||
succ in [mkCanonicalNameDef(cn2), mkCanonicalNameUse(cn2)]
|
||||
exists(string moduleName, string exportName |
|
||||
pred = MkModuleImport(moduleName) and
|
||||
lbl = Label::member(exportName) and
|
||||
succ = MkHasUnderlyingType(moduleName, exportName)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node nd, DataFlow::FunctionNode f |
|
||||
|
||||
@@ -9,12 +9,9 @@ module ArrayTaintTracking {
|
||||
/**
|
||||
* A taint propagating data flow edge caused by the builtin array functions.
|
||||
*/
|
||||
private class ArrayFunctionTaintStep extends TaintTracking::AdditionalTaintStep,
|
||||
DataFlow::CallNode {
|
||||
ArrayFunctionTaintStep() { arrayFunctionTaintStep(_, _, this) }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
arrayFunctionTaintStep(pred, succ, this)
|
||||
private class ArrayFunctionTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate arrayStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
arrayFunctionTaintStep(pred, succ, _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,14 +67,12 @@ module Base64 {
|
||||
* Note that we currently do not model base64 encoding as a taint-propagating data flow edge
|
||||
* to avoid false positives.
|
||||
*/
|
||||
private class Base64DecodingStep extends TaintTracking::AdditionalTaintStep {
|
||||
Decode dec;
|
||||
|
||||
Base64DecodingStep() { this = dec }
|
||||
|
||||
private class Base64DecodingStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = dec.getInput() and
|
||||
succ = dec.getOutput()
|
||||
exists(Decode dec |
|
||||
pred = dec.getInput() and
|
||||
succ = dec.getOutput()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,14 +165,12 @@ private class FunctionalExtendCallShallow extends ExtendCall {
|
||||
* A taint propagating data flow edge from the objects flowing into an extend call to its return value
|
||||
* and to the source of the destination object.
|
||||
*/
|
||||
private class ExtendCallTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
ExtendCall extend;
|
||||
|
||||
ExtendCallTaintStep() { this = extend }
|
||||
|
||||
private class ExtendCallTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = extend.getASourceOperand() and succ = extend.getDestinationOperand().getALocalSource()
|
||||
or
|
||||
pred = extend.getAnOperand() and succ = extend
|
||||
exists(ExtendCall extend |
|
||||
pred = extend.getASourceOperand() and succ = extend.getDestinationOperand().getALocalSource()
|
||||
or
|
||||
pred = extend.getAnOperand() and succ = extend
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,74 +398,65 @@ module PromiseFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint propagates from `pred` to `succ` through promises.
|
||||
* DEPRECATED. Use `TaintTracking::promiseStep` instead.
|
||||
*/
|
||||
predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// from `x` to `new Promise((res, rej) => res(x))`
|
||||
pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0)
|
||||
or
|
||||
// from `x` to `Promise.resolve(x)`
|
||||
pred = succ.(PromiseCreationCall).getValue() and
|
||||
not succ instanceof PromiseAllCreation
|
||||
or
|
||||
// from `arr` to `Promise.all(arr)`
|
||||
pred = succ.(PromiseAllCreation).getArrayNode()
|
||||
or
|
||||
exists(DataFlow::MethodCallNode thn | thn.getMethodName() = "then" |
|
||||
// from `p` to `x` in `p.then(x => ...)`
|
||||
pred = thn.getReceiver() and
|
||||
succ = thn.getCallback(0).getParameter(0)
|
||||
or
|
||||
// from `v` to `p.then(x => return v)`
|
||||
pred = thn.getCallback([0 .. 1]).getAReturn() and
|
||||
succ = thn
|
||||
)
|
||||
or
|
||||
exists(DataFlow::MethodCallNode catch | catch.getMethodName() = "catch" |
|
||||
// from `p` to `p.catch(..)`
|
||||
pred = catch.getReceiver() and
|
||||
succ = catch
|
||||
or
|
||||
// from `v` to `p.catch(x => return v)`
|
||||
pred = catch.getCallback(0).getAReturn() and
|
||||
succ = catch
|
||||
)
|
||||
or
|
||||
// from `p` to `p.finally(..)`
|
||||
exists(DataFlow::MethodCallNode finally | finally.getMethodName() = "finally" |
|
||||
pred = finally.getReceiver() and
|
||||
succ = finally
|
||||
)
|
||||
or
|
||||
// from `x` to `await x`
|
||||
exists(AwaitExpr await |
|
||||
pred.getEnclosingExpr() = await.getOperand() and
|
||||
succ.getEnclosingExpr() = await
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode mapSeries |
|
||||
mapSeries = DataFlow::moduleMember("bluebird", "mapSeries").getACall()
|
||||
|
|
||||
// from `xs` to `x` in `require("bluebird").mapSeries(xs, (x) => {...})`.
|
||||
pred = mapSeries.getArgument(0) and
|
||||
succ = mapSeries.getABoundCallbackParameter(1, 0)
|
||||
or
|
||||
// from `y` to `require("bluebird").mapSeries(x, x => y)`.
|
||||
pred = mapSeries.getCallback(1).getAReturn() and
|
||||
succ = mapSeries
|
||||
)
|
||||
}
|
||||
deprecated predicate promiseTaintStep = TaintTracking::promiseStep/2;
|
||||
|
||||
/**
|
||||
* An additional taint step that involves promises.
|
||||
*/
|
||||
private class PromiseTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
DataFlow::Node source;
|
||||
|
||||
PromiseTaintStep() { promiseTaintStep(source, this) }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = source and succ = this
|
||||
private class PromiseTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate promiseStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// from `x` to `new Promise((res, rej) => res(x))`
|
||||
pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0)
|
||||
or
|
||||
// from `x` to `Promise.resolve(x)`
|
||||
pred = succ.(PromiseCreationCall).getValue() and
|
||||
not succ instanceof PromiseAllCreation
|
||||
or
|
||||
// from `arr` to `Promise.all(arr)`
|
||||
pred = succ.(PromiseAllCreation).getArrayNode()
|
||||
or
|
||||
exists(DataFlow::MethodCallNode thn | thn.getMethodName() = "then" |
|
||||
// from `p` to `x` in `p.then(x => ...)`
|
||||
pred = thn.getReceiver() and
|
||||
succ = thn.getCallback(0).getParameter(0)
|
||||
or
|
||||
// from `v` to `p.then(x => return v)`
|
||||
pred = thn.getCallback([0 .. 1]).getAReturn() and
|
||||
succ = thn
|
||||
)
|
||||
or
|
||||
exists(DataFlow::MethodCallNode catch | catch.getMethodName() = "catch" |
|
||||
// from `p` to `p.catch(..)`
|
||||
pred = catch.getReceiver() and
|
||||
succ = catch
|
||||
or
|
||||
// from `v` to `p.catch(x => return v)`
|
||||
pred = catch.getCallback(0).getAReturn() and
|
||||
succ = catch
|
||||
)
|
||||
or
|
||||
// from `p` to `p.finally(..)`
|
||||
exists(DataFlow::MethodCallNode finally | finally.getMethodName() = "finally" |
|
||||
pred = finally.getReceiver() and
|
||||
succ = finally
|
||||
)
|
||||
or
|
||||
// from `x` to `await x`
|
||||
exists(AwaitExpr await |
|
||||
pred.getEnclosingExpr() = await.getOperand() and
|
||||
succ.getEnclosingExpr() = await
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode mapSeries |
|
||||
mapSeries = DataFlow::moduleMember("bluebird", "mapSeries").getACall()
|
||||
|
|
||||
// from `xs` to `x` in `require("bluebird").mapSeries(xs, (x) => {...})`.
|
||||
pred = mapSeries.getArgument(0) and
|
||||
succ = mapSeries.getABoundCallbackParameter(1, 0)
|
||||
or
|
||||
// from `y` to `require("bluebird").mapSeries(x, x => y)`.
|
||||
pred = mapSeries.getCallback(1).getAReturn() and
|
||||
succ = mapSeries
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,14 +491,13 @@ private module AsyncReturnSteps {
|
||||
/**
|
||||
* A data-flow step for ordinary return from an async function in a taint configuration.
|
||||
*/
|
||||
private class AsyncTaintReturn extends TaintTracking::AdditionalTaintStep, DataFlow::FunctionNode {
|
||||
Function f;
|
||||
|
||||
AsyncTaintReturn() { this.getFunction() = f and f.isAsync() }
|
||||
|
||||
private class AsyncTaintReturn extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
returnExpr(f, pred, _) and
|
||||
succ.(DataFlow::FunctionReturnNode).getFunction() = f
|
||||
exists(Function f |
|
||||
f.isAsync() and
|
||||
returnExpr(f, pred, _) and
|
||||
succ.(DataFlow::FunctionReturnNode).getFunction() = f
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -616,14 +606,12 @@ private module ClosurePromise {
|
||||
/**
|
||||
* Taint steps through closure promise methods.
|
||||
*/
|
||||
private class ClosurePromiseTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
DataFlow::Node pred;
|
||||
|
||||
ClosurePromiseTaintStep() {
|
||||
private class ClosurePromiseTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// static methods in goog.Promise
|
||||
exists(DataFlow::CallNode call, string name |
|
||||
call = Closure::moduleImport("goog.Promise." + name).getACall() and
|
||||
this = call and
|
||||
succ = call and
|
||||
pred = call.getAnArgument()
|
||||
|
|
||||
name = "all" or
|
||||
@@ -635,12 +623,10 @@ private module ClosurePromise {
|
||||
// promise created through goog.promise.withResolver()
|
||||
exists(DataFlow::CallNode resolver |
|
||||
resolver = Closure::moduleImport("goog.Promise.withResolver").getACall() and
|
||||
this = resolver.getAPropertyRead("promise") and
|
||||
succ = resolver.getAPropertyRead("promise") and
|
||||
pred = resolver.getAMethodCall("resolve").getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node src, DataFlow::Node dst) { src = pred and dst = this }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -904,7 +904,7 @@ private DataFlow::Node regExpSource(DataFlow::Node re, DataFlow::TypeBackTracker
|
||||
exists(DataFlow::TypeBackTracker t2, DataFlow::Node succ | succ = regExpSource(re, t2) |
|
||||
t2 = t.smallstep(result, succ)
|
||||
or
|
||||
any(TaintTracking::AdditionalTaintStep dts).step(result, succ) and
|
||||
TaintTracking::sharedTaintStep(result, succ) and
|
||||
t = t2
|
||||
)
|
||||
}
|
||||
@@ -1114,4 +1114,64 @@ module RegExp {
|
||||
or
|
||||
result = node.asExpr().(StringLiteral).asRegExp()
|
||||
}
|
||||
|
||||
/**
|
||||
* A character that will be analyzed by `RegExp::alwaysMatchesMetaCharacter`.
|
||||
*
|
||||
* Currently only `<`, `'`, and `"` are considered to be meta-characters, but new meta-characters
|
||||
* can be added by subclassing this class.
|
||||
*/
|
||||
abstract class MetaCharacter extends string {
|
||||
bindingset[this]
|
||||
MetaCharacter() { any() }
|
||||
|
||||
/**
|
||||
* Holds if the given atomic term matches this meta-character.
|
||||
*
|
||||
* Does not hold for derived terms like alternatives and groups.
|
||||
*
|
||||
* By default, `.`, `\W`, `\S`, and `\D` are considered to match any meta-character,
|
||||
* but the predicate can be overridden for meta-characters where this is not the case.
|
||||
*/
|
||||
predicate matchedByAtom(RegExpTerm term) {
|
||||
term.(RegExpConstant).getConstantValue() = this
|
||||
or
|
||||
term instanceof RegExpDot
|
||||
or
|
||||
term.(RegExpCharacterClassEscape).getValue() = ["\\W", "\\S", "\\D"]
|
||||
or
|
||||
exists(string lo, string hi |
|
||||
term.(RegExpCharacterRange).isRange(lo, hi) and
|
||||
lo <= this and
|
||||
this <= hi
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class DefaultMetaCharacter extends MetaCharacter {
|
||||
DefaultMetaCharacter() { this = ["<", "'", "\""] }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `term` can match any occurence of `char` within a string (not taking into account
|
||||
* the context in which `term` appears).
|
||||
*
|
||||
* This predicate is under-approximate and never considers sequences to guarantee a match.
|
||||
*/
|
||||
predicate alwaysMatchesMetaCharacter(RegExpTerm term, MetaCharacter char) {
|
||||
not term.getParent() instanceof RegExpSequence and // restrict size of predicate
|
||||
char.matchedByAtom(term)
|
||||
or
|
||||
alwaysMatchesMetaCharacter(term.(RegExpGroup).getAChild(), char)
|
||||
or
|
||||
alwaysMatchesMetaCharacter(term.(RegExpAlt).getAlternative(), char)
|
||||
or
|
||||
exists(RegExpCharacterClass class_ | term = class_ |
|
||||
not class_.isInverted() and
|
||||
char.matchedByAtom(class_.getAChild())
|
||||
or
|
||||
class_.isInverted() and
|
||||
not char.matchedByAtom(class_.getAChild())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ class StringReplaceCall extends DataFlow::MethodCallNode {
|
||||
}
|
||||
|
||||
/** Gets the regular expression passed as the first argument to `replace`, if any. */
|
||||
DataFlow::RegExpLiteralNode getRegExp() { result.flowsTo(getArgument(0)) }
|
||||
DataFlow::RegExpCreationNode getRegExp() { result.flowsTo(getArgument(0)) }
|
||||
|
||||
/** Gets a string that is being replaced by this call. */
|
||||
string getAReplacedString() {
|
||||
@@ -149,6 +149,25 @@ class StringReplaceCall extends DataFlow::MethodCallNode {
|
||||
pr.flowsTo(replacer.getAReturn()) and
|
||||
map.hasPropertyWrite(old, any(DataFlow::Node repl | repl.getStringValue() = new))
|
||||
)
|
||||
or
|
||||
// str.replace(regex, match => {
|
||||
// if (match === 'old') return 'new';
|
||||
// if (match === 'foo') return 'bar';
|
||||
// ...
|
||||
// })
|
||||
exists(
|
||||
DataFlow::FunctionNode replacer, ConditionGuardNode guard, EqualityTest test,
|
||||
DataFlow::Node ret
|
||||
|
|
||||
replacer = getCallback(1) and
|
||||
guard.getOutcome() = test.getPolarity() and
|
||||
guard.getTest() = test and
|
||||
replacer.getParameter(0).flowsToExpr(test.getAnOperand()) and
|
||||
test.getAnOperand().getStringValue() = old and
|
||||
ret = replacer.getAReturn() and
|
||||
guard.dominates(ret.getBasicBlock()) and
|
||||
new = ret.getStringValue()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ private import javascript
|
||||
private import internal.FlowSteps
|
||||
private import internal.AccessPaths
|
||||
private import internal.CallGraphs
|
||||
private import internal.Unit
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
|
||||
/**
|
||||
@@ -609,6 +610,57 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow edge that should be added to all data flow configurations in
|
||||
* addition to standard data flow edges.
|
||||
*
|
||||
* This class is a singleton, and thus subclasses do not need to specify a characteristic predicate.
|
||||
*
|
||||
* Note: For performance reasons, all subclasses of this class should be part
|
||||
* of the standard library. Override `Configuration::isAdditionalFlowStep`
|
||||
* for analysis-specific flow steps.
|
||||
*/
|
||||
class SharedFlowStep extends Unit {
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a data flow edge.
|
||||
*/
|
||||
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a data flow edge
|
||||
* transforming values with label `predlbl` to have label `succlbl`.
|
||||
*/
|
||||
predicate step(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
|
||||
DataFlow::FlowLabel succlbl
|
||||
) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contributes subclasses of `SharedFlowStep` to `AdditionalFlowStep`.
|
||||
*
|
||||
* This is a placeholder until we migrate to the `SharedFlowStep` class and deprecate `AdditionalFlowStep`.
|
||||
*/
|
||||
private class SharedStepAsAdditionalFlowStep extends AdditionalFlowStep {
|
||||
SharedStepAsAdditionalFlowStep() {
|
||||
any(SharedFlowStep st).step(_, this) or
|
||||
any(SharedFlowStep st).step(_, this, _, _)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedFlowStep st).step(pred, succ) and succ = this
|
||||
}
|
||||
|
||||
override predicate step(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
|
||||
DataFlow::FlowLabel succlbl
|
||||
) {
|
||||
any(SharedFlowStep st).step(pred, succ, predlbl, succlbl) and succ = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of pseudo-properties that are used in multiple files.
|
||||
*
|
||||
|
||||
@@ -1624,6 +1624,9 @@ class RegExpCreationNode extends DataFlow::SourceNode {
|
||||
result = this.(RegExpLiteralNode).getFlags()
|
||||
}
|
||||
|
||||
/** Holds if the constructed predicate has the `g` flag. */
|
||||
predicate isGlobal() { RegExp::isGlobal(getFlags()) }
|
||||
|
||||
/** Gets a data flow node referring to this regular expression. */
|
||||
private DataFlow::SourceNode getAReference(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
||||
private import semmle.javascript.dataflow.internal.Unit
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
|
||||
@@ -139,7 +140,7 @@ module TaintTracking {
|
||||
|
||||
final override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
isAdditionalTaintStep(pred, succ) or
|
||||
any(AdditionalTaintStep dts).step(pred, succ)
|
||||
sharedTaintStep(pred, succ)
|
||||
}
|
||||
|
||||
final override predicate isAdditionalFlowStep(
|
||||
@@ -211,32 +212,288 @@ module TaintTracking {
|
||||
* A taint-propagating data flow edge that should be added to all taint tracking
|
||||
* configurations in addition to standard data flow edges.
|
||||
*
|
||||
* This class is a singleton, and thus subclasses do not need to specify a characteristic predicate.
|
||||
*
|
||||
* Note: For performance reasons, all subclasses of this class should be part
|
||||
* of the standard library. Override `Configuration::isAdditionalTaintStep`
|
||||
* for analysis-specific taint steps.
|
||||
*
|
||||
* This class has multiple kinds of `step` predicates; these all have the same
|
||||
* effect on taint-tracking configurations. However, the categorization of steps
|
||||
* allows some data-flow configurations to opt in to specific kinds of taint steps.
|
||||
*/
|
||||
cached
|
||||
abstract class AdditionalTaintStep extends DataFlow::Node {
|
||||
class SharedTaintStep extends Unit {
|
||||
// Each step relation in this class should have a cached version in the `Cached` module
|
||||
// and be included in the `sharedTaintStep` predicate.
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge.
|
||||
*/
|
||||
cached
|
||||
abstract predicate step(DataFlow::Node pred, DataFlow::Node succ);
|
||||
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through URI manipulation.
|
||||
*
|
||||
* Does not include string operations that aren't specific to URIs, such
|
||||
* as concatenation and substring operations.
|
||||
*/
|
||||
predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge contributed by the heuristics library.
|
||||
*
|
||||
* Such steps are provided by the `semmle.javascript.heuristics` libraries
|
||||
* and will default to be being empty if those libraries are not imported.
|
||||
*/
|
||||
predicate heuristicStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through persistent storage.
|
||||
*/
|
||||
predicate persistentStorageStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through the heap.
|
||||
*/
|
||||
predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through arrays.
|
||||
*
|
||||
* These steps considers an array to be tainted if it contains tainted elements.
|
||||
*/
|
||||
predicate arrayStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through the `state` or `props` or a React component.
|
||||
*/
|
||||
predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through string concatenation.
|
||||
*/
|
||||
predicate stringConcatenationStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through string manipulation (other than concatenation).
|
||||
*/
|
||||
predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through data serialization, such as `JSON.stringify`.
|
||||
*/
|
||||
predicate serializeStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through data deserialization, such as `JSON.parse`.
|
||||
*/
|
||||
predicate deserializeStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through a promise.
|
||||
*
|
||||
* These steps consider a promise object to tainted if it can resolve to
|
||||
* a tainted value.
|
||||
*/
|
||||
predicate promiseStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge through object or array elements and
|
||||
* promises.
|
||||
* Module existing only to ensure all taint steps are cached as a single stage,
|
||||
* and without the the `Unit` type column.
|
||||
*/
|
||||
private class HeapTaintStep extends AdditionalTaintStep {
|
||||
HeapTaintStep() { heapStep(_, this) }
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
predicate forceStage() { Stages::Taint::ref() }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
Stages::Taint::ref() and
|
||||
heapStep(pred, succ) and
|
||||
succ = this
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge, which doesn't fit into a more specific category.
|
||||
*/
|
||||
cached
|
||||
predicate genericStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).step(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge, contribued by the heuristics library.
|
||||
*/
|
||||
cached
|
||||
predicate heuristicStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).heuristicStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` is an edge contributed by an `AdditionalTaintStep` instance.
|
||||
*/
|
||||
cached
|
||||
predicate legacyAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(InternalAdditionalTaintStep step).step(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Public taint step relations.
|
||||
*/
|
||||
cached
|
||||
module Public {
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through a URI library function.
|
||||
*/
|
||||
cached
|
||||
predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).uriStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` is a taint propagating data flow edge through persistent storage.
|
||||
*/
|
||||
cached
|
||||
predicate persistentStorageStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).persistentStorageStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` is a taint propagating data flow edge through the heap.
|
||||
*/
|
||||
cached
|
||||
predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).heapStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` is a taint propagating data flow edge through an array.
|
||||
*/
|
||||
cached
|
||||
predicate arrayStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).arrayStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` is a taint propagating data flow edge through the
|
||||
* properties of a view compenent, such as the `state` or `props` of a React component.
|
||||
*/
|
||||
cached
|
||||
predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).viewComponentStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` is a taint propagating data flow edge through string
|
||||
* concatenation.
|
||||
*/
|
||||
cached
|
||||
predicate stringConcatenationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).stringConcatenationStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` is a taint propagating data flow edge through string manipulation
|
||||
* (other than concatenation).
|
||||
*/
|
||||
cached
|
||||
predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).stringManipulationStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through data serialization, such as `JSON.stringify`.
|
||||
*/
|
||||
cached
|
||||
predicate serializeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).serializeStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through data deserialization, such as `JSON.parse`.
|
||||
*/
|
||||
cached
|
||||
predicate deserializeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).deserializeStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge through a promise.
|
||||
*
|
||||
* These steps consider a promise object to tainted if it can resolve to
|
||||
* a tainted value.
|
||||
*/
|
||||
cached
|
||||
predicate promiseStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTaintStep step).promiseStep(pred, succ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import Cached::Public
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` is a taint propagating data flow edge through a string operation.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate stringStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
stringConcatenationStep(pred, succ) or
|
||||
stringManipulationStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` is an edge used by all taint-tracking configurations.
|
||||
*/
|
||||
predicate sharedTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
Cached::legacyAdditionalTaintStep(pred, succ) or
|
||||
Cached::genericStep(pred, succ) or
|
||||
Cached::heuristicStep(pred, succ) or
|
||||
uriStep(pred, succ) or
|
||||
persistentStorageStep(pred, succ) or
|
||||
heapStep(pred, succ) or
|
||||
arrayStep(pred, succ) or
|
||||
viewComponentStep(pred, succ) or
|
||||
stringConcatenationStep(pred, succ) or
|
||||
stringManipulationStep(pred, succ) or
|
||||
serializeStep(pred, succ) or
|
||||
deserializeStep(pred, succ) or
|
||||
promiseStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Subclasses should extend `SharedTaintStep` instead, unless the subclass
|
||||
* is part of a query, in which case it should be moved into the `isAdditionalTaintStep` predicate
|
||||
* of the relevant taint-tracking configuration.
|
||||
* Other uses of the `step` relation in this class should instead use the `TaintTracking::sharedTaintStep`
|
||||
* predicate.
|
||||
*
|
||||
* A taint-propagating data flow edge that should be added to all taint tracking
|
||||
* configurations in addition to standard data flow edges.
|
||||
*
|
||||
* Note: For performance reasons, all subclasses of this class should be part
|
||||
* of the standard library. Override `Configuration::isAdditionalTaintStep`
|
||||
* for analysis-specific taint steps.
|
||||
*/
|
||||
deprecated class AdditionalTaintStep = InternalAdditionalTaintStep;
|
||||
|
||||
/** Internal version of `AdditionalTaintStep` that won't trigger deprecation warnings. */
|
||||
abstract private class InternalAdditionalTaintStep extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge.
|
||||
*/
|
||||
abstract predicate step(DataFlow::Node pred, DataFlow::Node succ);
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to the client side URL. */
|
||||
@@ -269,61 +526,61 @@ module TaintTracking {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is taint propagation through the heap from `pred` to `succ`.
|
||||
* A taint propagating data flow edge through object or array elements and
|
||||
* promises.
|
||||
*/
|
||||
private predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Expr e, Expr f | e = succ.asExpr() and f = pred.asExpr() |
|
||||
exists(Property prop | e.(ObjectExpr).getAProperty() = prop |
|
||||
prop.isComputed() and f = prop.getNameExpr()
|
||||
private class HeapTaintStep extends SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Expr e, Expr f | e = succ.asExpr() and f = pred.asExpr() |
|
||||
exists(Property prop | e.(ObjectExpr).getAProperty() = prop |
|
||||
prop.isComputed() and f = prop.getNameExpr()
|
||||
)
|
||||
or
|
||||
// spreading a tainted object into an object literal gives a tainted object
|
||||
e.(ObjectExpr).getAProperty().(SpreadProperty).getInit().(SpreadElement).getOperand() = f
|
||||
or
|
||||
// spreading a tainted value into an array literal gives a tainted array
|
||||
e.(ArrayExpr).getAnElement().(SpreadElement).getOperand() = f
|
||||
)
|
||||
or
|
||||
// awaiting a tainted expression gives a tainted result
|
||||
e.(AwaitExpr).getOperand() = f
|
||||
// arrays with tainted elements and objects with tainted property names are tainted
|
||||
succ.(DataFlow::ArrayCreationNode).getAnElement() = pred and
|
||||
not any(PromiseAllCreation call).getArrayNode() = succ
|
||||
or
|
||||
// spreading a tainted object into an object literal gives a tainted object
|
||||
e.(ObjectExpr).getAProperty().(SpreadProperty).getInit().(SpreadElement).getOperand() = f
|
||||
// reading from a tainted object yields a tainted result
|
||||
succ.(DataFlow::PropRead).getBase() = pred and
|
||||
not AccessPath::DominatingPaths::hasDominatingWrite(succ) and
|
||||
not isSafeClientSideUrlProperty(succ)
|
||||
or
|
||||
// spreading a tainted value into an array literal gives a tainted array
|
||||
e.(ArrayExpr).getAnElement().(SpreadElement).getOperand() = f
|
||||
)
|
||||
or
|
||||
// arrays with tainted elements and objects with tainted property names are tainted
|
||||
succ.(DataFlow::ArrayCreationNode).getAnElement() = pred and
|
||||
not any(PromiseAllCreation call).getArrayNode() = succ
|
||||
or
|
||||
// reading from a tainted object yields a tainted result
|
||||
succ.(DataFlow::PropRead).getBase() = pred and
|
||||
not AccessPath::DominatingPaths::hasDominatingWrite(succ) and
|
||||
not isSafeClientSideUrlProperty(succ)
|
||||
or
|
||||
// iterating over a tainted iterator taints the loop variable
|
||||
exists(ForOfStmt fos |
|
||||
pred = DataFlow::valueNode(fos.getIterationDomain()) and
|
||||
succ = DataFlow::lvalueNode(fos.getLValue())
|
||||
)
|
||||
or
|
||||
// taint-tracking rest patterns in l-values. E.g. `const {...spread} = foo()` or `const [...spread] = foo()`.
|
||||
exists(DestructuringPattern pattern |
|
||||
pred = DataFlow::lvalueNode(pattern) and
|
||||
succ = DataFlow::lvalueNode(pattern.getRest())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge through persistent storage.
|
||||
*/
|
||||
class PersistentStorageTaintStep extends AdditionalTaintStep {
|
||||
PersistentReadAccess read;
|
||||
|
||||
PersistentStorageTaintStep() { this = read }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = read.getAWrite().getValue() and
|
||||
succ = read
|
||||
// iterating over a tainted iterator taints the loop variable
|
||||
exists(ForOfStmt fos |
|
||||
pred = DataFlow::valueNode(fos.getIterationDomain()) and
|
||||
succ = DataFlow::lvalueNode(fos.getLValue())
|
||||
)
|
||||
or
|
||||
// taint-tracking rest patterns in l-values. E.g. `const {...spread} = foo()` or `const [...spread] = foo()`.
|
||||
exists(DestructuringPattern pattern |
|
||||
pred = DataFlow::lvalueNode(pattern) and
|
||||
succ = DataFlow::lvalueNode(pattern.getRest())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
predicate arrayFunctionTaintStep = ArrayTaintTracking::arrayFunctionTaintStep/3;
|
||||
/**
|
||||
* DEPRECATED. Use the predicate `TaintTracking::persistentStorageStep` instead.
|
||||
*
|
||||
* A taint propagating data flow edge through persistent storage.
|
||||
*/
|
||||
deprecated class PersistentStorageTaintStep extends SharedTaintStep {
|
||||
override predicate persistentStorageStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(PersistentReadAccess read |
|
||||
pred = read.getAWrite().getValue() and
|
||||
succ = read
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
deprecated predicate arrayFunctionTaintStep = ArrayTaintTracking::arrayFunctionTaintStep/3;
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge for assignments of the form `o[k] = v`, where
|
||||
@@ -335,78 +592,16 @@ module TaintTracking {
|
||||
* a map, not as a real object. In this case, it makes sense to consider the entire
|
||||
* map to be tainted as soon as one of its entries is.
|
||||
*/
|
||||
private class DictionaryTaintStep extends AdditionalTaintStep {
|
||||
DictionaryTaintStep() { dictionaryTaintStep(_, this) }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ = this and
|
||||
dictionaryTaintStep(pred, succ)
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if there is a step `pred -> succ` used by `DictionaryTaintStep`. */
|
||||
private predicate dictionaryTaintStep(DataFlow::Node pred, DataFlow::ObjectLiteralNode succ) {
|
||||
exists(AssignExpr assgn, IndexExpr idx |
|
||||
assgn.getTarget() = idx and
|
||||
succ.flowsToExpr(idx.getBase()) and
|
||||
not exists(idx.getPropertyName()) and
|
||||
pred = DataFlow::valueNode(assgn.getRhs())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge for assignments of the form `c1.state.p = v`,
|
||||
* where `c1` is an instance of React component `C`; in this case, we consider
|
||||
* taint to flow from `v` to any read of `c2.state.p`, where `c2`
|
||||
* also is an instance of `C`.
|
||||
*/
|
||||
private class ReactComponentStateTaintStep extends AdditionalTaintStep {
|
||||
DataFlow::Node source;
|
||||
|
||||
ReactComponentStateTaintStep() {
|
||||
exists(ReactComponent c, DataFlow::PropRead prn, DataFlow::PropWrite pwn |
|
||||
(
|
||||
c.getACandidateStateSource().flowsTo(pwn.getBase()) or
|
||||
c.getADirectStateAccess().flowsTo(pwn.getBase())
|
||||
) and
|
||||
(
|
||||
c.getAPreviousStateSource().flowsTo(prn.getBase()) or
|
||||
c.getADirectStateAccess().flowsTo(prn.getBase())
|
||||
)
|
||||
|
|
||||
prn.getPropertyName() = pwn.getPropertyName() and
|
||||
this = prn and
|
||||
source = pwn.getRhs()
|
||||
private class DictionaryTaintStep extends SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(AssignExpr assgn, IndexExpr idx, DataFlow::ObjectLiteralNode obj |
|
||||
assgn.getTarget() = idx and
|
||||
obj.flowsToExpr(idx.getBase()) and
|
||||
not exists(idx.getPropertyName()) and
|
||||
pred = DataFlow::valueNode(assgn.getRhs()) and
|
||||
succ = obj
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = source and succ = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge for assignments of the form `c1.props.p = v`,
|
||||
* where `c1` is an instance of React component `C`; in this case, we consider
|
||||
* taint to flow from `v` to any read of `c2.props.p`, where `c2`
|
||||
* also is an instance of `C`.
|
||||
*/
|
||||
private class ReactComponentPropsTaintStep extends AdditionalTaintStep {
|
||||
DataFlow::Node source;
|
||||
|
||||
ReactComponentPropsTaintStep() {
|
||||
exists(ReactComponent c, string name, DataFlow::PropRead prn |
|
||||
prn = c.getAPropRead(name) or
|
||||
prn = c.getAPreviousPropsSource().getAPropertyRead(name)
|
||||
|
|
||||
source = c.getACandidatePropsValue(name) and
|
||||
this = prn
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = source and succ = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,11 +610,8 @@ module TaintTracking {
|
||||
* Note that since we cannot easily distinguish string append from addition,
|
||||
* we consider any `+` operation to propagate taint.
|
||||
*/
|
||||
class StringConcatenationTaintStep extends AdditionalTaintStep {
|
||||
StringConcatenationTaintStep() { StringConcatenation::taintStep(_, this) }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ = this and
|
||||
class StringConcatenationTaintStep extends SharedTaintStep {
|
||||
override predicate stringConcatenationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
StringConcatenation::taintStep(pred, succ)
|
||||
}
|
||||
}
|
||||
@@ -428,109 +620,114 @@ module TaintTracking {
|
||||
* A taint propagating data flow edge arising from string manipulation
|
||||
* functions defined in the standard library.
|
||||
*/
|
||||
private class StringManipulationTaintStep extends AdditionalTaintStep, DataFlow::ValueNode {
|
||||
StringManipulationTaintStep() { stringManipulationStep(_, this) }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ = this and
|
||||
stringManipulationStep(pred, succ)
|
||||
private class StringManipulationTaintStep extends SharedTaintStep {
|
||||
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node target) {
|
||||
exists(DataFlow::ValueNode succ | target = succ |
|
||||
// string operations that propagate taint
|
||||
exists(string name | name = succ.getAstNode().(MethodCallExpr).getMethodName() |
|
||||
pred.asExpr() = succ.getAstNode().(MethodCallExpr).getReceiver() and
|
||||
(
|
||||
// sorted, interesting, properties of String.prototype
|
||||
name = "anchor" or
|
||||
name = "big" or
|
||||
name = "blink" or
|
||||
name = "bold" or
|
||||
name = "concat" or
|
||||
name = "fixed" or
|
||||
name = "fontcolor" or
|
||||
name = "fontsize" or
|
||||
name = "italics" or
|
||||
name = "link" or
|
||||
name = "padEnd" or
|
||||
name = "padStart" or
|
||||
name = "repeat" or
|
||||
name = "replace" or
|
||||
name = "replaceAll" or
|
||||
name = "slice" or
|
||||
name = "small" or
|
||||
name = "split" or
|
||||
name = "strike" or
|
||||
name = "sub" or
|
||||
name = "substr" or
|
||||
name = "substring" or
|
||||
name = "sup" or
|
||||
name = "toLocaleLowerCase" or
|
||||
name = "toLocaleUpperCase" or
|
||||
name = "toLowerCase" or
|
||||
name = "toUpperCase" or
|
||||
name = "trim" or
|
||||
name = "trimLeft" or
|
||||
name = "trimRight" or
|
||||
// sorted, interesting, properties of Object.prototype
|
||||
name = "toString" or
|
||||
name = "valueOf" or
|
||||
// sorted, interesting, properties of Array.prototype
|
||||
name = "join"
|
||||
)
|
||||
or
|
||||
exists(int i | pred.asExpr() = succ.getAstNode().(MethodCallExpr).getArgument(i) |
|
||||
name = "concat"
|
||||
or
|
||||
name = ["replace", "replaceAll"] and i = 1
|
||||
)
|
||||
)
|
||||
or
|
||||
// standard library constructors that propagate taint: `RegExp` and `String`
|
||||
exists(DataFlow::InvokeNode invk, string gv | gv = "RegExp" or gv = "String" |
|
||||
succ = invk and
|
||||
invk = DataFlow::globalVarRef(gv).getAnInvocation() and
|
||||
pred = invk.getArgument(0)
|
||||
)
|
||||
or
|
||||
// String.fromCharCode and String.fromCodePoint
|
||||
exists(int i, MethodCallExpr mce |
|
||||
mce = succ.getAstNode() and
|
||||
pred.asExpr() = mce.getArgument(i) and
|
||||
(mce.getMethodName() = "fromCharCode" or mce.getMethodName() = "fromCodePoint")
|
||||
)
|
||||
or
|
||||
// `(encode|decode)URI(Component)?` propagate taint
|
||||
exists(DataFlow::CallNode c, string name |
|
||||
succ = c and
|
||||
c = DataFlow::globalVarRef(name).getACall() and
|
||||
pred = c.getArgument(0)
|
||||
|
|
||||
name = "encodeURI" or
|
||||
name = "decodeURI" or
|
||||
name = "encodeURIComponent" or
|
||||
name = "decodeURIComponent"
|
||||
)
|
||||
or
|
||||
// In and out of .replace callbacks
|
||||
exists(StringReplaceCall call |
|
||||
// Into the callback if the regexp does not sanitize matches
|
||||
hasWildcardReplaceRegExp(call) and
|
||||
pred = call.getReceiver() and
|
||||
succ = call.getReplacementCallback().getParameter(0)
|
||||
or
|
||||
// Out of the callback
|
||||
pred = call.getReplacementCallback().getReturnNode() and
|
||||
succ = call
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint can propagate from `pred` to `succ` with a step related to string manipulation.
|
||||
*/
|
||||
private predicate stringManipulationStep(DataFlow::Node pred, DataFlow::ValueNode succ) {
|
||||
// string operations that propagate taint
|
||||
exists(string name | name = succ.getAstNode().(MethodCallExpr).getMethodName() |
|
||||
pred.asExpr() = succ.getAstNode().(MethodCallExpr).getReceiver() and
|
||||
(
|
||||
// sorted, interesting, properties of String.prototype
|
||||
name = "anchor" or
|
||||
name = "big" or
|
||||
name = "blink" or
|
||||
name = "bold" or
|
||||
name = "concat" or
|
||||
name = "fixed" or
|
||||
name = "fontcolor" or
|
||||
name = "fontsize" or
|
||||
name = "italics" or
|
||||
name = "link" or
|
||||
name = "padEnd" or
|
||||
name = "padStart" or
|
||||
name = "repeat" or
|
||||
name = "replace" or
|
||||
name = "replaceAll" or
|
||||
name = "slice" or
|
||||
name = "small" or
|
||||
name = "split" or
|
||||
name = "strike" or
|
||||
name = "sub" or
|
||||
name = "substr" or
|
||||
name = "substring" or
|
||||
name = "sup" or
|
||||
name = "toLocaleLowerCase" or
|
||||
name = "toLocaleUpperCase" or
|
||||
name = "toLowerCase" or
|
||||
name = "toUpperCase" or
|
||||
name = "trim" or
|
||||
name = "trimLeft" or
|
||||
name = "trimRight" or
|
||||
// sorted, interesting, properties of Object.prototype
|
||||
name = "toString" or
|
||||
name = "valueOf" or
|
||||
// sorted, interesting, properties of Array.prototype
|
||||
name = "join"
|
||||
)
|
||||
or
|
||||
exists(int i | pred.asExpr() = succ.getAstNode().(MethodCallExpr).getArgument(i) |
|
||||
name = "concat"
|
||||
or
|
||||
name = ["replace", "replaceAll"] and i = 1
|
||||
)
|
||||
)
|
||||
or
|
||||
// standard library constructors that propagate taint: `RegExp` and `String`
|
||||
exists(DataFlow::InvokeNode invk, string gv | gv = "RegExp" or gv = "String" |
|
||||
succ = invk and
|
||||
invk = DataFlow::globalVarRef(gv).getAnInvocation() and
|
||||
pred = invk.getArgument(0)
|
||||
)
|
||||
or
|
||||
// String.fromCharCode and String.fromCodePoint
|
||||
exists(int i, MethodCallExpr mce |
|
||||
mce = succ.getAstNode() and
|
||||
pred.asExpr() = mce.getArgument(i) and
|
||||
(mce.getMethodName() = "fromCharCode" or mce.getMethodName() = "fromCodePoint")
|
||||
)
|
||||
or
|
||||
// `(encode|decode)URI(Component)?` propagate taint
|
||||
exists(DataFlow::CallNode c, string name |
|
||||
succ = c and
|
||||
c = DataFlow::globalVarRef(name).getACall() and
|
||||
pred = c.getArgument(0)
|
||||
|
|
||||
name = "encodeURI" or
|
||||
name = "decodeURI" or
|
||||
name = "encodeURIComponent" or
|
||||
name = "decodeURIComponent"
|
||||
)
|
||||
/** Holds if the given call takes a regexp containing a wildcard. */
|
||||
pragma[noinline]
|
||||
private predicate hasWildcardReplaceRegExp(StringReplaceCall call) {
|
||||
RegExp::isWildcardLike(call.getRegExp().getRoot().getAChild*())
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge arising from string formatting.
|
||||
*/
|
||||
private class StringFormattingTaintStep extends AdditionalTaintStep {
|
||||
PrintfStyleCall call;
|
||||
|
||||
StringFormattingTaintStep() {
|
||||
this = call and
|
||||
call.returnsFormatted()
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ = this and
|
||||
(
|
||||
private class StringFormattingTaintStep extends SharedTaintStep {
|
||||
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(PrintfStyleCall call |
|
||||
call.returnsFormatted() and
|
||||
succ = call
|
||||
|
|
||||
pred = call.getFormatString()
|
||||
or
|
||||
pred = call.getFormatArgument(_)
|
||||
@@ -538,64 +735,68 @@ module TaintTracking {
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::MethodCallNode execMethodCall() {
|
||||
result.getMethodName() = "exec" and
|
||||
result.getReceiver().analyze().getAType() = TTRegExp()
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-propagating data flow edge from the first (and only) argument in a call to
|
||||
* `RegExp.prototype.exec` to its result.
|
||||
*/
|
||||
private class RegExpExecTaintStep extends AdditionalTaintStep {
|
||||
DataFlow::MethodCallNode self;
|
||||
|
||||
RegExpExecTaintStep() {
|
||||
this = self and
|
||||
self.getReceiver().analyze().getAType() = TTRegExp() and
|
||||
self.getMethodName() = "exec" and
|
||||
self.getNumArgument() = 1
|
||||
private class RegExpExecTaintStep extends SharedTaintStep {
|
||||
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call = execMethodCall() and
|
||||
call.getNumArgument() = 1 and
|
||||
pred = call.getArgument(0) and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = self.getArgument(0) and
|
||||
succ = this
|
||||
}
|
||||
pragma[nomagic]
|
||||
private DataFlow::MethodCallNode matchMethodCall() {
|
||||
result.getMethodName() = "match" and
|
||||
result.getArgument(0).analyze().getAType() = TTRegExp()
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge arising from calling `String.prototype.match()`.
|
||||
*/
|
||||
private class StringMatchTaintStep extends AdditionalTaintStep, DataFlow::MethodCallNode {
|
||||
StringMatchTaintStep() {
|
||||
this.getMethodName() = "match" and
|
||||
this.getNumArgument() = 1 and
|
||||
this.getArgument(0).analyze().getAType() = TTRegExp()
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this.getReceiver() and
|
||||
succ = this
|
||||
private class StringMatchTaintStep extends SharedTaintStep {
|
||||
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call = matchMethodCall() and
|
||||
call.getNumArgument() = 1 and
|
||||
pred = call.getReceiver() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge arising from JSON unparsing.
|
||||
*/
|
||||
private class JsonStringifyTaintStep extends AdditionalTaintStep, DataFlow::CallNode {
|
||||
JsonStringifyTaintStep() { this instanceof JsonStringifyCall }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getArgument(0) and succ = this
|
||||
private class JsonStringifyTaintStep extends SharedTaintStep {
|
||||
override predicate serializeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(JsonStringifyCall call |
|
||||
pred = call.getArgument(0) and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge arising from JSON parsing.
|
||||
*/
|
||||
private class JsonParserTaintStep extends AdditionalTaintStep, DataFlow::CallNode {
|
||||
JsonParserCall call;
|
||||
|
||||
JsonParserTaintStep() { this = call }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = call.getInput() and
|
||||
succ = call.getOutput()
|
||||
private class JsonParserTaintStep extends SharedTaintStep {
|
||||
override predicate deserializeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(JsonParserCall call |
|
||||
pred = call.getInput() and
|
||||
succ = call.getOutput()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,20 +898,26 @@ module TaintTracking {
|
||||
/**
|
||||
* A taint propagating data flow edge arising from sorting.
|
||||
*/
|
||||
private class SortTaintStep extends AdditionalTaintStep, DataFlow::MethodCallNode {
|
||||
SortTaintStep() { getMethodName() = "sort" }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getReceiver() and succ = this
|
||||
private class SortTaintStep extends SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "sort" and
|
||||
pred = call.getReceiver() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint step through an exception constructor, such as `x` to `new Error(x)`.
|
||||
*/
|
||||
class ErrorConstructorTaintStep extends AdditionalTaintStep, DataFlow::InvokeNode {
|
||||
ErrorConstructorTaintStep() {
|
||||
exists(string name | this = DataFlow::globalVarRef(name).getAnInvocation() |
|
||||
class ErrorConstructorTaintStep extends SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::NewNode invoke, string name |
|
||||
invoke = DataFlow::globalVarRef(name).getAnInvocation() and
|
||||
pred = invoke.getArgument(0) and
|
||||
succ = invoke
|
||||
|
|
||||
name = "Error" or
|
||||
name = "EvalError" or
|
||||
name = "RangeError" or
|
||||
@@ -720,11 +927,6 @@ module TaintTracking {
|
||||
name = "URIError"
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getArgument(0) and
|
||||
succ = this
|
||||
}
|
||||
}
|
||||
|
||||
private module RegExpCaptureSteps {
|
||||
@@ -776,28 +978,34 @@ module TaintTracking {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a step `pred -> succ` from the input of a RegExp match to
|
||||
* a static property of `RegExp`.
|
||||
* A step `pred -> succ` from the input of a RegExp match to a static property of `RegExp`.
|
||||
*/
|
||||
private predicate staticRegExpCaptureStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
getACaptureSetter(pred) = getANodeReachingCaptureRef(succ)
|
||||
or
|
||||
exists(StringReplaceCall replace |
|
||||
getANodeReachingCaptureRef(succ) = replace.getReplacementCallback().getFunction().getEntry() and
|
||||
pred = replace.getReceiver()
|
||||
)
|
||||
}
|
||||
|
||||
private class StaticRegExpCaptureStep extends AdditionalTaintStep {
|
||||
StaticRegExpCaptureStep() { staticRegExpCaptureStep(this, _) }
|
||||
|
||||
private class StaticRegExpCaptureStep extends SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this and
|
||||
staticRegExpCaptureStep(this, succ)
|
||||
getACaptureSetter(pred) = getANodeReachingCaptureRef(succ)
|
||||
or
|
||||
exists(StringReplaceCall replace |
|
||||
getANodeReachingCaptureRef(succ) =
|
||||
replace.getReplacementCallback().getFunction().getEntry() and
|
||||
pred = replace.getReceiver()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint step through the Node.JS function `util.inspect(..)`.
|
||||
*/
|
||||
class UtilInspectTaintStep extends SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = DataFlow::moduleImport("util").getAMemberCall("inspect") and
|
||||
call.getAnArgument() = pred and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A conditional checking a tainted string against a regular expression, which is
|
||||
* considered to be a sanitizer for all configurations.
|
||||
@@ -1083,6 +1291,6 @@ module TaintTracking {
|
||||
*/
|
||||
predicate localTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
DataFlow::localFlowStep(pred, succ) or
|
||||
any(AdditionalTaintStep s).step(pred, succ)
|
||||
sharedTaintStep(pred, succ)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
private newtype TUnit = MkUnit()
|
||||
|
||||
/**
|
||||
* A class with only one instance.
|
||||
*/
|
||||
class Unit extends TUnit {
|
||||
/** Gets a textual representation of this element. */
|
||||
final string toString() { result = "Unit" }
|
||||
}
|
||||
@@ -180,9 +180,7 @@ module Angular2 {
|
||||
)
|
||||
}
|
||||
|
||||
private class AngularTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
AngularTaintStep() { taintStep(_, this) }
|
||||
|
||||
private class AngularTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { taintStep(pred, succ) }
|
||||
}
|
||||
|
||||
@@ -483,14 +481,12 @@ module Angular2 {
|
||||
* A taint step `array -> elem` in `*ngFor="let elem of array"`, or more precisely,
|
||||
* a step from `array` to each access to `elem`.
|
||||
*/
|
||||
private class ForLoopStep extends TaintTracking::AdditionalTaintStep {
|
||||
ForLoopAttribute attrib;
|
||||
|
||||
ForLoopStep() { this = attrib.getIterationDomain() }
|
||||
|
||||
private class ForLoopStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this and
|
||||
succ = attrib.getAnIteratorAccess()
|
||||
exists(ForLoopAttribute attrib |
|
||||
pred = attrib.getIterationDomain() and
|
||||
succ = attrib.getAnIteratorAccess()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,27 +509,26 @@ module Angular2 {
|
||||
result.getCalleeNode().asExpr().(PipeRefExpr).getName() = name
|
||||
}
|
||||
|
||||
private class BuiltinPipeStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
string name;
|
||||
|
||||
BuiltinPipeStep() { this = getAPipeCall(name) }
|
||||
|
||||
private class BuiltinPipeStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ = this and
|
||||
exists(int i | pred = getArgument(i) |
|
||||
i = 0 and
|
||||
name =
|
||||
[
|
||||
"async", "i18nPlural", "json", "keyvalue", "lowercase", "uppercase", "titlecase",
|
||||
"slice"
|
||||
]
|
||||
exists(DataFlow::CallNode call, string name |
|
||||
call = getAPipeCall(name) and
|
||||
succ = call
|
||||
|
|
||||
exists(int i | pred = call.getArgument(i) |
|
||||
i = 0 and
|
||||
name =
|
||||
[
|
||||
"async", "i18nPlural", "json", "keyvalue", "lowercase", "uppercase", "titlecase",
|
||||
"slice"
|
||||
]
|
||||
or
|
||||
i = 1 and name = "date" // date format string
|
||||
)
|
||||
or
|
||||
i = 1 and name = "date" // date format string
|
||||
name = "translate" and
|
||||
pred = [call.getArgument(1), call.getOptionArgument(1, _)]
|
||||
)
|
||||
or
|
||||
name = "translate" and
|
||||
succ = this and
|
||||
pred = [getArgument(1), getOptionArgument(1, _)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,27 +577,25 @@ module Angular2 {
|
||||
* </mat-table>
|
||||
* ```
|
||||
*/
|
||||
private class MatTableTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
MatTableElement table;
|
||||
|
||||
MatTableTaintStep() { this = table.getDataSourceNode() }
|
||||
|
||||
private class MatTableTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this and
|
||||
succ = table.getARowRef()
|
||||
exists(MatTableElement table |
|
||||
pred = table.getDataSourceNode() and
|
||||
succ = table.getARowRef()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A taint step into the data array of a `MatTableDataSource` instance. */
|
||||
private class MatTableDataSourceStep extends TaintTracking::AdditionalTaintStep, DataFlow::NewNode {
|
||||
MatTableDataSourceStep() {
|
||||
this =
|
||||
DataFlow::moduleMember("@angular/material/table", "MatTableDataSource").getAnInstantiation()
|
||||
}
|
||||
|
||||
private class MatTableDataSourceStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = [getArgument(0), getAPropertyWrite("data").getRhs()] and
|
||||
succ = this
|
||||
exists(DataFlow::NewNode invoke |
|
||||
invoke =
|
||||
DataFlow::moduleMember("@angular/material/table", "MatTableDataSource")
|
||||
.getAnInstantiation() and
|
||||
pred = [invoke.getArgument(0), invoke.getAPropertyWrite("data").getRhs()] and
|
||||
succ = invoke
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,11 +150,11 @@ module AsyncPackage {
|
||||
*
|
||||
* For example: `data -> item` in `async.each(data, (item, cb) => {})`.
|
||||
*/
|
||||
private class IterationInputTaintStep extends TaintTracking::AdditionalTaintStep, IterationCall {
|
||||
private class IterationInputTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::FunctionNode iteratee |
|
||||
iteratee = getIteratorCallback() and // Require a closure to avoid spurious call/return mismatch.
|
||||
pred = getCollection() and
|
||||
exists(DataFlow::FunctionNode iteratee, IterationCall call |
|
||||
iteratee = call.getIteratorCallback() and // Require a closure to avoid spurious call/return mismatch.
|
||||
pred = call.getCollection() and
|
||||
succ = iteratee.getParameter(0)
|
||||
)
|
||||
}
|
||||
@@ -166,20 +166,21 @@ module AsyncPackage {
|
||||
*
|
||||
* For example: `item + taint()` -> result` in `async.map(data, (item, cb) => cb(null, item + taint()), (err, result) => {})`.
|
||||
*/
|
||||
private class IterationOutputTaintStep extends TaintTracking::AdditionalTaintStep, IterationCall {
|
||||
IterationOutputTaintStep() {
|
||||
name = "concat" or
|
||||
name = "map" or
|
||||
name = "reduce" or
|
||||
name = "reduceRight"
|
||||
}
|
||||
|
||||
private class IterationOutputTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::FunctionNode iteratee, DataFlow::FunctionNode final, int i |
|
||||
iteratee = getIteratorCallback().getALocalSource() and
|
||||
final = getFinalCallback() and // Require a closure to avoid spurious call/return mismatch.
|
||||
exists(
|
||||
DataFlow::FunctionNode iteratee, DataFlow::FunctionNode final, int i, IterationCall call
|
||||
|
|
||||
iteratee = call.getIteratorCallback().getALocalSource() and
|
||||
final = call.getFinalCallback() and // Require a closure to avoid spurious call/return mismatch.
|
||||
pred = getLastParameter(iteratee).getACall().getArgument(i) and
|
||||
succ = final.getParameter(i)
|
||||
succ = final.getParameter(i) and
|
||||
exists(string name | name = call.getName() |
|
||||
name = "concat" or
|
||||
name = "map" or
|
||||
name = "reduce" or
|
||||
name = "reduceRight"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -189,17 +190,13 @@ module AsyncPackage {
|
||||
*
|
||||
* For example: `data -> result` in `async.sortBy(data, orderingFn, (err, result) => {})`.
|
||||
*/
|
||||
private class IterationPreserveTaintStep extends TaintTracking::AdditionalTaintStep, IterationCall {
|
||||
IterationPreserveTaintStep() {
|
||||
name = "sortBy"
|
||||
// We don't currently include `filter` and `reject` as they could act as sanitizers.
|
||||
}
|
||||
|
||||
private class IterationPreserveTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::FunctionNode final |
|
||||
final = getFinalCallback() and // Require a closure to avoid spurious call/return mismatch.
|
||||
pred = getCollection() and
|
||||
succ = final.getParameter(1)
|
||||
exists(DataFlow::FunctionNode final, IterationCall call |
|
||||
final = call.getFinalCallback() and // Require a closure to avoid spurious call/return mismatch.
|
||||
pred = call.getCollection() and
|
||||
succ = final.getParameter(1) and
|
||||
call.getName() = "sortBy"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,32 +8,28 @@ private DataFlow::SourceNode classnames() {
|
||||
result = DataFlow::moduleImport(["classnames", "classnames/dedupe", "classnames/bind"])
|
||||
}
|
||||
|
||||
private class PlainStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
PlainStep() {
|
||||
this = classnames().getACall()
|
||||
or
|
||||
this = DataFlow::moduleImport("clsx").getACall()
|
||||
}
|
||||
|
||||
private class PlainStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getAnArgument() and
|
||||
succ = this
|
||||
exists(DataFlow::CallNode call |
|
||||
call = [classnames().getACall(), DataFlow::moduleImport("clsx").getACall()] and
|
||||
pred = call.getAnArgument() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step from `x` or `y` to the result of `classnames.bind(x)(y)`.
|
||||
*/
|
||||
private class BindStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
DataFlow::CallNode bind;
|
||||
|
||||
BindStep() {
|
||||
bind = classnames().getAMemberCall("bind") and
|
||||
this = bind.getACall()
|
||||
}
|
||||
|
||||
private class BindStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = [getAnArgument(), bind.getAnArgument(), bind.getOptionArgument(_, _)] and
|
||||
succ = this
|
||||
exists(DataFlow::CallNode bind | bind = classnames().getAMemberCall("bind") |
|
||||
pred =
|
||||
[
|
||||
succ.(DataFlow::CallNode).getAnArgument(), bind.getAnArgument(),
|
||||
bind.getOptionArgument(_, _)
|
||||
] and
|
||||
succ = bind.getACall()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import javascript
|
||||
module ClosureLibrary {
|
||||
private import DataFlow
|
||||
|
||||
private class StringStep extends TaintTracking::AdditionalTaintStep, CallNode {
|
||||
Node pred;
|
||||
|
||||
StringStep() {
|
||||
exists(string name | this = Closure::moduleImport("goog.string." + name).getACall() |
|
||||
pred = getAnArgument() and
|
||||
private class StringStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(Node pred, Node succ) {
|
||||
exists(string name, CallNode call |
|
||||
call = Closure::moduleImport("goog.string." + name).getACall() and succ = call
|
||||
|
|
||||
pred = call.getAnArgument() and
|
||||
(
|
||||
name = "canonicalizeNewlines" or
|
||||
name = "capitalize" or
|
||||
@@ -39,7 +39,7 @@ module ClosureLibrary {
|
||||
name = "whitespaceEscape"
|
||||
)
|
||||
or
|
||||
pred = getArgument(0) and
|
||||
pred = call.getArgument(0) and
|
||||
(
|
||||
name = "truncate" or
|
||||
name = "truncateMiddle" or
|
||||
@@ -47,10 +47,5 @@ module ClosureLibrary {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(Node src, Node dst) {
|
||||
src = pred and
|
||||
dst = this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,20 +110,15 @@ module FunctionCompositionCall {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint step for a composed function.
|
||||
*/
|
||||
private class ComposedFunctionTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
FunctionCompositionCall composed;
|
||||
DataFlow::CallNode call;
|
||||
|
||||
ComposedFunctionTaintStep() {
|
||||
call = composed.getACall() and
|
||||
this = call
|
||||
}
|
||||
|
||||
private class ComposedFunctionTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(int fnIndex, DataFlow::FunctionNode fn | fn = composed.getOperandFunction(fnIndex) |
|
||||
exists(
|
||||
int fnIndex, DataFlow::FunctionNode fn, FunctionCompositionCall composed,
|
||||
DataFlow::CallNode call
|
||||
|
|
||||
fn = composed.getOperandFunction(fnIndex) and
|
||||
call = composed.getACall()
|
||||
|
|
||||
// flow into the first function
|
||||
fnIndex = composed.getNumOperand() - 1 and
|
||||
exists(int callArgIndex |
|
||||
@@ -140,7 +135,7 @@ private class ComposedFunctionTaintStep extends TaintTracking::AdditionalTaintSt
|
||||
// flow out of the composed call
|
||||
fnIndex = 0 and
|
||||
pred = fn.getReturnNode() and
|
||||
succ = this
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,24 +29,26 @@ private module DateFns {
|
||||
*
|
||||
* A format string can use single-quotes to include mostly arbitrary text.
|
||||
*/
|
||||
private class FormatStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
FormatStep() { this = formatFunction().getACall() }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getArgument(1) and
|
||||
succ = this
|
||||
private class FormatStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = formatFunction().getACall() and
|
||||
pred = call.getArgument(1) and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint step of form: `f -> format(f)(date)`
|
||||
*/
|
||||
private class CurriedFormatStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
CurriedFormatStep() { this = curriedFormatFunction().getACall() }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getArgument(0) and
|
||||
succ = getACall()
|
||||
private class CurriedFormatStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = curriedFormatFunction().getACall() and
|
||||
pred = call.getArgument(0) and
|
||||
succ = call.getACall()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,12 +68,13 @@ private module Moment {
|
||||
*
|
||||
* The format string can use backslash-escaping to include mostly arbitrary text.
|
||||
*/
|
||||
private class MomentFormatStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
MomentFormatStep() { this = moment().getMember("format").getACall() }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getArgument(0) and
|
||||
succ = this
|
||||
private class MomentFormatStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = moment().getMember("format").getACall() and
|
||||
pred = call.getArgument(0) and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,12 +85,13 @@ private module DateFormat {
|
||||
*
|
||||
* The format string can use single-quotes to include mostly arbitrary text.
|
||||
*/
|
||||
private class DateFormatStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
DateFormatStep() { this = DataFlow::moduleImport("dateformat").getACall() }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getArgument(1) and
|
||||
succ = this
|
||||
private class DateFormatStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = DataFlow::moduleImport("dateformat").getACall() and
|
||||
pred = call.getArgument(1) and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ module Firebase {
|
||||
result.hasUnderlyingType("firebase", "database.DataSnapshot")
|
||||
)
|
||||
or
|
||||
promiseTaintStep(snapshot(t), result)
|
||||
TaintTracking::promiseStep(snapshot(t), result)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = snapshot(t2).track(t2, t))
|
||||
}
|
||||
|
||||
@@ -11,12 +11,13 @@ private module JwtDecode {
|
||||
/**
|
||||
* A taint-step for `succ = require("jwt-decode")(pred)`.
|
||||
*/
|
||||
private class JwtDecodeStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
JwtDecodeStep() { this = DataFlow::moduleImport("jwt-decode").getACall() }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this.getArgument(0) and
|
||||
succ = this
|
||||
private class JwtDecodeStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate deserializeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = DataFlow::moduleImport("jwt-decode").getACall() and
|
||||
pred = call.getArgument(0) and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,12 +29,13 @@ private module JsonWebToken {
|
||||
/**
|
||||
* A taint-step for `require("jsonwebtoken").verify(pred, "key", (err succ) => {...})`.
|
||||
*/
|
||||
private class VerifyStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
VerifyStep() { this = DataFlow::moduleMember("jsonwebtoken", "verify").getACall() }
|
||||
|
||||
private class VerifyStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this.getArgument(0) and
|
||||
succ = this.getABoundCallbackParameter(2, 1)
|
||||
exists(DataFlow::CallNode call |
|
||||
call = DataFlow::moduleMember("jsonwebtoken", "verify").getACall() and
|
||||
pred = call.getArgument(0) and
|
||||
succ = call.getABoundCallbackParameter(2, 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -440,11 +440,9 @@ module LodashUnderscore {
|
||||
/**
|
||||
* A model for taint-steps involving (non-function) underscore methods.
|
||||
*/
|
||||
private class UnderscoreTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
UnderscoreTaintStep() { underscoreTaintStep(this, _) }
|
||||
|
||||
private class UnderscoreTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
underscoreTaintStep(pred, succ) and pred = this
|
||||
underscoreTaintStep(pred, succ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,45 +7,45 @@ import javascript
|
||||
/**
|
||||
* A taint step for the `marked` library, that converts markdown to HTML.
|
||||
*/
|
||||
private class MarkedStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
MarkedStep() {
|
||||
this = DataFlow::globalVarRef("marked").getACall()
|
||||
or
|
||||
this = DataFlow::moduleImport("marked").getACall()
|
||||
}
|
||||
|
||||
private class MarkedStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ = this and
|
||||
pred = this.getArgument(0)
|
||||
exists(DataFlow::CallNode call |
|
||||
call = DataFlow::globalVarRef("marked").getACall()
|
||||
or
|
||||
call = DataFlow::moduleImport("marked").getACall()
|
||||
|
|
||||
succ = call and
|
||||
pred = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint step for the `markdown-table` library.
|
||||
*/
|
||||
private class MarkdownTableStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
MarkdownTableStep() { this = DataFlow::moduleImport("markdown-table").getACall() }
|
||||
|
||||
private class MarkdownTableStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ = this and
|
||||
pred = this.getArgument(0)
|
||||
exists(DataFlow::CallNode call | call = DataFlow::moduleImport("markdown-table").getACall() |
|
||||
succ = call and
|
||||
pred = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint step for the `showdown` library.
|
||||
*/
|
||||
private class ShowDownStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
ShowDownStep() {
|
||||
this =
|
||||
[DataFlow::globalVarRef("showdown"), DataFlow::moduleImport("showdown")]
|
||||
.getAConstructorInvocation("Converter")
|
||||
.getAMemberCall(["makeHtml", "makeMd"])
|
||||
}
|
||||
|
||||
private class ShowDownStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
succ = this and
|
||||
pred = this.getArgument(0)
|
||||
exists(DataFlow::CallNode call |
|
||||
call =
|
||||
[DataFlow::globalVarRef("showdown"), DataFlow::moduleImport("showdown")]
|
||||
.getAConstructorInvocation("Converter")
|
||||
.getAMemberCall(["makeHtml", "makeMd"])
|
||||
|
|
||||
succ = call and
|
||||
pred = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,16 +93,16 @@ private module Unified {
|
||||
/**
|
||||
* A taint step for the `unified` library.
|
||||
*/
|
||||
class UnifiedStep extends TaintTracking::AdditionalTaintStep, UnifiedChain {
|
||||
UnifiedStep() {
|
||||
// sanitizer. Mostly looking for `rehype-sanitize`, but also other plugins with `sanitize` in their name.
|
||||
not this.getAUsedPlugin().getALocalSource() =
|
||||
DataFlow::moduleImport(any(string s | s.matches("%sanitize%")))
|
||||
}
|
||||
|
||||
class UnifiedStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getInput() and
|
||||
succ = getOutput()
|
||||
exists(UnifiedChain chain |
|
||||
// sanitizer. Mostly looking for `rehype-sanitize`, but also other plugins with `sanitize` in their name.
|
||||
not chain.getAUsedPlugin().getALocalSource() =
|
||||
DataFlow::moduleImport(any(string s | s.matches("%sanitize%")))
|
||||
|
|
||||
pred = chain.getInput() and
|
||||
succ = chain.getOutput()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,11 +110,11 @@ private module Unified {
|
||||
/**
|
||||
* A taint step for the `snarkdown` library.
|
||||
*/
|
||||
private class SnarkdownStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
SnarkdownStep() { this = DataFlow::moduleImport("snarkdown").getACall() }
|
||||
|
||||
private class SnarkdownStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
this = succ and
|
||||
pred = this.getArgument(0)
|
||||
exists(DataFlow::CallNode call | call = DataFlow::moduleImport("snarkdown").getACall() |
|
||||
call = succ and
|
||||
pred = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,74 +291,65 @@ module NodeJSLib {
|
||||
/**
|
||||
* A call to a path-module method that preserves taint.
|
||||
*/
|
||||
private class PathFlowTarget extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
|
||||
DataFlow::Node tainted;
|
||||
|
||||
PathFlowTarget() {
|
||||
exists(string methodName | this = NodeJSLib::Path::moduleMember(methodName).getACall() |
|
||||
private class PathFlowStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call, string methodName |
|
||||
call = NodeJSLib::Path::moduleMember(methodName).getACall() and
|
||||
succ = call
|
||||
|
|
||||
// getters
|
||||
methodName = "basename" and tainted = getArgument(0)
|
||||
methodName = "basename" and pred = call.getArgument(0)
|
||||
or
|
||||
methodName = "dirname" and tainted = getArgument(0)
|
||||
methodName = "dirname" and pred = call.getArgument(0)
|
||||
or
|
||||
methodName = "extname" and tainted = getArgument(0)
|
||||
methodName = "extname" and pred = call.getArgument(0)
|
||||
or
|
||||
// transformers
|
||||
methodName = "join" and tainted = getAnArgument()
|
||||
methodName = "join" and pred = call.getAnArgument()
|
||||
or
|
||||
methodName = "normalize" and tainted = getArgument(0)
|
||||
methodName = "normalize" and pred = call.getArgument(0)
|
||||
or
|
||||
methodName = "relative" and tainted = getArgument([0 .. 1])
|
||||
methodName = "relative" and pred = call.getArgument([0 .. 1])
|
||||
or
|
||||
methodName = "resolve" and tainted = getAnArgument()
|
||||
methodName = "resolve" and pred = call.getAnArgument()
|
||||
or
|
||||
methodName = "toNamespacedPath" and tainted = getArgument(0)
|
||||
methodName = "toNamespacedPath" and pred = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = tainted and succ = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a fs-module method that preserves taint.
|
||||
*/
|
||||
private class FsFlowTarget extends TaintTracking::AdditionalTaintStep {
|
||||
DataFlow::Node tainted;
|
||||
|
||||
FsFlowTarget() {
|
||||
private class FsFlowStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call, string methodName |
|
||||
call = FS::moduleMember(methodName).getACall()
|
||||
|
|
||||
methodName = "realpathSync" and
|
||||
tainted = call.getArgument(0) and
|
||||
this = call
|
||||
pred = call.getArgument(0) and
|
||||
succ = call
|
||||
or
|
||||
methodName = "realpath" and
|
||||
tainted = call.getArgument(0) and
|
||||
this = call.getCallback(1).getParameter(1)
|
||||
pred = call.getArgument(0) and
|
||||
succ = call.getCallback(1).getParameter(1)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = tainted and succ = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of taint propagation through `new Buffer` and `Buffer.from`.
|
||||
*/
|
||||
private class BufferTaintStep extends TaintTracking::AdditionalTaintStep, DataFlow::InvokeNode {
|
||||
BufferTaintStep() {
|
||||
this = DataFlow::globalVarRef("Buffer").getAnInstantiation()
|
||||
or
|
||||
this = DataFlow::globalVarRef("Buffer").getAMemberInvocation("from")
|
||||
}
|
||||
|
||||
private class BufferTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getArgument(0) and
|
||||
succ = this
|
||||
exists(DataFlow::InvokeNode invoke |
|
||||
invoke = DataFlow::globalVarRef("Buffer").getAnInstantiation()
|
||||
or
|
||||
invoke = DataFlow::globalVarRef("Buffer").getAMemberInvocation("from")
|
||||
|
|
||||
pred = invoke.getArgument(0) and
|
||||
succ = invoke
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,14 +134,12 @@ private class SimplePropertyProjection extends PropertyProjection::Range {
|
||||
/**
|
||||
* A taint step for a property projection.
|
||||
*/
|
||||
private class PropertyProjectionTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
PropertyProjection projection;
|
||||
|
||||
PropertyProjectionTaintStep() { projection = this }
|
||||
|
||||
private class PropertyProjectionTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// reading from a tainted object yields a tainted result
|
||||
this = succ and
|
||||
pred = projection.getObject()
|
||||
exists(PropertyProjection projection |
|
||||
pred = projection.getObject() and
|
||||
succ = projection
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
95
javascript/ql/src/semmle/javascript/frameworks/Puppeteer.qll
Normal file
95
javascript/ql/src/semmle/javascript/frameworks/Puppeteer.qll
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Provides classes and predicates for reasoning about [puppeteer](https://www.npmjs.com/package/puppeteer).
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Classes and predicates modelling the [puppeteer](https://www.npmjs.com/package/puppeteer) library.
|
||||
*/
|
||||
module Puppeteer {
|
||||
/**
|
||||
* A reference to a module import of puppeteer.
|
||||
*/
|
||||
private API::Node puppeteer() { result = API::moduleImport(["puppeteer", "puppeteer-core"]) }
|
||||
|
||||
/**
|
||||
* A reference to a `Browser` from puppeteer.
|
||||
*/
|
||||
private API::Node browser() {
|
||||
result = API::Node::ofType("puppeteer", "Browser")
|
||||
or
|
||||
result = puppeteer().getMember(["launch", "connect"]).getReturn().getPromised()
|
||||
or
|
||||
result = [page(), context(), target()].getMember("browser").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a `Page` from puppeteer.
|
||||
*/
|
||||
API::Node page() {
|
||||
result = API::Node::ofType("puppeteer", "Page")
|
||||
or
|
||||
result = [browser(), context()].getMember("newPage").getReturn().getPromised()
|
||||
or
|
||||
result = [browser(), context()].getMember("pages").getReturn().getPromised().getUnknownMember()
|
||||
or
|
||||
result = target().getMember("page").getReturn().getPromised()
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a `Target` from puppeteer.
|
||||
*/
|
||||
private API::Node target() {
|
||||
result = API::Node::ofType("puppeteer", "Target")
|
||||
or
|
||||
result = [page(), browser()].getMember("target").getReturn()
|
||||
or
|
||||
result = context().getMember("targets").getReturn().getUnknownMember()
|
||||
or
|
||||
result = target().getMember("opener").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a `BrowserContext` from puppeteer.
|
||||
*/
|
||||
private API::Node context() {
|
||||
result = API::Node::ofType("puppeteer", "BrowserContext")
|
||||
or
|
||||
result = [page(), target()].getMember("browserContext").getReturn()
|
||||
or
|
||||
result = browser().getMember("browserContexts").getReturn().getUnknownMember()
|
||||
or
|
||||
result = browser().getMember("createIncognitoBrowserContext").getReturn().getPromised()
|
||||
or
|
||||
result = browser().getMember("defaultBrowserContext").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* A call requesting a `Page` to navigate to some url, seen as a `ClientRequest`.
|
||||
*/
|
||||
private class PuppeteerGotoCall extends ClientRequest::Range, API::InvokeNode {
|
||||
PuppeteerGotoCall() { this = page().getMember("goto").getACall() }
|
||||
|
||||
override DataFlow::Node getUrl() { result = getArgument(0) }
|
||||
|
||||
override DataFlow::Node getHost() { none() }
|
||||
|
||||
override DataFlow::Node getADataNode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call requesting a `Page` to load a stylesheet or script, seen as a `ClientRequest`.
|
||||
*/
|
||||
private class PuppeteerLoadResourceCall extends ClientRequest::Range, API::InvokeNode {
|
||||
PuppeteerLoadResourceCall() {
|
||||
this = page().getMember(["addStyleTag", "addScriptTag"]).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrl() { result = getParameter(0).getMember("url").getARhs() }
|
||||
|
||||
override DataFlow::Node getHost() { none() }
|
||||
|
||||
override DataFlow::Node getADataNode() { none() }
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,12 @@ abstract class ReactComponent extends ASTNode {
|
||||
*/
|
||||
abstract DataFlow::SourceNode getAComponentCreatorReference();
|
||||
|
||||
/**
|
||||
* Gets a reference to an instance of this component.
|
||||
*/
|
||||
pragma[noinline]
|
||||
DataFlow::SourceNode getAnInstanceReference() { result = ref() }
|
||||
|
||||
/**
|
||||
* Gets a reference to this component.
|
||||
*/
|
||||
@@ -70,20 +76,19 @@ abstract class ReactComponent extends ASTNode {
|
||||
* Gets an access to the `state` object of this component.
|
||||
*/
|
||||
DataFlow::SourceNode getADirectStateAccess() {
|
||||
result.(DataFlow::PropRef).accesses(ref(), "state")
|
||||
result = getAnInstanceReference().getAPropertyReference("state")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node that reads a prop of this component.
|
||||
*/
|
||||
DataFlow::PropRead getAPropRead() { getADirectPropsAccess().flowsTo(result.getBase()) }
|
||||
DataFlow::PropRead getAPropRead() { result = getADirectPropsAccess().getAPropertyRead() }
|
||||
|
||||
/**
|
||||
* Gets a data flow node that reads prop `name` of this component.
|
||||
*/
|
||||
DataFlow::PropRead getAPropRead(string name) {
|
||||
result = getAPropRead() and
|
||||
result.getPropertyName() = name
|
||||
result = getADirectPropsAccess().getAPropertyRead(name)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +98,7 @@ abstract class ReactComponent extends ASTNode {
|
||||
DataFlow::SourceNode getAStateAccess() {
|
||||
result = getADirectStateAccess()
|
||||
or
|
||||
exists(DataFlow::PropRef prn | result = prn | getAStateAccess().flowsTo(prn.getBase()))
|
||||
result = getAStateAccess().getAPropertyReference()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,18 +121,17 @@ abstract class ReactComponent extends ASTNode {
|
||||
/**
|
||||
* Gets a call to method `name` on this component.
|
||||
*/
|
||||
DataFlow::MethodCallNode getAMethodCall(string name) { result.calls(ref(), name) }
|
||||
DataFlow::MethodCallNode getAMethodCall(string name) {
|
||||
result = getAnInstanceReference().getAMethodCall(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value that will become (part of) the state
|
||||
* object of this component, for example an assignment to `this.state`.
|
||||
*/
|
||||
DataFlow::SourceNode getACandidateStateSource() {
|
||||
exists(DataFlow::PropWrite pwn, DataFlow::Node rhs |
|
||||
// a direct definition: `this.state = o`
|
||||
result.flowsTo(rhs) and
|
||||
pwn.writes(ref(), "state", rhs)
|
||||
)
|
||||
// a direct definition: `this.state = o`
|
||||
result = getAnInstanceReference().getAPropertySource("state")
|
||||
or
|
||||
exists(DataFlow::MethodCallNode mce, DataFlow::SourceNode arg0 |
|
||||
mce = getAMethodCall("setState") or
|
||||
@@ -314,7 +318,8 @@ abstract private class SharedReactPreactClassComponent extends ReactComponent, C
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getADirectPropsAccess() {
|
||||
result.(DataFlow::PropRef).accesses(ref(), "props") or
|
||||
result = getAnInstanceReference().getAPropertyRead("props")
|
||||
or
|
||||
result = DataFlow::parameterNode(getConstructor().getBody().getParameter(0))
|
||||
}
|
||||
|
||||
@@ -437,7 +442,7 @@ class ES5Component extends ReactComponent, ObjectExpr {
|
||||
override Function getStaticMethod(string name) { none() }
|
||||
|
||||
override DataFlow::SourceNode getADirectPropsAccess() {
|
||||
result.(DataFlow::PropRef).accesses(ref(), "props")
|
||||
result = getAnInstanceReference().getAPropertyRead("props")
|
||||
}
|
||||
|
||||
override AbstractValue getAbstractComponent() { result = TAbstractObjectLiteral(this) }
|
||||
@@ -790,3 +795,46 @@ private class HigherOrderComponentStep extends PreCallGraphStep {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge for assignments of the form `c1.state.p = v`,
|
||||
* where `c1` is an instance of React component `C`; in this case, we consider
|
||||
* taint to flow from `v` to any read of `c2.state.p`, where `c2`
|
||||
* also is an instance of `C`.
|
||||
*/
|
||||
private class StateTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(ReactComponent c, DataFlow::PropRead prn, DataFlow::PropWrite pwn |
|
||||
(
|
||||
c.getACandidateStateSource().flowsTo(pwn.getBase()) or
|
||||
c.getADirectStateAccess().flowsTo(pwn.getBase())
|
||||
) and
|
||||
(
|
||||
c.getAPreviousStateSource().flowsTo(prn.getBase()) or
|
||||
c.getADirectStateAccess().flowsTo(prn.getBase())
|
||||
)
|
||||
|
|
||||
prn.getPropertyName() = pwn.getPropertyName() and
|
||||
succ = prn and
|
||||
pred = pwn.getRhs()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge for assignments of the form `c1.props.p = v`,
|
||||
* where `c1` is an instance of React component `C`; in this case, we consider
|
||||
* taint to flow from `v` to any read of `c2.props.p`, where `c2`
|
||||
* also is an instance of `C`.
|
||||
*/
|
||||
private class PropsTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(ReactComponent c, string name, DataFlow::PropRead prn |
|
||||
prn = c.getAPropRead(name) or
|
||||
prn = c.getAPreviousPropsSource().getAPropertyRead(name)
|
||||
|
|
||||
pred = c.getACandidatePropsValue(name) and
|
||||
succ = prn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ private import javascript
|
||||
/**
|
||||
* A step `x -> y` in `x.subscribe(y => ...)`, modeling flow out of an rxjs Observable.
|
||||
*/
|
||||
private class RxJsSubscribeStep extends TaintTracking::AdditionalTaintStep, DataFlow::MethodCallNode {
|
||||
RxJsSubscribeStep() { getMethodName() = "subscribe" }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getReceiver() and
|
||||
succ = getCallback(0).getParameter(0)
|
||||
private class RxJsSubscribeStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::MethodCallNode call | call.getMethodName() = "subscribe" |
|
||||
pred = call.getReceiver() and
|
||||
succ = call.getCallback(0).getParameter(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,24 +54,24 @@ private predicate isIdentityPipe(DataFlow::CallNode pipe) {
|
||||
/**
|
||||
* A step in or out of the map callback in a call of form `x.pipe(map(y => ...))`.
|
||||
*/
|
||||
private class RxJsPipeMapStep extends TaintTracking::AdditionalTaintStep, DataFlow::MethodCallNode {
|
||||
RxJsPipeMapStep() { getMethodName() = "pipe" }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getReceiver() and
|
||||
succ = pipeInput(getArgument(0).getALocalSource())
|
||||
or
|
||||
exists(int i |
|
||||
pred = pipeOutput(getArgument(i).getALocalSource()) and
|
||||
succ = pipeInput(getArgument(i + 1).getALocalSource())
|
||||
private class RxJsPipeMapStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::MethodCallNode call | call.getMethodName() = "pipe" |
|
||||
pred = call.getReceiver() and
|
||||
succ = pipeInput(call.getArgument(0).getALocalSource())
|
||||
or
|
||||
exists(int i |
|
||||
pred = pipeOutput(call.getArgument(i).getALocalSource()) and
|
||||
succ = pipeInput(call.getArgument(i + 1).getALocalSource())
|
||||
)
|
||||
or
|
||||
pred = pipeOutput(call.getLastArgument().getALocalSource()) and
|
||||
succ = call
|
||||
or
|
||||
// Handle a common case where the last step is `catchError`.
|
||||
isIdentityPipe(call.getLastArgument().getALocalSource()) and
|
||||
pred = pipeOutput(call.getArgument(call.getNumArgument() - 2)) and
|
||||
succ = call
|
||||
)
|
||||
or
|
||||
pred = pipeOutput(getLastArgument().getALocalSource()) and
|
||||
succ = this
|
||||
or
|
||||
// Handle a common case where the last step is `catchError`.
|
||||
isIdentityPipe(getLastArgument().getALocalSource()) and
|
||||
pred = pipeOutput(getArgument(getNumArgument() - 2)) and
|
||||
succ = this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,11 +126,13 @@ module Typeahead {
|
||||
/**
|
||||
* A taint step that models that a function in the `source` of typeahead.js is used to determine the input to the suggestion function.
|
||||
*/
|
||||
private class TypeaheadSourceTaintStep extends TypeaheadSource, TaintTracking::AdditionalTaintStep {
|
||||
private class TypeaheadSourceTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// Matches `$(...).typeahead({..}, {source: function(q, cb) {..;cb(<pred>);..}, templates: { suggestion: function(<succ>) {} } })`.
|
||||
pred = this.getAFunctionValue().getParameter([1 .. 2]).getACall().getAnArgument() and
|
||||
succ = this.getASuggestion()
|
||||
exists(TypeaheadSource typeahead |
|
||||
pred = typeahead.getAFunctionValue().getParameter([1 .. 2]).getACall().getAnArgument() and
|
||||
succ = typeahead.getASuggestion()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `TaintTracking::SharedTaintStep` or `TaintTracking::uriStep` instead.
|
||||
*
|
||||
* A taint propagating data flow edge arising from an operation in a URI library.
|
||||
*/
|
||||
abstract class UriLibraryStep extends DataFlow::ValueNode, TaintTracking::AdditionalTaintStep { }
|
||||
abstract deprecated class UriLibraryStep extends DataFlow::ValueNode {
|
||||
/** Holds if `pred -> succ` is a step through a URI library function. */
|
||||
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes for working with [urijs](http://medialize.github.io/URI.js/) code.
|
||||
@@ -56,26 +61,22 @@ module urijs {
|
||||
/**
|
||||
* A taint step in the urijs library.
|
||||
*/
|
||||
private class Step extends UriLibraryStep {
|
||||
DataFlow::Node src;
|
||||
|
||||
Step() {
|
||||
private class Step extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// flow through "constructors" (`new` is optional)
|
||||
exists(DataFlow::InvokeNode invk | invk = this and invk = invocation() |
|
||||
src = invk.getAnArgument()
|
||||
exists(DataFlow::InvokeNode invk | invk = succ and invk = invocation() |
|
||||
pred = invk.getAnArgument()
|
||||
)
|
||||
or
|
||||
// flow through chained calls
|
||||
exists(DataFlow::MethodCallNode mc | mc = this and this = chainCall() |
|
||||
src = mc.getReceiver() or
|
||||
src = mc.getAnArgument()
|
||||
exists(DataFlow::MethodCallNode mc | mc = succ and succ = chainCall() |
|
||||
pred = mc.getReceiver() or
|
||||
pred = mc.getAnArgument()
|
||||
)
|
||||
or
|
||||
// flow through getter calls
|
||||
exists(DataFlow::MethodCallNode mc | mc = this and this = getter() | src = mc.getReceiver())
|
||||
exists(DataFlow::MethodCallNode mc | mc = succ and succ = getter() | pred = mc.getReceiver())
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { pred = src and succ = this }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,22 +94,19 @@ module uridashjs {
|
||||
/**
|
||||
* A taint step in the urijs library.
|
||||
*/
|
||||
private class Step extends UriLibraryStep, DataFlow::CallNode {
|
||||
DataFlow::Node src;
|
||||
|
||||
Step() {
|
||||
exists(string name |
|
||||
private class Step extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(string name, DataFlow::CallNode call |
|
||||
name = "parse" or
|
||||
name = "serialize" or
|
||||
name = "resolve" or
|
||||
name = "normalize"
|
||||
|
|
||||
this = uridashjsMember(name).getACall() and
|
||||
src = getAnArgument()
|
||||
call = uridashjsMember(name).getACall() and
|
||||
pred = call.getAnArgument() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { pred = src and succ = this }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,22 +124,19 @@ module punycode {
|
||||
/**
|
||||
* A taint step in the punycode library.
|
||||
*/
|
||||
private class Step extends UriLibraryStep, DataFlow::CallNode {
|
||||
DataFlow::Node src;
|
||||
|
||||
Step() {
|
||||
exists(string name |
|
||||
private class Step extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(string name, DataFlow::CallNode call |
|
||||
name = "decode" or
|
||||
name = "encode" or
|
||||
name = "toUnicode" or
|
||||
name = "toASCII"
|
||||
|
|
||||
this = punycodeMember(name).getACall() and
|
||||
src = getAnArgument()
|
||||
call = punycodeMember(name).getACall() and
|
||||
pred = call.getAnArgument() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { pred = src and succ = this }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,24 +157,23 @@ module urlParse {
|
||||
/**
|
||||
* A taint step in the url-parse library.
|
||||
*/
|
||||
private class Step extends UriLibraryStep, DataFlow::CallNode {
|
||||
DataFlow::Node src;
|
||||
|
||||
Step() {
|
||||
// parse(src)
|
||||
this = call() and
|
||||
src = getAnArgument()
|
||||
or
|
||||
exists(DataFlow::MethodCallNode mc | this = mc and mc = call().getAMethodCall("set") |
|
||||
// src = parse(...); src.set(x, y)
|
||||
src = mc.getReceiver()
|
||||
private class Step extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call | succ = call |
|
||||
// parse(pred)
|
||||
call = call() and
|
||||
pred = call.getAnArgument()
|
||||
or
|
||||
// parse(x).set(y, src)
|
||||
src = mc.getArgument(1)
|
||||
call = call().getAMethodCall("set") and
|
||||
(
|
||||
// pred = parse(...); pred.set(x, y)
|
||||
pred = call.getReceiver()
|
||||
or
|
||||
// parse(x).set(y, pred)
|
||||
pred = call.getArgument(1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { pred = src and succ = this }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,20 +191,17 @@ module querystringify {
|
||||
/**
|
||||
* A taint step in the querystringify library.
|
||||
*/
|
||||
private class Step extends UriLibraryStep, DataFlow::CallNode {
|
||||
DataFlow::Node src;
|
||||
|
||||
Step() {
|
||||
exists(string name |
|
||||
private class Step extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(string name, DataFlow::CallNode call |
|
||||
name = "parse" or
|
||||
name = "stringify"
|
||||
|
|
||||
this = querystringifyMember(name).getACall() and
|
||||
src = getAnArgument()
|
||||
call = querystringifyMember(name).getACall() and
|
||||
pred = call.getAnArgument() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { pred = src and succ = this }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,22 +219,19 @@ module querydashstring {
|
||||
/**
|
||||
* A taint step in the query-string library.
|
||||
*/
|
||||
private class Step extends UriLibraryStep, DataFlow::CallNode {
|
||||
DataFlow::Node src;
|
||||
|
||||
Step() {
|
||||
exists(string name |
|
||||
private class Step extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(string name, DataFlow::CallNode call |
|
||||
name = "parse" or
|
||||
name = "extract" or
|
||||
name = "parseUrl" or
|
||||
name = "stringify"
|
||||
|
|
||||
this = querydashstringMember(name).getACall() and
|
||||
src = getAnArgument()
|
||||
call = querydashstringMember(name).getACall() and
|
||||
pred = call.getAnArgument() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { pred = src and succ = this }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,21 +247,18 @@ module url {
|
||||
/**
|
||||
* A taint step in the url library.
|
||||
*/
|
||||
private class Step extends UriLibraryStep, DataFlow::CallNode {
|
||||
DataFlow::Node src;
|
||||
|
||||
Step() {
|
||||
exists(string name |
|
||||
private class Step extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(string name, DataFlow::CallNode call |
|
||||
name = "parse" or
|
||||
name = "format" or
|
||||
name = "resolve"
|
||||
|
|
||||
this = urlMember(name).getACall() and
|
||||
src = getAnArgument()
|
||||
call = urlMember(name).getACall() and
|
||||
pred = call.getAnArgument() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { pred = src and succ = this }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,22 +276,19 @@ module querystring {
|
||||
/**
|
||||
* A taint step in the querystring library.
|
||||
*/
|
||||
private class Step extends UriLibraryStep, DataFlow::CallNode {
|
||||
DataFlow::Node src;
|
||||
|
||||
Step() {
|
||||
exists(string name |
|
||||
private class Step extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(string name, DataFlow::CallNode call |
|
||||
name = "escape" or
|
||||
name = "unescape" or
|
||||
name = "parse" or
|
||||
name = "stringify"
|
||||
|
|
||||
this = querystringMember(name).getACall() and
|
||||
src = getAnArgument()
|
||||
call = querystringMember(name).getACall() and
|
||||
pred = call.getAnArgument() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { pred = src and succ = this }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,56 +299,53 @@ private module ClosureLibraryUri {
|
||||
/**
|
||||
* Taint step from an argument of a `goog.Uri` call to the return value.
|
||||
*/
|
||||
private class ArgumentStep extends UriLibraryStep, DataFlow::InvokeNode {
|
||||
int arg;
|
||||
|
||||
ArgumentStep() {
|
||||
// goog.Uri constructor
|
||||
this = Closure::moduleImport("goog.Uri").getAnInstantiation() and arg = 0
|
||||
or
|
||||
// static methods on goog.Uri
|
||||
exists(string name | this = Closure::moduleImport("goog.Uri." + name).getACall() |
|
||||
name = "parse" and arg = 0
|
||||
private class ArgumentStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::InvokeNode invoke, int arg |
|
||||
pred = invoke.getArgument(arg) and succ = invoke
|
||||
|
|
||||
// goog.Uri constructor
|
||||
invoke = Closure::moduleImport("goog.Uri").getAnInstantiation() and arg = 0
|
||||
or
|
||||
name = "create" and
|
||||
(arg = 0 or arg = 2 or arg = 4)
|
||||
// static methods on goog.Uri
|
||||
exists(string name | invoke = Closure::moduleImport("goog.Uri." + name).getACall() |
|
||||
name = "parse" and arg = 0
|
||||
or
|
||||
name = "create" and
|
||||
(arg = 0 or arg = 2 or arg = 4)
|
||||
or
|
||||
name = "resolve" and
|
||||
(arg = 0 or arg = 1)
|
||||
)
|
||||
or
|
||||
name = "resolve" and
|
||||
(arg = 0 or arg = 1)
|
||||
// static methods in goog.uri.utils
|
||||
arg = 0 and
|
||||
exists(string name | invoke = Closure::moduleImport("goog.uri.utils." + name).getACall() |
|
||||
name = "appendParam" or // preserve taint from the original URI, but not from the appended param
|
||||
name = "appendParams" or
|
||||
name = "appendParamsFromMap" or
|
||||
name = "appendPath" or
|
||||
name = "getParamValue" or
|
||||
name = "getParamValues" or
|
||||
name = "getPath" or
|
||||
name = "getPathAndAfter" or
|
||||
name = "getQueryData" or
|
||||
name = "parseQueryData" or
|
||||
name = "removeFragment" or
|
||||
name = "removeParam" or
|
||||
name = "setParam" or
|
||||
name = "setParamsFromMap" or
|
||||
name = "setPath" or
|
||||
name = "split"
|
||||
)
|
||||
or
|
||||
// static methods in goog.string
|
||||
arg = 0 and
|
||||
exists(string name | invoke = Closure::moduleImport("goog.string." + name).getACall() |
|
||||
name = "urlDecode" or
|
||||
name = "urlEncode"
|
||||
)
|
||||
)
|
||||
or
|
||||
// static methods in goog.uri.utils
|
||||
arg = 0 and
|
||||
exists(string name | this = Closure::moduleImport("goog.uri.utils." + name).getACall() |
|
||||
name = "appendParam" or // preserve taint from the original URI, but not from the appended param
|
||||
name = "appendParams" or
|
||||
name = "appendParamsFromMap" or
|
||||
name = "appendPath" or
|
||||
name = "getParamValue" or
|
||||
name = "getParamValues" or
|
||||
name = "getPath" or
|
||||
name = "getPathAndAfter" or
|
||||
name = "getQueryData" or
|
||||
name = "parseQueryData" or
|
||||
name = "removeFragment" or
|
||||
name = "removeParam" or
|
||||
name = "setParam" or
|
||||
name = "setParamsFromMap" or
|
||||
name = "setPath" or
|
||||
name = "split"
|
||||
)
|
||||
or
|
||||
// static methods in goog.string
|
||||
arg = 0 and
|
||||
exists(string name | this = Closure::moduleImport("goog.string." + name).getACall() |
|
||||
name = "urlDecode" or
|
||||
name = "urlEncode"
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getArgument(arg) and
|
||||
succ = this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +354,7 @@ private module ClosureLibraryUri {
|
||||
*
|
||||
* Setters mutate the URI object and return the same instance.
|
||||
*/
|
||||
private class SetterCall extends DataFlow::MethodCallNode, UriLibraryStep {
|
||||
private class SetterCall extends DataFlow::MethodCallNode {
|
||||
DataFlow::NewNode uri;
|
||||
string name;
|
||||
|
||||
@@ -393,12 +372,19 @@ private module ClosureLibraryUri {
|
||||
|
||||
DataFlow::NewNode getUri() { result = uri }
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getReceiver() and succ = this
|
||||
or
|
||||
(name = "setDomain" or name = "setPath" or name = "setScheme") and
|
||||
pred = getArgument(0) and
|
||||
succ = uri
|
||||
string getName() { result = name }
|
||||
}
|
||||
|
||||
/** A taint step derived from a setter call on a `goog.Uri` object. */
|
||||
private class SetterCallStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(SetterCall call |
|
||||
pred = call.getReceiver() and succ = call
|
||||
or
|
||||
(call.getName() = "setDomain" or call.getName() = "setPath" or call.getName() = "setScheme") and
|
||||
pred = call.getArgument(0) and
|
||||
succ = call.getUri()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,25 +395,20 @@ private module ClosureLibraryUri {
|
||||
/**
|
||||
* A taint step in the path module.
|
||||
*/
|
||||
private class Step extends UriLibraryStep, DataFlow::CallNode {
|
||||
DataFlow::Node src;
|
||||
|
||||
Step() {
|
||||
exists(DataFlow::SourceNode ref |
|
||||
private class Step extends TaintTracking::SharedTaintStep {
|
||||
override predicate uriStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::SourceNode ref, DataFlow::CallNode call |
|
||||
ref = NodeJSLib::Path::moduleMember("parse") or
|
||||
// a ponyfill: https://www.npmjs.com/package/path-parse
|
||||
ref = DataFlow::moduleImport("path-parse") or
|
||||
ref = DataFlow::moduleMember("path-parse", "posix") or
|
||||
ref = DataFlow::moduleMember("path-parse", "win32")
|
||||
|
|
||||
this = ref.getACall() and
|
||||
src = getAnArgument()
|
||||
call = ref.getACall() and
|
||||
pred = call.getAnArgument() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = src and succ = this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,20 +303,24 @@ module Vue {
|
||||
result = getOptionSource(_).getAPropertySource()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private DataFlow::PropWrite getAPropertyValueWrite(string name) {
|
||||
result = getData().getALocalSource().getAPropertyWrite(name)
|
||||
or
|
||||
result =
|
||||
getABoundFunction()
|
||||
.getALocalSource()
|
||||
.(DataFlow::FunctionNode)
|
||||
.getReceiver()
|
||||
.getAPropertyWrite(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data flow node that flows into the property `name` of this instance, or is
|
||||
* returned form a getter defining that property.
|
||||
*/
|
||||
DataFlow::Node getAPropertyValue(string name) {
|
||||
exists(DataFlow::SourceNode obj | obj.getAPropertyWrite(name).getRhs() = result |
|
||||
obj.flowsTo(getData())
|
||||
or
|
||||
exists(DataFlow::FunctionNode bound |
|
||||
bound.flowsTo(getABoundFunction()) and
|
||||
not bound.getFunction() instanceof ArrowFunctionExpr and
|
||||
obj = bound.getReceiver()
|
||||
)
|
||||
)
|
||||
result = getAPropertyValueWrite(name).getRhs()
|
||||
or
|
||||
exists(DataFlow::FunctionNode getter |
|
||||
getter.flowsTo(getAccessor(name, DataFlow::MemberKind::getter())) and
|
||||
@@ -490,19 +494,53 @@ module Vue {
|
||||
/**
|
||||
* A taint propagating data flow edge through a Vue instance property.
|
||||
*/
|
||||
class InstanceHeapStep extends TaintTracking::AdditionalTaintStep {
|
||||
DataFlow::Node src;
|
||||
|
||||
InstanceHeapStep() {
|
||||
class InstanceHeapStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Instance i, string name, DataFlow::FunctionNode bound |
|
||||
bound.flowsTo(i.getABoundFunction()) and
|
||||
not bound.getFunction() instanceof ArrowFunctionExpr and
|
||||
bound.getReceiver().getAPropertyRead(name) = this and
|
||||
src = i.getAPropertyValue(name)
|
||||
succ = bound.getReceiver().getAPropertyRead(name) and
|
||||
pred = i.getAPropertyValue(name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { pred = src and succ = this }
|
||||
/**
|
||||
* A Vue `v-html` attribute.
|
||||
*/
|
||||
class VHtmlAttribute extends DataFlow::Node {
|
||||
HTML::Attribute attr;
|
||||
|
||||
VHtmlAttribute() {
|
||||
this.(DataFlow::HtmlAttributeNode).getAttribute() = attr and attr.getName() = "v-html"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTML attribute of this sink.
|
||||
*/
|
||||
HTML::Attribute getAttr() { result = attr }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge through a string interpolation of a
|
||||
* Vue instance property to a `v-html` attribute.
|
||||
*
|
||||
* As an example, `<div v-html="prop"/>` reads the `prop` property
|
||||
* of `inst = new Vue({ ..., data: { prop: source } })`, if the
|
||||
* `div` element is part of the template for `inst`.
|
||||
*/
|
||||
class VHtmlSourceWrite extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Vue::Instance instance, string expr, VHtmlAttribute attr |
|
||||
attr.getAttr().getRoot() =
|
||||
instance.getTemplateElement().(Vue::Template::HtmlElement).getElement() and
|
||||
expr = attr.getAttr().getValue() and
|
||||
// only support for simple identifier expressions
|
||||
expr.regexpMatch("(?i)[a-z0-9_]+") and
|
||||
pred = instance.getAPropertyValue(expr) and
|
||||
succ = attr
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -81,7 +81,7 @@ module ClientWebSocket {
|
||||
/**
|
||||
* A client WebSocket instance.
|
||||
*/
|
||||
class ClientSocket extends EventEmitter::Range, DataFlow::SourceNode {
|
||||
class ClientSocket extends EventEmitter::Range, DataFlow::NewNode, ClientRequest::Range {
|
||||
SocketClass socketClass;
|
||||
|
||||
ClientSocket() { this = socketClass.getAnInstantiation() }
|
||||
@@ -90,6 +90,26 @@ module ClientWebSocket {
|
||||
* Gets the WebSocket library name.
|
||||
*/
|
||||
LibraryName getLibrary() { result = socketClass.getLibrary() }
|
||||
|
||||
override DataFlow::Node getUrl() { result = getArgument(0) }
|
||||
|
||||
override DataFlow::Node getHost() { none() }
|
||||
|
||||
override DataFlow::Node getADataNode() {
|
||||
exists(SendNode send |
|
||||
send.getEmitter() = this and
|
||||
result = send.getSentItem(_)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
|
||||
responseType = "json" and
|
||||
promise = false and
|
||||
exists(WebSocketReceiveNode receiver |
|
||||
receiver.getEmitter() = this and
|
||||
result = receiver.getReceivedItem(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -276,14 +276,12 @@ module XML {
|
||||
}
|
||||
}
|
||||
|
||||
private class XMLParserTaintStep extends js::TaintTracking::AdditionalTaintStep {
|
||||
XML::ParserInvocation parser;
|
||||
|
||||
XMLParserTaintStep() { this.asExpr() = parser }
|
||||
|
||||
override predicate step(js::DataFlow::Node pred, js::DataFlow::Node succ) {
|
||||
pred.asExpr() = parser.getSourceArgument() and
|
||||
succ = parser.getAResult()
|
||||
private class XMLParserTaintStep extends js::TaintTracking::SharedTaintStep {
|
||||
override predicate deserializeStep(js::DataFlow::Node pred, js::DataFlow::Node succ) {
|
||||
exists(XML::ParserInvocation parser |
|
||||
pred.asExpr() = parser.getSourceArgument() and
|
||||
succ = parser.getAResult()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Provides classes that heuristically increase the extent of `TaintTracking::AdditionalTaintStep`.
|
||||
* Provides classes that heuristically increase the extent of `TaintTracking::SharedTaintStep`.
|
||||
*
|
||||
* Note: This module should not be a permanent part of the standard library imports.
|
||||
*/
|
||||
@@ -7,16 +7,22 @@
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A heuristic additional flow step in a security query.
|
||||
* DEPRECATED.
|
||||
*
|
||||
* The target of a heuristic additional flow step in a security query.
|
||||
*/
|
||||
abstract class HeuristicAdditionalTaintStep extends DataFlow::ValueNode { }
|
||||
deprecated class HeuristicAdditionalTaintStep extends DataFlow::Node {
|
||||
HeuristicAdditionalTaintStep() { any(TaintTracking::SharedTaintStep step).heuristicStep(_, this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `tainted.replace(x, y)` that preserves taint.
|
||||
*/
|
||||
private class HeuristicStringManipulationTaintStep extends HeuristicAdditionalTaintStep,
|
||||
TaintTracking::AdditionalTaintStep, StringReplaceCall {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getReceiver() and succ = this
|
||||
private class HeuristicStringManipulationTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate heuristicStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(StringReplaceCall call |
|
||||
pred = call.getReceiver() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ module Stages {
|
||||
predicate backref() {
|
||||
1 = 1
|
||||
or
|
||||
any(TaintTracking::AdditionalTaintStep step).step(_, _)
|
||||
TaintTracking::heapStep(_, _)
|
||||
or
|
||||
exists(RemoteFlowSource r)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,9 @@ class StringReplaceCallSequence extends DataFlow::CallNode {
|
||||
|
||||
/** Gets a string that is the replacement of this call. */
|
||||
string getAReplacementString() {
|
||||
// this is more restrictive than `StringReplaceCall::replaces/2`, in the name of precision
|
||||
getAMember().replaces(_, result)
|
||||
or
|
||||
// StringReplaceCall::replaces/2 can't always find the `old` string, so this is added as a fallback.
|
||||
getAMember().getRawReplacement().getStringValue() = result
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@ module DomBasedXss {
|
||||
*/
|
||||
deprecated class Configuration = HtmlInjectionConfiguration;
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `Vue::VHtmlSourceWrite` instead.
|
||||
*/
|
||||
deprecated class VHtmlSourceWrite = Vue::VHtmlSourceWrite;
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about XSS.
|
||||
*/
|
||||
@@ -71,11 +76,9 @@ module DomBasedXss {
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
|
||||
DataFlow::FlowLabel succlbl
|
||||
) {
|
||||
exists(TaintTracking::AdditionalTaintStep step |
|
||||
step.step(pred, succ) and
|
||||
predlbl.isData() and
|
||||
succlbl.isTaint()
|
||||
)
|
||||
TaintTracking::sharedTaintStep(pred, succ) and
|
||||
predlbl.isData() and
|
||||
succlbl.isTaint()
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
|
||||
@@ -255,7 +255,7 @@ module ExternalAPIUsedWithUntrustedData {
|
||||
not exists(DataFlow::Node arg |
|
||||
arg = this.getAnArgument() and not arg instanceof DeepObjectSink
|
||||
|
|
||||
any(TaintTracking::AdditionalTaintStep s).step(arg, _)
|
||||
TaintTracking::sharedTaintStep(arg, _)
|
||||
or
|
||||
exists(DataFlow::AdditionalFlowStep s |
|
||||
s.step(arg, _) or
|
||||
|
||||
@@ -52,7 +52,7 @@ private DataFlow::SourceNode argumentList(SystemCommandExecution sys, DataFlow::
|
||||
result = pred.backtrack(t2, t)
|
||||
or
|
||||
t = t2.continue() and
|
||||
TaintTracking::arrayFunctionTaintStep(any(DataFlow::Node n | result.flowsTo(n)), pred, _)
|
||||
TaintTracking::arrayStep(any(DataFlow::Node n | result.flowsTo(n)), pred)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ module PrototypePollutingAssignment {
|
||||
// users wouldn't bother to call Object.create in that case.
|
||||
result = DataFlow::globalVarRef("Object").getAMemberCall("create")
|
||||
or
|
||||
// Allow use of AdditionalFlowSteps and AdditionalTaintSteps to track a bit further
|
||||
// Allow use of AdditionalFlowSteps to track a bit further
|
||||
exists(DataFlow::Node mid |
|
||||
prototypeLessObject(t.continue()).flowsTo(mid) and
|
||||
any(DataFlow::AdditionalFlowStep s).step(mid, result)
|
||||
|
||||
@@ -636,6 +636,20 @@ module TaintedPath {
|
||||
SendPathSink() { this = DataFlow::moduleImport("send").getACall().getArgument(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A path argument given to a `Page` in puppeteer, specifying where a pdf/screenshot should be saved.
|
||||
*/
|
||||
private class PuppeteerPath extends TaintedPath::Sink {
|
||||
PuppeteerPath() {
|
||||
this =
|
||||
Puppeteer::page()
|
||||
.getMember(["pdf", "screenshot"])
|
||||
.getParameter(0)
|
||||
.getMember("path")
|
||||
.getARhs()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a step `src -> dst` mapping `srclabel` to `dstlabel` relevant for path traversal vulnerabilities.
|
||||
*/
|
||||
@@ -649,7 +663,7 @@ module TaintedPath {
|
||||
srclabel instanceof Label::PosixPath and
|
||||
dstlabel instanceof Label::PosixPath and
|
||||
(
|
||||
any(UriLibraryStep step).step(src, dst)
|
||||
TaintTracking::uriStep(src, dst)
|
||||
or
|
||||
exists(DataFlow::CallNode decode |
|
||||
decode.getCalleeName() = "decodeURIComponent" or decode.getCalleeName() = "decodeURI"
|
||||
@@ -659,9 +673,9 @@ module TaintedPath {
|
||||
)
|
||||
)
|
||||
or
|
||||
promiseTaintStep(src, dst) and srclabel = dstlabel
|
||||
TaintTracking::promiseStep(src, dst) and srclabel = dstlabel
|
||||
or
|
||||
any(TaintTracking::PersistentStorageTaintStep st).step(src, dst) and srclabel = dstlabel
|
||||
TaintTracking::persistentStorageStep(src, dst) and srclabel = dstlabel
|
||||
or
|
||||
exists(DataFlow::PropRead read | read = dst |
|
||||
src = read.getBase() and
|
||||
|
||||
@@ -28,19 +28,13 @@ module Shared {
|
||||
abstract class SanitizerGuard extends TaintTracking::SanitizerGuardNode { }
|
||||
|
||||
/**
|
||||
* A global regexp replacement involving an HTML meta-character, viewed as a sanitizer for
|
||||
* A global regexp replacement involving the `<`, `'`, or `"` meta-character, viewed as a sanitizer for
|
||||
* XSS vulnerabilities.
|
||||
*
|
||||
* The XSS queries do not attempt to reason about correctness or completeness of sanitizers,
|
||||
* so any such replacement stops taint propagation.
|
||||
*/
|
||||
class MetacharEscapeSanitizer extends Sanitizer, StringReplaceCall {
|
||||
MetacharEscapeSanitizer() {
|
||||
this.isGlobal() and
|
||||
exists(RegExpConstant c |
|
||||
c.getLiteral() = getRegExp().asExpr() and
|
||||
c.getValue().regexpMatch("['\"&<>]")
|
||||
)
|
||||
isGlobal() and
|
||||
RegExp::alwaysMatchesMetaCharacter(getRegExp().getRoot(), ["<", "'", "\""])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,45 +332,7 @@ module DomBasedXss {
|
||||
/**
|
||||
* A Vue `v-html` attribute, viewed as an XSS sink.
|
||||
*/
|
||||
class VHtmlSink extends DomBasedXss::Sink {
|
||||
HTML::Attribute attr;
|
||||
|
||||
VHtmlSink() {
|
||||
this.(DataFlow::HtmlAttributeNode).getAttribute() = attr and attr.getName() = "v-html"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTML attribute of this sink.
|
||||
*/
|
||||
HTML::Attribute getAttr() { result = attr }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge through a string interpolation of a
|
||||
* Vue instance property to a `v-html` attribute.
|
||||
*
|
||||
* As an example, `<div v-html="prop"/>` reads the `prop` property
|
||||
* of `inst = new Vue({ ..., data: { prop: source } })`, if the
|
||||
* `div` element is part of the template for `inst`.
|
||||
*/
|
||||
class VHtmlSourceWrite extends TaintTracking::AdditionalTaintStep {
|
||||
VHtmlSink attr;
|
||||
|
||||
VHtmlSourceWrite() {
|
||||
exists(Vue::Instance instance, string expr |
|
||||
attr.getAttr().getRoot() =
|
||||
instance.getTemplateElement().(Vue::Template::HtmlElement).getElement() and
|
||||
expr = attr.getAttr().getValue() and
|
||||
// only support for simple identifier expressions
|
||||
expr.regexpMatch("(?i)[a-z0-9_]+") and
|
||||
this = instance.getAPropertyValue(expr)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this and succ = attr
|
||||
}
|
||||
}
|
||||
class VHtmlSink extends Vue::VHtmlAttribute, DomBasedXss::Sink { }
|
||||
|
||||
/**
|
||||
* A property read from a safe property is considered a sanitizer.
|
||||
|
||||
Reference in New Issue
Block a user