Merge pull request #10490 from asgerf/js/remove-old-docs

JS: Remove old Portal-based flow summary implementation
This commit is contained in:
Asger F
2022-09-22 16:01:30 +02:00
committed by GitHub
20 changed files with 0 additions and 962 deletions

View File

@@ -1,227 +0,0 @@
Summary-based information flow analysis
=======================================
Overview
--------
This document presents an approach for running information flow analyses (such as the standard
security queries) on an application that depends on one or more npm packages. Instead of
installing the npm packages during the snapshot build and analyzing them together with application
code, we analyze each package in isolation and compute *flow summaries* that record information
about any sources, sinks and flow steps contributed by the package's API. These flow summaries
are then imported when building a snapshot of the application (usually in the form of CSV files
added as external data), and are picked up by the standard security queries, allowing them to reason
about flow into, out of and through the npm packages as though they had been included as part of the
build.
Note that flow summaries are an experimental technology, and not ready to be used in production
queries or libraries. Also note that flow summaries do not currently work with CodeQL, but require
the legacy Semmle Core toolchain.
Motivating example
------------------
Let us take the `mkdirp <https://www.npmjs.com/package/mkdirp>`_ package as an example. It exports
a function that takes as its first argument a file system path, and creates a folder with that
path, as well as any parent folders that do not exist yet. As further arguments, the function
accepts an optional configuration object and a callback to invoke once the folder has been
created.
An application might use this package as follows:
.. code-block:: js
const mkdirp = require('mkdirp');
// ...
mkdirp(p, opts, function cb(err) {
// ...
});
If the value of ``p`` can be controlled by an untrusted user, this would allow them to create arbitrary
folders, which may not be desirable.
By analyzing the application code base together with the source code for the ``mkdirp`` package,
the default path injection analysis would be able to track taint through the call to ``mkdirp`` into its
implementation, which ultimately uses built-in Node.js file system APIs to create the folder. Since
the path injection analysis has built-in models of these APIs it would then be able to spot and flag this
vulnerability.
However, analyzing ``mkdirp`` from scratch for every client application is wasteful. Moreover, it would
in this case be undesirable to flag the location inside ``mkdirp`` where the folder is actually created
as part of the alert: the developer of the client application did not write that code and hence will
have a hard time understanding why it is being flagged.
Both of these concerns can be addressed by treating the first argument to ``mkdirp`` as a path injection
sink in its own right: the analysis no longer needs to track flow into the implementation of ``mkdirp``,
so we would no longer need to include its source code in the analysis, and the alert would flag the call
to ``mkdirp`` in application code, not its implementation in library code.
The information that the first parameter of ``mkdirp`` is interpreted as a file system path and hence should
be considered a path injection sink is an example of a *flow summary*, or more precisely a *sink summary*.
Besides sink summaries, we also consider *source summaries* and *flow-step summaries*.
In general, a sink summary states that some API interface point (such as a function parameter) should
be considered a sink for a certain analysis, so if data from a known source reaches this point without
undergoing appropriate sanitization, it should be flagged with an alert. A sink summary may also
specify which taint kind the data needs to have in order for the sink to be problematic.
Conversely, a source summary identifies some API (such as the return value of a function) as a source
of tainted data for a certain analysis, again optionally specifying a taint kind.
Finally, a flow-step summary records the fact that data that flows into the package at some point
may propagate to another point (for example, from a function parameter to its return value).
In this case, there are two relevant taint kinds, one describing the kind of taint data has that
enters, and one describing the taint of the data that emerges. In general, flow steps (like sources
and sinks) are analysis-specific, since we need to know about sanitizers.
In what follows we will first discuss how summaries are generated from a snapshot of an npm package,
and then how they are imported when analyzing client code. Finally, we will discuss the format in which
flow summaries are stored.
Note that flow summaries are considered an experimental feature at this point. Using them involves
some manual configuration, and we make no guarantee that the API will remain stable.
Generating summaries
--------------------
Flow summaries of an npm package can be generated by running special summary extraction queries
either on a snapshot of the package itself, or on a snapshot of a hand-written model of the
package. (Note that this requires a working installation of Semmle Core.)
There are three default summary extraction queries:
- Extract flow step summaries (``js/step-summary-extraction``,
``experimental/Summaries/ExtractSourceSummaries.ql``)
- Extract sink summaries (``js/sink-summary-extraction``,
``experimental/Summaries/ExtractSinkSummaries.ql``)
- Extract source summaries (``js/source-summary-extraction``,
``experimental/Summaries/ExtractSourceSummaries.ql``)
You can run these queries individually against a snapshot of the npm package you want to create
flow summaries for using ``odasa runQuery``, and store the output as CSV files named
``additional-steps.csv``, ``additional-sinks.csv`` and ``additional-sources.csv``, respectively.
For example, assuming that folder ``mkdirp-snapshot`` contains a snapshot of the ``mkdirp``
project, we can extract sink summaries using the command
.. code-block:: bash
odasa runQuery \
--query $SEMMLE_DIST/queries/semmlecode-javascript-queries/experimental/Summaries/ExtractSinkSummaries.ql \
--output-file additional-sinks.csv --snapshot mkdirp-snapshot
Instead of generating summaries directly from the package source code, you can also generate
them from a hand-written model of the package. The model should contain a ``package.json`` file
giving the correct package name, and models for the relevant API entry points. The models are
plain JavaScript with special comments annotating certain expressions as sources or sinks.
For example, a model of ``mkdirp`` might look like this:
.. code-block:: js
module.exports = function mkdirp(path) {
path /* Semmle: sink: taint, TaintedPath */
};
Annotation comments start with ``Semmle:``, and contain ``source`` and ``sink`` specifications.
Each such specification lists a flow label (in this case, ``taint``) and a configuration to which
the specification applies (in this case, ``TaintedPath``).
A source specification annotates an expression as being a source of flow with the given label
for the purposes of the given configuration, and similar for sinks. Annotation comments apply to
any expression (and more generally any data flow node) whose source location ends on the line
where the comment starts.
Using summaries
---------------
Once you have created summaries using the approach outlined above, you have two options for
including them in the analysis of a client application.
External data
:::::::::::::
Firstly, you can include the CSV files generated by running the extraction queries as external
data when building a snapshot of the client application by copying them into the
``$snapshot/external/data`` folder. This is typically done by including a command like this
in your ``project`` file:
.. code-block:: xml
<build>cp /path/to/additional-sinks.csv ${snapshot}/external/data</build>
If you want to include summaries for multiple libraries, you have to concatenate the
corresponding CSV files before copying them into the external data folder.
Additionally, you need to import the library ``Security.Summaries.ImportFromCsv`` in your
``javascript.qll``, which will pick up the summaries from external data and interpret them
as additional sources, sinks and flow steps:
.. code-block:: ql
import Security.Summaries.ImportFromCsv
After these preparatory steps, you can run your analysis without any further changes.
External predicates
:::::::::::::::::::
The second method for including flow summaries is by including the
``Security.Summaries.ImportFromExternalPredicates`` library in your analysis, which declares
three external predicates ``additionalSteps``, ``additionalSinks`` and ``additionalSources`` that
need to be instantiated with the flow summary CSV data.
This is most easily done in QL for Eclipse, which will prompt you for CSV files to populate
the three predicates.
This approach has the advantage that you do not need to include the CSV files during the
snapshot build, so you can use an existing snapshot, for example as downloaded from LGTM.com.
Summary format
--------------
Source and sink summaries are specified as tuples of the form ``(portal, kind, configuration)``,
where ``portal`` is a description of the API element being marked as a source or sink, ``kind``
is a flow label (also known as "taint kind") describing the kind of information being generated
or consumed, and ``configuration`` specifies which flow configuration the summary applies to.
If ``kind`` is empty, it defaults to ``data`` for sources and either ``data`` or ``taint`` for sinks.
If ``configuration`` is empty, the specification applies to all configurations.
The default extraction queries never produce empty ``kind`` or ``configuration`` columns.
Similarly, step summaries are tuples of the form
``(inPortal, inKind, outPortal, outKind, configuration)``, stating that information with label
``inKind`` that flows into ``inPortal`` resurfaces from ``outPortal``, now having kind ``outKind``.
As before, ``configuration`` specifies which configuration this information applies to.
In all of the above, ``portal`` is an S-expression that abstractly describes a *portal*, that is,
an API interface point by which data may enter or leave the npm package being analyzed.
Currently, we model five kinds of portals:
- ``(root <uri>)``, representing the ``module`` object of the main module of the npm package
described by ``<uri>``, which is a URL of the form ``https://www.npmjs.com/package/<pkg>``;
- ``(member <name> <base>)``, representing property ``<name>`` of an object described by
portal ``<base>``;
- ``(instance <base>)``, representing an instance of a (constructor) function or class
described by portal ``base``;
- ``(parameter <i> <base>)``, representing the ``i`` th parameter of a function described by
portal ``base``;
- ``(return <base>)``, representing the return value of a function described by portal ``base``.
In our example above, the first parameter of the default export of package ``mkdirp`` is
described by the portal
.. code-block:: lisp
(parameter 0 (member default (root https://www.npmjs.com/package/mkdirp))
As a more complicated example,
.. code-block:: lisp
(parameter 0 (parameter 1 (member then (instance (member Promise (root https://www.npmjs.com/package/bluebird))))))
describes the first parameter of a function passed as second argument to the ``then`` method of
the ``Promise`` constructor exported by package ``bluebird``.

View File

@@ -1,9 +0,0 @@
/**
* Imports the standard library and any taint-tracking configuration classes for which
* flow summarization should be enabled.
*
* To enable flow summarization for other queries, import their configuration classes here.
*/
import javascript
import semmle.javascript.security.dataflow.CodeInjection

View File

@@ -1,35 +0,0 @@
/**
* @name Extract flow step summaries
* @description Extracts flow step summaries, that is, tuples `(p1, lbl1, p2, lbl2, cfg)`
* representing the fact that data with flow label `lbl1` may flow from a
* user-controlled exit node of portal `p1` to an escaping entry node of portal `p2`,
* and have label `lbl2` at that point. Moreover, the path from `p1` to `p2` contains
* no sanitizers specified by configuration `cfg`.
* @kind table
* @id js/step-summary-extraction
*/
import Configurations
import PortalExitSource
import PortalEntrySink
from
TaintTracking::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p1, Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2,
DataFlow::MidPathNode last
where
cfg = source.getConfiguration() and
last = source.getASuccessor*() and
sink = last.getASuccessor() and
p1 = source.getNode().(PortalExitSource).getPortal() and
p2 = sink.getNode().(PortalEntrySink).getPortal() and
lbl1 = last.getPathSummary().getStartLabel() and
lbl2 = last.getPathSummary().getEndLabel() and
// avoid constructing infeasible paths
last.getPathSummary().hasCall() = false and
last.getPathSummary().hasReturn() = false and
// restrict to steps flow function parameters to returns
p1.(ParameterPortal).getBasePortal() = p2.(ReturnPortal).getBasePortal() and
// restrict to data/taint flow
lbl1 instanceof DataFlow::StandardFlowLabel
select p1.toString(), lbl1.toString(), p2.toString(), lbl2.toString(), cfg.toString()

View File

@@ -1,24 +0,0 @@
/**
* @name Extract sink summaries
* @description Extracts sink summaries, that is, tuples `(p, lbl, cfg)` representing the fact
* that data with flow label `lbl` may flow from a user-controlled exit node of portal
* `p` to a known sink for configuration `cfg`.
* @kind table
* @id js/sink-summary-extraction
*/
import Configurations
import PortalExitSource
import SinkFromAnnotation
from
DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p, DataFlow::MidPathNode last
where
cfg = source.getConfiguration() and
last = source.getASuccessor*() and
sink = last.getASuccessor() and
p = source.getNode().(PortalExitSource).getPortal() and
// avoid constructing infeasible paths
last.getPathSummary().hasReturn() = false
select p.toString(), last.getPathSummary().getStartLabel().toString(), cfg.toString()

View File

@@ -1,24 +0,0 @@
/**
* @name Extract source summaries
* @description Extracts source summaries, that is, tuples `(p, lbl, cfg)` representing the fact
* that data may flow from a known source for configuration `cfg` to an escaping entry
* node of portal `p`, and have flow label `lbl` at that point.
* @kind table
* @id js/source-summary-extraction
*/
import Configurations
import PortalEntrySink
import SourceFromAnnotation
from
DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p, DataFlow::MidPathNode last
where
cfg = source.getConfiguration() and
last = source.getASuccessor*() and
sink = last.getASuccessor() and
p = sink.getNode().(PortalEntrySink).getPortal() and
// avoid constructing infeasible paths
last.getPathSummary().hasCall() = false
select p.toString(), last.getPathSummary().getEndLabel().toString(), cfg.toString()

View File

@@ -1,163 +0,0 @@
/**
* Provides classes for importing source, sink and flow step summaries
* from external CSV data added at snapshot build time.
*/
import javascript
import semmle.javascript.dataflow.Portals
import external.ExternalArtifact
private import Shared
/**
* An additional source specified in an `additional-sources.csv` file.
*/
class AdditionalSourceSpec extends ExternalData {
AdditionalSourceSpec() { this.getDataPath() = "additional-sources.csv" }
/**
* Gets the portal of this additional source.
*/
Portal getPortal() { result.toString() = this.getField(0) }
/**
* Gets the flow label of this source.
*/
DataFlow::FlowLabel getFlowLabel() { sourceFlowLabelSpec(result, this.getField(1)) }
/**
* Gets the configuration for which this is a source, or any
* configuration if this source does not specify a configuration.
*/
DataFlow::Configuration getConfiguration() { configSpec(result, this.getField(2)) }
override string toString() {
exists(string config |
if this.getField(2) = ""
then config = "any configuration"
else config = this.getConfiguration()
|
result = this.getPortal() + " as " + this.getFlowLabel() + " source for " + config
)
}
}
private class AdditionalSourceFromSpec extends DataFlow::AdditionalSource {
AdditionalSourceSpec spec;
AdditionalSourceFromSpec() { this = spec.getPortal().getAnExitNode(_) }
override predicate isSourceFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
cfg = spec.getConfiguration() and lbl = spec.getFlowLabel()
}
}
/**
* An additional sink specified in an `additional-sinks.csv` file.
*/
class AdditionalSinkSpec extends ExternalData {
AdditionalSinkSpec() { this.getDataPath() = "additional-sinks.csv" }
/**
* Gets the portal specification of this additional sink.
*/
Portal getPortal() { result.toString() = this.getField(0) }
/**
* Gets the flow label of this sink, or any standard flow label if this sink
* does not specify a flow label.
*/
DataFlow::FlowLabel getFlowLabel() { sinkFlowLabelSpec(result, this.getField(1)) }
/**
* Gets the configuration for which this is a sink, or any configuration if
* this sink does not specify a configuration.
*/
DataFlow::Configuration getConfiguration() { configSpec(result, this.getField(2)) }
override string toString() {
exists(string labels, string config |
labels = strictconcat(this.getFlowLabel(), " or ") and
if this.getField(2) = ""
then config = "any configuration"
else config = this.getConfiguration()
|
result = this.getPortal() + " as " + labels + " sink for " + config
)
}
}
private class AdditionalSinkFromSpec extends DataFlow::AdditionalSink {
AdditionalSinkSpec spec;
AdditionalSinkFromSpec() { this = spec.getPortal().getAnEntryNode(_) }
override predicate isSinkFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
cfg = spec.getConfiguration() and lbl = spec.getFlowLabel()
}
}
/**
* An additional flow step specified in an `additional-steps.csv` file.
*/
class AdditionalStepSpec extends ExternalData {
AdditionalStepSpec() { this.getDataPath() = "additional-steps.csv" }
/**
* Gets the start portal of this additional step.
*/
Portal getStartPortal() { result.toString() = this.getField(0) }
/**
* Gets the start flow label of this additional step.
*/
DataFlow::FlowLabel getStartFlowLabel() { result.toString() = this.getField(1) }
/**
* Gets the end portal of this additional step.
*/
Portal getEndPortal() { result.toString() = this.getField(2) }
/**
* Gets the end flow label of this additional step.
*/
DataFlow::FlowLabel getEndFlowLabel() { result.toString() = this.getField(3) }
/**
* Gets the configuration to which this step should be added.
*/
DataFlow::Configuration getConfiguration() { configSpec(result, this.getField(4)) }
override string toString() {
exists(string config |
if this.getField(4) = ""
then config = "any configuration"
else config = this.getConfiguration()
|
result =
"edge from " + this.getStartPortal() + " to " + this.getEndPortal() + ", transforming " +
this.getStartFlowLabel() + " into " + this.getEndFlowLabel() + " for " + config
)
}
}
private class AdditionalFlowStepFromSpec extends DataFlow::Configuration {
AdditionalStepSpec spec;
DataFlow::Node entry;
DataFlow::Node exit;
AdditionalFlowStepFromSpec() {
this = spec.getConfiguration() and
entry = spec.getStartPortal().getAnEntryNode(_) and
exit = spec.getEndPortal().getAnExitNode(_)
}
override predicate isAdditionalFlowStep(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
) {
pred = entry and
succ = exit and
predlbl = spec.getStartFlowLabel() and
succlbl = spec.getEndFlowLabel()
}
}

View File

@@ -1,100 +0,0 @@
/**
* Provides classes for importing source, sink and flow step summaries
* through external predicates.
*/
import javascript
import semmle.javascript.dataflow.Portals
import external.ExternalArtifact
import Shared
/**
* An external predicate providing information about additional sources.
*
* This predicate can be populated from the output of the `ExtractSourceSummaries` query.
*/
external predicate additionalSources(string portal, string flowLabel, string config);
/**
* An external predicate providing information about additional sinks.
*
* This predicate can be populated from the output of the `ExtractSinkSummaries` query.
*/
external predicate additionalSinks(string portal, string flowLabel, string config);
/**
* An external predicate providing information about additional flow steps.
*
* This predicate can be populated from the output of the `ExtractFlowStepSummaries` query.
*/
external predicate additionalSteps(
string startPortal, string startFlowLabel, string endPortal, string endFlowLabel, string config
);
/**
* An additional source specified through the `additionalSources` predicate.
*/
private class AdditionalSourceFromSpec extends DataFlow::AdditionalSource {
string flowLabel;
string config;
AdditionalSourceFromSpec() {
exists(Portal portal |
additionalSources(portal.toString(), flowLabel, config) and
this = portal.getAnExitNode(_)
)
}
override predicate isSourceFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
configSpec(cfg, config) and sourceFlowLabelSpec(lbl, flowLabel)
}
}
/**
* An additional sink specified through the `additionalSinks` predicate.
*/
private class AdditionalSinkFromSpec extends DataFlow::AdditionalSink {
string flowLabel;
string config;
AdditionalSinkFromSpec() {
exists(Portal portal |
additionalSinks(portal.toString(), flowLabel, config) and
this = portal.getAnEntryNode(_)
)
}
override predicate isSinkFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
configSpec(cfg, config) and sinkFlowLabelSpec(lbl, flowLabel)
}
}
/**
* An additional flow step specified through the `additionalSteps` predicate.
*/
private class AdditionalFlowStepFromSpec extends DataFlow::Configuration {
DataFlow::Node entry;
string startFlowLabel;
DataFlow::Node exit;
string endFlowLabel;
AdditionalFlowStepFromSpec() {
exists(Portal startPortal, Portal endPortal, string config |
additionalSteps(startPortal.toString(), startFlowLabel, endPortal.toString(), endFlowLabel,
config) and
configSpec(this, config) and
entry = startPortal.getAnEntryNode(_) and
exit = endPortal.getAnExitNode(_)
)
}
override predicate isAdditionalFlowStep(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
) {
pred = entry and
succ = exit and
predlbl = startFlowLabel and
succlbl = endFlowLabel
}
}

View File

@@ -1,17 +0,0 @@
import javascript
import semmle.javascript.dataflow.Portals
/**
* An escaping entry node of a portal, viewed as an additional sink node for any flow
* configuration currently in scope.
*/
class PortalEntrySink extends DataFlow::AdditionalSink {
Portal p;
PortalEntrySink() { this = p.getAnEntryNode(true) }
override predicate isSinkFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) { any() }
/** Gets the portal of which this is an entry node. */
Portal getPortal() { result = p }
}

View File

@@ -1,17 +0,0 @@
import javascript
import semmle.javascript.dataflow.Portals
/**
* A remote exit node of a portal, viewed as an additional source node for any flow
* configuration currently in scope.
*/
class PortalExitSource extends DataFlow::AdditionalSource {
Portal p;
PortalExitSource() { this = p.getAnExitNode(true) }
override predicate isSourceFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) { any() }
/** Gets the portal of which this is an exit node. */
Portal getPortal() { result = p }
}

View File

@@ -1,91 +0,0 @@
/**
* Provides utility predicates for working with flow summary specifications.
*/
import javascript
/**
* Holds if `config` matches `spec`, that is, either `spec` is the ID of `config`
* or `spec` is the empty string and `config` is an arbitrary configuration.
*/
predicate configSpec(DataFlow::Configuration config, string spec) {
config.getId() = spec
or
spec = ""
}
/**
* Holds if `lbl` matches `spec`, that is, either `spec` is the name of `config`
* or `spec` is the empty string and `lbl` is the built-in flow label `data`.
*/
predicate sourceFlowLabelSpec(DataFlow::FlowLabel lbl, string spec) {
lbl.toString() = spec
or
spec = "" and
lbl.isData()
}
/**
* Holds if `lbl` matches `spec`, that is, either `spec` is the name of `config`
* or `spec` is the empty string and `lbl` is an arbitrary standard flow label.
*/
predicate sinkFlowLabelSpec(DataFlow::FlowLabel lbl, string spec) {
lbl.toString() = spec
or
spec = "" and
lbl.isDataOrTaint()
}
/**
* A comment that annotates data flow nodes as sources/sinks.
*
* Such a comment starts with "Semmle:", possibly preceded by whitespace, and
* may contain specifications of the form "source: label,config" and "sink: label, config"
* where again whitespace is not significant and the ",config" part may be missing.
*
* It applies to any data flow node that ends on the line where the comment starts,
* and annotates the node as being a source/sink with the given flow label(s) for
* the given configuration (or any configuration if omitted).
*/
class AnnotationComment extends Comment {
string ann;
AnnotationComment() { ann = getText().regexpCapture("(?s)\\s*Semmle:(.*)", 1) }
/**
* Holds if this comment applies to `nd`, that is, it starts on the same line on
* which `nd` ends.
*/
predicate appliesTo(DataFlow::Node nd) {
exists(string file, int line |
getLocation().hasLocationInfo(file, line, _, _, _) and
nd.hasLocationInfo(file, _, _, line, _)
)
}
/**
* Holds if this comment contains an annotation of the form `source: label, config`
* or `source: label` (modulo whitespace). In the latter case, `config` may be
* any configuration.
*/
predicate specifiesSource(DataFlow::FlowLabel label, DataFlow::Configuration config) {
exists(string spec |
spec = ann.regexpFind("(?<=\\bsource:)\\s*[^\\s,]+(\\s*,\\s*[^\\s,])?", _, _) and
sourceFlowLabelSpec(label, spec.splitAt(",", 0).trim()) and
configSpec(config, spec.splitAt(",", 1).trim())
)
}
/**
* Holds if this comment contains an annotation of the form `sink: label, config`
* or `sink: label` (modulo whitespace). In the latter case, `config` may be
* any configuration.
*/
predicate specifiesSink(string label, string config) {
exists(string spec |
spec = ann.regexpFind("(?<=\\bsink:)\\s*[^\\s,]+(\\s*,\\s*[^\\s,]+)?", _, _) and
sinkFlowLabelSpec(label, spec.splitAt(",", 0).trim()) and
configSpec(config, spec.splitAt(",", 1).trim())
)
}
}

View File

@@ -1,18 +0,0 @@
import javascript
import Shared
/**
* A data flow node that is annotated as a sink.
*/
class SinkFromAnnotation extends DataFlow::AdditionalSink {
AnnotationComment c;
SinkFromAnnotation() {
c.appliesTo(this) and
c.specifiesSink(_, _)
}
override predicate isSinkFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
c.specifiesSink(lbl, cfg)
}
}

View File

@@ -1,18 +0,0 @@
import javascript
import Shared
/**
* A data flow node that is annotated as a source.
*/
class SourceFromAnnotation extends DataFlow::AdditionalSource {
AnnotationComment c;
SourceFromAnnotation() {
c.appliesTo(this) and
c.specifiesSource(_, _)
}
override predicate isSourceFor(DataFlow::Configuration cfg, DataFlow::FlowLabel lbl) {
c.specifiesSource(lbl, cfg)
}
}

View File

@@ -1,6 +0,0 @@
| (parameter 0 (member h (root https://www.npmjs.com/package/infer-sources))) | data | (return (member h (root https://www.npmjs.com/package/infer-sources))) | data | CodeInjection |
| (parameter 0 (member h (root https://www.npmjs.com/package/infer-sources))) | taint | (return (member h (root https://www.npmjs.com/package/infer-sources))) | taint | CodeInjection |
| (parameter 0 (member notASink (root https://www.npmjs.com/package/infer-sources))) | data | (return (member notASink (root https://www.npmjs.com/package/infer-sources))) | data | CodeInjection |
| (parameter 0 (member notASink (root https://www.npmjs.com/package/infer-sources))) | taint | (return (member notASink (root https://www.npmjs.com/package/infer-sources))) | taint | CodeInjection |
| (parameter 0 (member notATaintedSource (root https://www.npmjs.com/package/infer-sources))) | data | (return (member notATaintedSource (root https://www.npmjs.com/package/infer-sources))) | data | CodeInjection |
| (parameter 0 (member notATaintedSource (root https://www.npmjs.com/package/infer-sources))) | taint | (return (member notATaintedSource (root https://www.npmjs.com/package/infer-sources))) | taint | CodeInjection |

View File

@@ -1 +0,0 @@
experimental/Summaries/ExtractFlowStepSummaries.ql

View File

@@ -1,6 +0,0 @@
| (parameter 0 (member codeInjection (root https://www.npmjs.com/package/infer-sources))) | data | CodeInjection |
| (parameter 0 (member codeInjection (root https://www.npmjs.com/package/infer-sources))) | taint | CodeInjection |
| (parameter 0 (member hashPass (root https://www.npmjs.com/package/infer-sources))) | data | CodeInjection |
| (parameter 0 (member hashPass (root https://www.npmjs.com/package/infer-sources))) | taint | CodeInjection |
| (parameter 0 (member multiple (root https://www.npmjs.com/package/infer-sources))) | data | CodeInjection |
| (parameter 0 (member multiple (root https://www.npmjs.com/package/infer-sources))) | taint | CodeInjection |

View File

@@ -1 +0,0 @@
experimental/Summaries/ExtractSinkSummaries.ql

View File

@@ -1,2 +0,0 @@
| (parameter 0 (parameter 0 (member listen (root https://www.npmjs.com/package/infer-sources)))) | taint | CodeInjection |
| (return (member taintedSource (root https://www.npmjs.com/package/infer-sources))) | taint | CodeInjection |

View File

@@ -1 +0,0 @@
experimental/Summaries/ExtractSourceSummaries.ql

View File

@@ -1,192 +0,0 @@
var http = require('http'),
url = require('url');
function listenForHeaders(cb) {
http.createServer(function (req, res) {
let cmd = url.parse(req.url, true).query.path;
cb(cmd); // sink
res.write('Hello World!');
res.end();
}).listen(8080);
};
function codeInjection(input) {
eval("url[" + input + "]");
}
function commandInjection(input) {
require("child_process").exec("ls " + input);
}
function multiple(input) {
codeInjection(input);
commandInjection(input);
}
function taintedPath(input) {
require("/tmp/" + input);
}
function regexpInj(data) {
new RegExp("^"+ data.name + "$", "i");
}
function xpathInj(userName) {
const xpath = require('xpath');
let badXPathExpr = xpath.parse("//users/user[login/text()='" + userName + "']/home_dir/text()");
badXPathExpr.select({
node: root
});
}
function xxe(input) {
const expat = require('node-expat');
var parser = new expat.Parser();
parser.write(input);
}
function xmlBomb(input) {
const libxmljs = require('libxmljs');
libxmljs.parseXml(input, { noent: true });
}
function hashPass(input) {
require('crypto').createCipher('aes192').write(input);
codeInjection(input)
}
function unsafeDes(input) {
const jsyaml = require("js-yaml");
let data;
return jsyaml.load(input);
}
function remoteProp(input1, input2, input3) {
var obj = url[input1];
obj[input2] = input3;
}
function reflected(userID) {
var express = require('express');
var app = express();
app.get('/user/:id', function(req, res) {
res.send("Unknown user: " + userID);
});
}
function redirect(input) {
var https = require('https');
var url = require('url');
var server = https.createServer(function(req, res) {
res.writeHead(302, { Location: '/' + input});
}).listen(8080)
}
function sqlInj(input) {
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'me',
password : 'secret',
database : 'my_db'
});
connection.connect();
connection.query('SELECT ' + input + ' AS solution', function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});
}
function createError () {
var err, status;
for (i = 0; i < 1000; i++) {
err = {};
status = err.a || err.b || err.c || err.d || err;
}
err.a = err.b = status
return err;
}
function forLoop(input) {
var intObj = {};
var res = 0;
for (var i = 0; i < input.x.length; i++) {
res += intObj.x + input.x[i];
}
if (res < 1000) {
intObj.res = res;
return intObj;
} else
return res;
}
function notASink(foo) {
return foo;
}
// this call should not make parameter `foo` a command injection sink
eval(notASink(42));
function taintedSource() {
return location.search;
}
function notATaintedSource(x) {
return x;
}
// this call should not make the return value of `notATaintedSource` a remote flow source
notATaintedSource(location.search);
function invoke(cb, x) {
cb(x);
}
// this call should not make the first argument to `cb` above a remote flow source
invoke((x)=>x, location.search);
function g(x) {
h(x);
}
function h(y) {
return y;
}
function mkdirp(path) {
path /* Semmle: sink: taint, TaintedPath */
}
module.exports = {
codeInjection: codeInjection,
commandInjection: commandInjection,
remotePropeInjection: remoteProp,
multiple: multiple,
taintedPath: taintedPath,
sqlInj: sqlInj,
listen: listenForHeaders,
createError: createError,
regexpInj: regexpInj,
xpathInj: xpathInj,
xmlBomb: xmlBomb,
hashPass: hashPass,
xxe: xxe,
unsafeDes: unsafeDes,
redirect: redirect,
reflected: reflected,
notASink: notASink,
taintedSource: taintedSource,
notATaintedSource: notATaintedSource,
invoke: invoke,
g: g,
h: h,
mkdirp: mkdirp
}

View File

@@ -1,10 +0,0 @@
{
"name": "infer-sources",
"version": "0.0.1",
"dependencies": {
"mysql": "2.15.0",
"xpath": "0.0.27",
"libxmljs": "0.19.1"
},
"main": "index.js"
}