Merge branch 'main' into koa

This commit is contained in:
Erik Krogh Kristensen
2021-03-19 16:56:15 +01:00
302 changed files with 6279 additions and 2265 deletions

View File

@@ -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
)
}

View File

@@ -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>

View File

@@ -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."

View File

@@ -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>

View File

@@ -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`"

View File

@@ -0,0 +1,8 @@
on: issue_comment
jobs:
echo-body:
runs-on: ubuntu-latest
steps:
- run: |
echo '${{ github.event.comment.body }}'

View File

@@ -0,0 +1,10 @@
on: issue_comment
jobs:
echo-body:
runs-on: ubuntu-latest
steps:
- env:
BODY: ${{ github.event.issue.body }}
run: |
echo '$BODY'

View File

@@ -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!'
});

View File

@@ -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!

View File

@@ -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/

View 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)
}
}
}

View File

@@ -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")
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 |

View File

@@ -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, _)
}
}

View File

@@ -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()
)
}
}
}

View File

@@ -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
)
}
}

View File

@@ -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 }
}
}

View File

@@ -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())
)
}
}

View File

@@ -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()
)
}
}

View File

@@ -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` &rarr; `succ` should be considered a data flow edge.
*/
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
/**
* Holds if `pred` &rarr; `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.
*

View File

@@ -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

View File

@@ -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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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` &rarr; `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)
}
}

View File

@@ -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" }
}

View File

@@ -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
)
}
}
}

View File

@@ -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"
)
}
}

View File

@@ -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()
)
}
}

View File

@@ -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
}
}
}

View File

@@ -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
)
}
}

View File

@@ -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
)
}
}
}

View File

@@ -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))
}

View File

@@ -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)
)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
)
}
}

View File

@@ -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
)
}
}

View File

@@ -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
)
}
}

View 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() }
}
}

View File

@@ -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
)
}
}

View File

@@ -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
}
}

View File

@@ -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()
)
}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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
)
}
}
/*

View File

@@ -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(_)
)
}
}
/**

View File

@@ -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()
)
}
}
}

View File

@@ -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
)
}
}

View File

@@ -243,7 +243,7 @@ module Stages {
predicate backref() {
1 = 1
or
any(TaintTracking::AdditionalTaintStep step).step(_, _)
TaintTracking::heapStep(_, _)
or
exists(RemoteFlowSource r)
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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)
)
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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.