mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Merge branch 'main' into rdmarsh/cpp/use-taint-configuration-dtt
This commit is contained in:
@@ -38,7 +38,7 @@ If you have an idea for a query that you would like to share with other CodeQL u
|
||||
|
||||
- The queries and libraries must be autoformatted, for example using the "Format Document" command in [CodeQL for Visual Studio Code](https://help.semmle.com/codeql/codeql-for-vscode/procedures/about-codeql-for-vscode.html).
|
||||
|
||||
If you prefer, you can use this [pre-commit hook](misc/scripts/pre-commit) that automatically checks whether your files are correctly formatted. See the [pre-commit hook installation guide](docs/install-pre-commit-hook.md) for instructions on how to install the hook.
|
||||
If you prefer, you can use this [pre-commit hook](misc/scripts/pre-commit) that automatically checks whether your files are correctly formatted. See the [pre-commit hook installation guide](docs/pre-commit-hook-setup.md) for instructions on how to install the hook.
|
||||
|
||||
4. **Compilation**
|
||||
|
||||
|
||||
2
cpp/change-notes/2021-03-11-failed-extractions.md
Normal file
2
cpp/change-notes/2021-03-11-failed-extractions.md
Normal file
@@ -0,0 +1,2 @@
|
||||
codescanning
|
||||
* Added cpp/diagnostics/failed-extractions. This query gives information about which extractions did not run to completion.
|
||||
22
cpp/ql/src/Diagnostics/FailedExtractions.ql
Normal file
22
cpp/ql/src/Diagnostics/FailedExtractions.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Failed extractions
|
||||
* @description Gives the command-line of compilations for which extraction did not run to completion.
|
||||
* @kind diagnostic
|
||||
* @id cpp/diagnostics/failed-extractions
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
class AnonymousCompilation extends Compilation {
|
||||
override string toString() { result = "<compilation>" }
|
||||
}
|
||||
|
||||
string describe(Compilation c) {
|
||||
if c.getArgument(1) = "--mimic"
|
||||
then result = "compiler invocation " + concat(int i | i > 1 | c.getArgument(i), " " order by i)
|
||||
else result = "extractor invocation " + concat(int i | | c.getArgument(i), " " order by i)
|
||||
}
|
||||
|
||||
from Compilation c
|
||||
where not c.normalTermination()
|
||||
select c, "Extraction failed for " + describe(c), 2
|
||||
@@ -276,7 +276,10 @@ class File extends Container, @file {
|
||||
c.getAFileCompiled() = this and
|
||||
(
|
||||
c.getAnArgument() = "--microsoft" or
|
||||
c.getAnArgument().toLowerCase().replaceAll("\\", "/").matches("%/cl.exe")
|
||||
c.getAnArgument()
|
||||
.toLowerCase()
|
||||
.replaceAll("\\", "/")
|
||||
.matches(["%/cl.exe", "%/clang-cl.exe"])
|
||||
)
|
||||
)
|
||||
or
|
||||
|
||||
@@ -12,7 +12,7 @@ This topic offers some simple tips on how to avoid common problems that can affe
|
||||
Before reading the tips below, it is worth reiterating a few important points about CodeQL and the QL language:
|
||||
|
||||
- CodeQL :ref:`predicates <predicates>` and :ref:`classes <classes>` are evaluated to database `tables <https://en.wikipedia.org/wiki/Table_(database)>`__. Large predicates generate large tables with many rows, and are therefore expensive to compute.
|
||||
- The QL language is implemented using standard database operations and `relational algebra <https://en.wikipedia.org/wiki/Relational_algebra>`__ (such as join, projection, and union). For more information about query languages and databases, see ":ref:`About the QL language <about-the-ql-language>`.
|
||||
- The QL language is implemented using standard database operations and `relational algebra <https://en.wikipedia.org/wiki/Relational_algebra>`__ (such as join, projection, and union). For more information about query languages and databases, see ":ref:`About the QL language <about-the-ql-language>`."
|
||||
- Queries are evaluated *bottom-up*, which means that a predicate is not evaluated until *all* of the predicates that it depends on are evaluated. For more information on query evaluation, see ":ref:`Evaluation of QL programs <evaluation-of-ql-programs>`."
|
||||
|
||||
Performance tips
|
||||
|
||||
2
java/change-notes/2021-03-05-commons-object-utils.md
Normal file
2
java/change-notes/2021-03-05-commons-object-utils.md
Normal file
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* Added models for `ObjectUtils` methods in the Apache Commons Lang library. This may lead to more results from any dataflow query where traversal of `ObjectUtils` methods means we can now complete a path from a source of tainted data to a corresponding sink.
|
||||
@@ -144,7 +144,7 @@ class OpensDirective extends Directive, @opens {
|
||||
|
||||
/**
|
||||
* Gets a module specified in the `to` clause of this
|
||||
* `exports` directive, if any.
|
||||
* `opens` directive, if any.
|
||||
*/
|
||||
Module getATargetModule() { opensTo(this, result) }
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ predicate localAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
||||
localAdditionalTaintUpdateStep(src.asExpr(),
|
||||
sink.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr())
|
||||
or
|
||||
summaryStep(src, sink, "taint")
|
||||
summaryStep(src, sink, "taint") and
|
||||
not summaryStep(src, sink, "value")
|
||||
or
|
||||
exists(Argument arg |
|
||||
src.asExpr() = arg and
|
||||
|
||||
@@ -396,3 +396,30 @@ private class ApacheRegExUtilsModel extends SummaryModelCsv {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint-propagating models for `ObjectUtils`.
|
||||
*/
|
||||
private class ApacheObjectUtilsModel extends SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
// Note all the functions annotated with `taint` flow really should have `value` flow,
|
||||
// but we don't support value-preserving varargs functions at the moment.
|
||||
"org.apache.commons.lang3;ObjectUtils;false;clone;;;Argument;ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;cloneIfPossible;;;Argument;ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;CONST;;;Argument;ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;CONST_BYTE;;;Argument;ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;CONST_SHORT;;;Argument;ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;defaultIfNull;;;Argument;ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;firstNonNull;;;Argument;ReturnValue;taint",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;getIfNull;;;Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;max;;;Argument;ReturnValue;taint",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;median;;;Argument;ReturnValue;taint",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;min;;;Argument;ReturnValue;taint",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;mode;;;Argument;ReturnValue;taint",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;requireNonEmpty;;;Argument[0];ReturnValue;value",
|
||||
"org.apache.commons.lang3;ObjectUtils;false;toString;(Object,String);;Argument[1];ReturnValue;value"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
public class ObjectUtilsTest {
|
||||
String taint() { return "tainted"; }
|
||||
|
||||
private static class IntSource {
|
||||
static int taint() { return 0; }
|
||||
}
|
||||
|
||||
void sink(Object o) {}
|
||||
|
||||
void test() throws Exception {
|
||||
sink(ObjectUtils.clone(taint())); // $hasValueFlow
|
||||
sink(ObjectUtils.cloneIfPossible(taint())); // $hasValueFlow
|
||||
sink(ObjectUtils.CONST(taint())); // $hasValueFlow
|
||||
sink(ObjectUtils.CONST_SHORT(IntSource.taint())); // $hasValueFlow
|
||||
sink(ObjectUtils.CONST_BYTE(IntSource.taint())); // $hasValueFlow
|
||||
sink(ObjectUtils.defaultIfNull(taint(), null)); // $hasValueFlow
|
||||
sink(ObjectUtils.defaultIfNull(null, taint())); // $hasValueFlow
|
||||
sink(ObjectUtils.firstNonNull(taint(), null, null)); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.firstNonNull(null, taint(), null)); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.firstNonNull(null, null, taint())); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.getIfNull(taint(), null)); // $hasValueFlow
|
||||
sink(ObjectUtils.max(taint(), null, null)); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.max(null, taint(), null)); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.max(null, null, taint())); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.median(taint(), null, null)); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.median((String)null, taint(), null)); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.median((String)null, null, taint())); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.min(taint(), null, null)); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.min(null, taint(), null)); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.min(null, null, taint())); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.mode(taint(), null, null)); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.mode(null, taint(), null)); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.mode(null, null, taint())); // $hasTaintFlow $MISSING:hasValueFlow
|
||||
sink(ObjectUtils.requireNonEmpty(taint(), "message")); // $hasValueFlow
|
||||
sink(ObjectUtils.requireNonEmpty("not null", taint())); // GOOD (message doesn't propagate to the return)
|
||||
sink(ObjectUtils.toString(taint(), "default string")); // GOOD (first argument is stringified)
|
||||
sink(ObjectUtils.toString(null, taint())); // $hasValueFlow
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,20 @@ import java
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
class Conf extends TaintTracking::Configuration {
|
||||
Conf() { this = "qltest:frameworks:apache-commons-lang3" }
|
||||
class TaintFlowConf extends TaintTracking::Configuration {
|
||||
TaintFlowConf() { this = "qltest:frameworks:apache-commons-lang3-taint-flow" }
|
||||
|
||||
override predicate isSource(DataFlow::Node n) {
|
||||
n.asExpr().(MethodAccess).getMethod().hasName("taint")
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node n) {
|
||||
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
|
||||
}
|
||||
}
|
||||
|
||||
class ValueFlowConf extends DataFlow::Configuration {
|
||||
ValueFlowConf() { this = "qltest:frameworks:apache-commons-lang3-value-flow" }
|
||||
|
||||
override predicate isSource(DataFlow::Node n) {
|
||||
n.asExpr().(MethodAccess).getMethod().hasName("taint")
|
||||
@@ -17,11 +29,19 @@ class Conf extends TaintTracking::Configuration {
|
||||
class HasFlowTest extends InlineExpectationsTest {
|
||||
HasFlowTest() { this = "HasFlowTest" }
|
||||
|
||||
override string getARelevantTag() { result = "hasTaintFlow" }
|
||||
override string getARelevantTag() { result = ["hasTaintFlow", "hasValueFlow"] }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
tag = "hasTaintFlow" and
|
||||
exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) |
|
||||
exists(DataFlow::Node src, DataFlow::Node sink, TaintFlowConf conf | conf.hasFlow(src, sink) |
|
||||
not any(ValueFlowConf vconf).hasFlow(src, sink) and
|
||||
sink.getLocation() = location and
|
||||
element = sink.toString() and
|
||||
value = ""
|
||||
)
|
||||
or
|
||||
tag = "hasValueFlow" and
|
||||
exists(DataFlow::Node src, DataFlow::Node sink, ValueFlowConf conf | conf.hasFlow(src, sink) |
|
||||
sink.getLocation() = location and
|
||||
element = sink.toString() and
|
||||
value = ""
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.commons.lang3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.text.StrBuilder;
|
||||
|
||||
@SuppressWarnings("deprecation") // deprecated class StrBuilder is imported
|
||||
// because it is part of the signature of deprecated methods
|
||||
public class ObjectUtils {
|
||||
public static class Null implements Serializable {
|
||||
}
|
||||
public static final Null NULL = new Null();
|
||||
|
||||
public static boolean allNotNull(final Object... values) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean allNull(final Object... values) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean anyNotNull(final Object... values) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean anyNull(final Object... values) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T> T clone(final T obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T cloneIfPossible(final T obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends Comparable<? super T>> int compare(final T c1, final T c2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static <T extends Comparable<? super T>> int compare(final T c1, final T c2, final boolean nullGreater) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static boolean CONST(final boolean v) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static byte CONST(final byte v) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static char CONST(final char v) {
|
||||
return '\0';
|
||||
}
|
||||
|
||||
public static double CONST(final double v) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static float CONST(final float v) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int CONST(final int v) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static long CONST(final long v) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static short CONST(final short v) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static <T> T CONST(final T v) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte CONST_BYTE(final int v) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static short CONST_SHORT(final int v) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static <T> T defaultIfNull(final T object, final T defaultValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean equals(final Object object1, final Object object2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T> T firstNonNull(final T... values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T getFirstNonNull(final Supplier<T>... suppliers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T getIfNull(final T object, final Supplier<T> defaultSupplier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int hashCode(final Object obj) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int hashCodeMulti(final Object... objects) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static void identityToString(final Appendable appendable, final Object object) throws IOException {
|
||||
}
|
||||
|
||||
public static String identityToString(final Object object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void identityToString(final StrBuilder builder, final Object object) {
|
||||
}
|
||||
|
||||
public static void identityToString(final StringBuffer buffer, final Object object) {
|
||||
}
|
||||
|
||||
public static void identityToString(final StringBuilder builder, final Object object) {
|
||||
}
|
||||
|
||||
public static boolean isEmpty(final Object object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isNotEmpty(final Object object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T extends Comparable<? super T>> T max(final T... values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T median(final Comparator<T> comparator, final T... items) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends Comparable<? super T>> T median(final T... items) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends Comparable<? super T>> T min(final T... values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T mode(final T... items) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean notEqual(final Object object1, final Object object2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T> T requireNonEmpty(final T obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T requireNonEmpty(final T obj, final String message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String toString(final Object obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String toString(final Object obj, final String nullStr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String toString(final Object obj, final Supplier<String> supplier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void wait(final Object obj, final Duration duration) throws InterruptedException {
|
||||
}
|
||||
|
||||
public ObjectUtils() {
|
||||
}
|
||||
|
||||
}
|
||||
6
javascript/change-notes/2021-03-10-d3.md
Normal file
6
javascript/change-notes/2021-03-10-d3.md
Normal file
@@ -0,0 +1,6 @@
|
||||
lgtm,codescanning
|
||||
* Support for `d3` has improved. The XSS queries now recognize HTML injection sinks
|
||||
from the `d3` API.
|
||||
Affected packages are
|
||||
[d3](https://npmjs.com/package/d3),
|
||||
[d3-selection](https://npmjs.com/package/d3-selection).
|
||||
@@ -80,6 +80,7 @@ import semmle.javascript.frameworks.ClosureLibrary
|
||||
import semmle.javascript.frameworks.CookieLibraries
|
||||
import semmle.javascript.frameworks.Credentials
|
||||
import semmle.javascript.frameworks.CryptoLibraries
|
||||
import semmle.javascript.frameworks.D3
|
||||
import semmle.javascript.frameworks.DateFunctions
|
||||
import semmle.javascript.frameworks.DigitalOcean
|
||||
import semmle.javascript.frameworks.Electron
|
||||
|
||||
102
javascript/ql/src/semmle/javascript/frameworks/D3.qll
Normal file
102
javascript/ql/src/semmle/javascript/frameworks/D3.qll
Normal file
@@ -0,0 +1,102 @@
|
||||
/** Provides classes and predicates modelling aspects of the `d3` library. */
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.security.dataflow.Xss
|
||||
|
||||
/** Provides classes and predicates modelling aspects of the `d3` library. */
|
||||
module D3 {
|
||||
/** The global variable `d3` as an entry point for API graphs. */
|
||||
private class D3GlobalEntry extends API::EntryPoint {
|
||||
D3GlobalEntry() { this = "D3GlobalEntry" }
|
||||
|
||||
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("d3") }
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
}
|
||||
|
||||
/** Gets an API node referring to the `d3` module. */
|
||||
API::Node d3() {
|
||||
result = API::moduleImport("d3")
|
||||
or
|
||||
// recognize copies of d3 in a scope
|
||||
result = API::moduleImport(any(string s | s.regexpMatch("@.*/d3(-\\w+)?")))
|
||||
or
|
||||
result = API::moduleImport("d3-node").getInstance().getMember("d3")
|
||||
or
|
||||
result = API::root().getASuccessor(any(D3GlobalEntry i))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an API node referring to the given module, or to `d3`.
|
||||
*
|
||||
* Useful for accessing modules that are re-exported by `d3`.
|
||||
*/
|
||||
bindingset[name]
|
||||
API::Node d3Module(string name) {
|
||||
result = d3() // all d3 modules are re-exported as part of 'd3'
|
||||
or
|
||||
result = API::moduleImport(name)
|
||||
}
|
||||
|
||||
/** Gets an API node referring to a `d3` selection object, such as `d3.selection()`. */
|
||||
API::Node d3Selection() {
|
||||
result = d3Module("d3-selection").getMember(["selection", "select", "selectAll"]).getReturn()
|
||||
or
|
||||
exists(API::CallNode call, string name |
|
||||
call = d3Selection().getMember(name).getACall() and
|
||||
result = call.getReturn()
|
||||
|
|
||||
name =
|
||||
[
|
||||
"select", "selectAll", "filter", "merge", "selectChild", "selectChildren", "selection",
|
||||
"insert", "remove", "clone", "sort", "order", "raise", "lower", "append", "data", "join",
|
||||
"enter", "exit", "call"
|
||||
]
|
||||
or
|
||||
name = ["text", "html", "datum"] and
|
||||
call.getNumArgument() > 0 // exclude 0-argument version, which returns the current value
|
||||
or
|
||||
name = ["attr", "classed", "style", "property", "on"] and
|
||||
call.getNumArgument() > 1 // exclude 1-argument version, which returns the current value
|
||||
or
|
||||
// Setting multiple things at once
|
||||
name = ["attr", "classed", "style", "property", "on"] and
|
||||
call.getArgument(0).getALocalSource() instanceof DataFlow::ObjectLiteralNode
|
||||
)
|
||||
or
|
||||
result = d3Selection().getMember("call").getParameter(0).getParameter(0)
|
||||
}
|
||||
|
||||
private class D3XssSink extends DomBasedXss::Sink {
|
||||
D3XssSink() {
|
||||
exists(API::Node htmlArg |
|
||||
htmlArg = d3Selection().getMember("html").getParameter(0) and
|
||||
this = [htmlArg, htmlArg.getReturn()].getARhs()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class D3DomValueSource extends DOM::DomValueSource::Range {
|
||||
D3DomValueSource() {
|
||||
this = d3Selection().getMember("each").getReceiver().getAnImmediateUse()
|
||||
or
|
||||
this = d3Selection().getMember("node").getReturn().getAnImmediateUse()
|
||||
or
|
||||
this = d3Selection().getMember("nodes").getReturn().getUnknownMember().getAnImmediateUse()
|
||||
}
|
||||
}
|
||||
|
||||
private class D3AttributeDefinition extends DOM::AttributeDefinition {
|
||||
DataFlow::CallNode call;
|
||||
|
||||
D3AttributeDefinition() {
|
||||
call = d3Selection().getMember("attr").getACall() and
|
||||
call.getNumArgument() > 1 and
|
||||
this = call.asExpr()
|
||||
}
|
||||
|
||||
override string getName() { result = call.getArgument(0).getStringValue() }
|
||||
|
||||
override DataFlow::Node getValueNode() { result = call.getArgument(1) }
|
||||
}
|
||||
}
|
||||
@@ -479,8 +479,8 @@ module JQuery {
|
||||
// either a reference to a global variable `$` or `jQuery`
|
||||
this = DataFlow::globalVarRef(any(string jq | jq = "$" or jq = "jQuery"))
|
||||
or
|
||||
// or imported from a module named `jquery`
|
||||
this = DataFlow::moduleImport("jquery")
|
||||
// or imported from a module named `jquery` or `zepto`
|
||||
this = DataFlow::moduleImport(["jquery", "zepto"])
|
||||
or
|
||||
this.hasUnderlyingType("JQueryStatic")
|
||||
}
|
||||
|
||||
@@ -27,6 +27,11 @@ module XssThroughDom {
|
||||
result = ["name", "value", "title", "alt"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a DOM property name that could store user-controlled data.
|
||||
*/
|
||||
string unsafeDomPropertyName() { result = ["innerText", "textContent", "value", "name"] }
|
||||
|
||||
/**
|
||||
* A source for text from the DOM from a JQuery method call.
|
||||
*/
|
||||
@@ -35,10 +40,16 @@ module XssThroughDom {
|
||||
(
|
||||
this.getMethodName() = ["text", "val"] and this.getNumArgument() = 0
|
||||
or
|
||||
this.getMethodName() = "attr" and
|
||||
this.getNumArgument() = 1 and
|
||||
forex(InferredType t | t = this.getArgument(0).analyze().getAType() | t = TTString()) and
|
||||
this.getArgument(0).mayHaveStringValue(unsafeAttributeName())
|
||||
exists(string methodName, string value |
|
||||
this.getMethodName() = methodName and
|
||||
this.getNumArgument() = 1 and
|
||||
forex(InferredType t | t = this.getArgument(0).analyze().getAType() | t = TTString()) and
|
||||
this.getArgument(0).mayHaveStringValue(value)
|
||||
|
|
||||
methodName = "attr" and value = unsafeAttributeName()
|
||||
or
|
||||
methodName = "prop" and value = unsafeDomPropertyName()
|
||||
)
|
||||
) and
|
||||
// looks like a $("<p>" + ... ) source, which is benign for this query.
|
||||
not exists(DataFlow::Node prefix |
|
||||
@@ -51,6 +62,29 @@ module XssThroughDom {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A source for text from the DOM from a `d3` method call.
|
||||
*/
|
||||
class D3TextSource extends Source {
|
||||
D3TextSource() {
|
||||
exists(DataFlow::MethodCallNode call, string methodName |
|
||||
this = call and
|
||||
call = D3::d3Selection().getMember(methodName).getACall()
|
||||
|
|
||||
methodName = "attr" and
|
||||
call.getNumArgument() = 1 and
|
||||
call.getArgument(0).mayHaveStringValue(unsafeAttributeName())
|
||||
or
|
||||
methodName = "property" and
|
||||
call.getNumArgument() = 1 and
|
||||
call.getArgument(0).mayHaveStringValue(unsafeDomPropertyName())
|
||||
or
|
||||
methodName = "text" and
|
||||
call.getNumArgument() = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A source for text from the DOM from a DOM property read or call to `getAttribute()`.
|
||||
*/
|
||||
@@ -58,7 +92,7 @@ module XssThroughDom {
|
||||
DOMTextSource() {
|
||||
exists(DataFlow::PropRead read | read = this |
|
||||
read.getBase().getALocalSource() = DOM::domValueRef() and
|
||||
read.mayHavePropertyName(["innerText", "textContent", "value", "name"])
|
||||
read.mayHavePropertyName(unsafeDomPropertyName())
|
||||
)
|
||||
or
|
||||
exists(DataFlow::MethodCallNode mcn | mcn = this |
|
||||
|
||||
@@ -92,6 +92,16 @@ nodes
|
||||
| classnames.js:15:47:15:63 | clsx(window.name) |
|
||||
| classnames.js:15:52:15:62 | window.name |
|
||||
| classnames.js:15:52:15:62 | window.name |
|
||||
| d3.js:4:12:4:22 | window.name |
|
||||
| d3.js:4:12:4:22 | window.name |
|
||||
| d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:21:15:21:24 | getTaint() |
|
||||
| d3.js:21:15:21:24 | getTaint() |
|
||||
| dates.js:9:9:9:69 | taint |
|
||||
| dates.js:9:17:9:69 | decodeU ... ing(1)) |
|
||||
| dates.js:9:36:9:50 | window.location |
|
||||
@@ -772,6 +782,22 @@ edges
|
||||
| classnames.js:15:47:15:63 | clsx(window.name) | classnames.js:15:31:15:78 | `<span ... <span>` |
|
||||
| classnames.js:15:52:15:62 | window.name | classnames.js:15:47:15:63 | clsx(window.name) |
|
||||
| classnames.js:15:52:15:62 | window.name | classnames.js:15:47:15:63 | clsx(window.name) |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:21:15:21:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:21:15:21:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:21:15:21:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:21:15:21:24 | getTaint() |
|
||||
| dates.js:9:9:9:69 | taint | dates.js:11:63:11:67 | taint |
|
||||
| dates.js:9:9:9:69 | taint | dates.js:12:66:12:70 | taint |
|
||||
| dates.js:9:9:9:69 | taint | dates.js:13:59:13:63 | taint |
|
||||
@@ -1338,6 +1364,10 @@ edges
|
||||
| classnames.js:11:31:11:79 | `<span ... <span>` | classnames.js:10:45:10:55 | window.name | classnames.js:11:31:11:79 | `<span ... <span>` | Cross-site scripting vulnerability due to $@. | classnames.js:10:45:10:55 | window.name | user-provided value |
|
||||
| classnames.js:13:31:13:83 | `<span ... <span>` | classnames.js:13:57:13:67 | window.name | classnames.js:13:31:13:83 | `<span ... <span>` | Cross-site scripting vulnerability due to $@. | classnames.js:13:57:13:67 | window.name | user-provided value |
|
||||
| classnames.js:15:31:15:78 | `<span ... <span>` | classnames.js:15:52:15:62 | window.name | classnames.js:15:31:15:78 | `<span ... <span>` | Cross-site scripting vulnerability due to $@. | classnames.js:15:52:15:62 | window.name | user-provided value |
|
||||
| d3.js:11:15:11:24 | getTaint() | d3.js:4:12:4:22 | window.name | d3.js:11:15:11:24 | getTaint() | Cross-site scripting vulnerability due to $@. | d3.js:4:12:4:22 | window.name | user-provided value |
|
||||
| d3.js:12:20:12:29 | getTaint() | d3.js:4:12:4:22 | window.name | d3.js:12:20:12:29 | getTaint() | Cross-site scripting vulnerability due to $@. | d3.js:4:12:4:22 | window.name | user-provided value |
|
||||
| d3.js:14:20:14:29 | getTaint() | d3.js:4:12:4:22 | window.name | d3.js:14:20:14:29 | getTaint() | Cross-site scripting vulnerability due to $@. | d3.js:4:12:4:22 | window.name | user-provided value |
|
||||
| d3.js:21:15:21:24 | getTaint() | d3.js:4:12:4:22 | window.name | d3.js:21:15:21:24 | getTaint() | Cross-site scripting vulnerability due to $@. | d3.js:4:12:4:22 | window.name | user-provided value |
|
||||
| dates.js:11:31:11:70 | `Time i ... aint)}` | dates.js:9:36:9:50 | window.location | dates.js:11:31:11:70 | `Time i ... aint)}` | Cross-site scripting vulnerability due to $@. | dates.js:9:36:9:50 | window.location | user-provided value |
|
||||
| dates.js:12:31:12:73 | `Time i ... aint)}` | dates.js:9:36:9:50 | window.location | dates.js:12:31:12:73 | `Time i ... aint)}` | Cross-site scripting vulnerability due to $@. | dates.js:9:36:9:50 | window.location | user-provided value |
|
||||
| dates.js:13:31:13:72 | `Time i ... time)}` | dates.js:9:36:9:50 | window.location | dates.js:13:31:13:72 | `Time i ... time)}` | Cross-site scripting vulnerability due to $@. | dates.js:9:36:9:50 | window.location | user-provided value |
|
||||
|
||||
@@ -92,6 +92,16 @@ nodes
|
||||
| classnames.js:15:47:15:63 | clsx(window.name) |
|
||||
| classnames.js:15:52:15:62 | window.name |
|
||||
| classnames.js:15:52:15:62 | window.name |
|
||||
| d3.js:4:12:4:22 | window.name |
|
||||
| d3.js:4:12:4:22 | window.name |
|
||||
| d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:21:15:21:24 | getTaint() |
|
||||
| d3.js:21:15:21:24 | getTaint() |
|
||||
| dates.js:9:9:9:69 | taint |
|
||||
| dates.js:9:17:9:69 | decodeU ... ing(1)) |
|
||||
| dates.js:9:36:9:50 | window.location |
|
||||
@@ -783,6 +793,22 @@ edges
|
||||
| classnames.js:15:47:15:63 | clsx(window.name) | classnames.js:15:31:15:78 | `<span ... <span>` |
|
||||
| classnames.js:15:52:15:62 | window.name | classnames.js:15:47:15:63 | clsx(window.name) |
|
||||
| classnames.js:15:52:15:62 | window.name | classnames.js:15:47:15:63 | clsx(window.name) |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:11:15:11:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:12:20:12:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:14:20:14:29 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:21:15:21:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:21:15:21:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:21:15:21:24 | getTaint() |
|
||||
| d3.js:4:12:4:22 | window.name | d3.js:21:15:21:24 | getTaint() |
|
||||
| dates.js:9:9:9:69 | taint | dates.js:11:63:11:67 | taint |
|
||||
| dates.js:9:9:9:69 | taint | dates.js:12:66:12:70 | taint |
|
||||
| dates.js:9:9:9:69 | taint | dates.js:13:59:13:63 | taint |
|
||||
|
||||
22
javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/d3.js
vendored
Normal file
22
javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/d3.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
const d3 = require('d3');
|
||||
|
||||
function getTaint() {
|
||||
return window.name;
|
||||
}
|
||||
|
||||
function doSomething() {
|
||||
d3.select('#main')
|
||||
.attr('width', 100)
|
||||
.style('color', 'red')
|
||||
.html(getTaint()) // NOT OK
|
||||
.html(d => getTaint()) // NOT OK
|
||||
.call(otherFunction)
|
||||
.html(d => getTaint()); // NOT OK
|
||||
}
|
||||
|
||||
|
||||
function otherFunction(selection) {
|
||||
selection
|
||||
.attr('foo', 'bar')
|
||||
.html(getTaint()); // NOT OK
|
||||
}
|
||||
@@ -100,6 +100,9 @@ nodes
|
||||
| xss-through-dom.js:79:4:79:34 | documen ... t.value |
|
||||
| xss-through-dom.js:79:4:79:34 | documen ... t.value |
|
||||
| xss-through-dom.js:79:4:79:34 | documen ... t.value |
|
||||
| xss-through-dom.js:81:17:81:43 | $('#foo ... rText') |
|
||||
| xss-through-dom.js:81:17:81:43 | $('#foo ... rText') |
|
||||
| xss-through-dom.js:81:17:81:43 | $('#foo ... rText') |
|
||||
edges
|
||||
| forms.js:8:23:8:28 | values | forms.js:9:31:9:36 | values |
|
||||
| forms.js:8:23:8:28 | values | forms.js:9:31:9:36 | values |
|
||||
@@ -157,6 +160,7 @@ edges
|
||||
| xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | xss-through-dom.js:73:9:73:41 | selector |
|
||||
| xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | xss-through-dom.js:73:9:73:41 | selector |
|
||||
| xss-through-dom.js:79:4:79:34 | documen ... t.value | xss-through-dom.js:79:4:79:34 | documen ... t.value |
|
||||
| xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') |
|
||||
#select
|
||||
| forms.js:9:31:9:40 | values.foo | forms.js:8:23:8:28 | values | forms.js:9:31:9:40 | values.foo | $@ is reinterpreted as HTML without escaping meta-characters. | forms.js:8:23:8:28 | values | DOM text |
|
||||
| forms.js:12:31:12:40 | values.bar | forms.js:11:24:11:29 | values | forms.js:12:31:12:40 | values.bar | $@ is reinterpreted as HTML without escaping meta-characters. | forms.js:11:24:11:29 | values | DOM text |
|
||||
@@ -185,3 +189,4 @@ edges
|
||||
| xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:71:11:71:32 | $("inpu ... 0).name | DOM text |
|
||||
| xss-through-dom.js:77:4:77:11 | selector | xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | xss-through-dom.js:77:4:77:11 | selector | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:73:20:73:41 | $("inpu ... 0).name | DOM text |
|
||||
| xss-through-dom.js:79:4:79:34 | documen ... t.value | xss-through-dom.js:79:4:79:34 | documen ... t.value | xss-through-dom.js:79:4:79:34 | documen ... t.value | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:79:4:79:34 | documen ... t.value | DOM text |
|
||||
| xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:81:17:81:43 | $('#foo ... rText') | DOM text |
|
||||
|
||||
@@ -77,4 +77,6 @@
|
||||
$(selector); // NOT OK
|
||||
|
||||
$(document.my_form.my_input.value); // NOT OK
|
||||
|
||||
$("#id").html( $('#foo').prop('innerText') ); // NOT OK
|
||||
})();
|
||||
@@ -0,0 +1,3 @@
|
||||
lgtm,codescanning
|
||||
* Updated _Binding a socket to all network interfaces_ (`py/bind-socket-all-network-interfaces`) query to use the new type-tracking approach instead of points-to analysis. You may see differences in the results found by the query, but overall this change should result in a more robust and accurate analysis.
|
||||
* Updated _Binding a socket to all network interfaces_ (`py/bind-socket-all-network-interfaces`) to recognize binding to all interfaces in IPv6 with hostnames `::` and `::0`
|
||||
2
python/change-notes/2021-03-11-api-graph-builtins.md
Normal file
2
python/change-notes/2021-03-11-api-graph-builtins.md
Normal file
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* API graphs now contain nodes for built-in functions and classes. For instance, `API::builtin("open")` is the API graph node corresponding to the built-in `open` function.
|
||||
@@ -11,28 +11,86 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
Value aSocket() { result.getClass() = Value::named("socket.socket") }
|
||||
|
||||
CallNode socketBindCall() {
|
||||
result = aSocket().attr("bind").(CallableValue).getACall() and major_version() = 3
|
||||
or
|
||||
result.getFunction().(AttrNode).getObject("bind").pointsTo(aSocket()) and
|
||||
major_version() = 2
|
||||
/** Gets a hostname that can be used to bind to all interfaces. */
|
||||
private string vulnerableHostname() {
|
||||
result in [
|
||||
// IPv4
|
||||
"0.0.0.0", "",
|
||||
// IPv6
|
||||
"::", "::0"
|
||||
]
|
||||
}
|
||||
|
||||
string allInterfaces() { result = "0.0.0.0" or result = "" }
|
||||
|
||||
Value getTextValue(string address) {
|
||||
result = Value::forUnicode(address) and major_version() = 3
|
||||
/** Gets a reference to a hostname that can be used to bind to all interfaces. */
|
||||
private DataFlow::LocalSourceNode vulnerableHostnameRef(DataFlow::TypeTracker t, string hostname) {
|
||||
t.start() and
|
||||
exists(StrConst allInterfacesStrConst | hostname = vulnerableHostname() |
|
||||
allInterfacesStrConst.getText() = hostname and
|
||||
result.asExpr() = allInterfacesStrConst
|
||||
)
|
||||
or
|
||||
result = Value::forString(address) and major_version() = 2
|
||||
// Due to bad performance when using normal setup with `vulnerableHostnameRef(t2, hostname).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
vulnerableHostnameRef_first_join(t2, hostname, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from CallNode call, TupleValue args, string address
|
||||
pragma[nomagic]
|
||||
private predicate vulnerableHostnameRef_first_join(
|
||||
DataFlow::TypeTracker t2, string hostname, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(vulnerableHostnameRef(t2, hostname), res, summary)
|
||||
}
|
||||
|
||||
/** Gets a reference to a hostname that can be used to bind to all interfaces. */
|
||||
DataFlow::Node vulnerableHostnameRef(string hostname) {
|
||||
vulnerableHostnameRef(DataFlow::TypeTracker::end(), hostname).flowsTo(result)
|
||||
}
|
||||
|
||||
/** Gets a reference to a tuple for which the first element is a hostname that can be used to bind to all interfaces. */
|
||||
private DataFlow::LocalSourceNode vulnerableAddressTuple(DataFlow::TypeTracker t, string hostname) {
|
||||
t.start() and
|
||||
result.asExpr() = any(Tuple tup | tup.getElt(0) = vulnerableHostnameRef(hostname).asExpr())
|
||||
or
|
||||
// Due to bad performance when using normal setup with `vulnerableAddressTuple(t2, hostname).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
vulnerableAddressTuple_first_join(t2, hostname, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate vulnerableAddressTuple_first_join(
|
||||
DataFlow::TypeTracker t2, string hostname, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(vulnerableAddressTuple(t2, hostname), res, summary)
|
||||
}
|
||||
|
||||
/** Gets a reference to a tuple for which the first element is a hostname that can be used to bind to all interfaces. */
|
||||
DataFlow::Node vulnerableAddressTuple(string hostname) {
|
||||
vulnerableAddressTuple(DataFlow::TypeTracker::end(), hostname).flowsTo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of `socket.socket` using _some_ address family.
|
||||
*
|
||||
* See https://docs.python.org/3/library/socket.html
|
||||
*/
|
||||
API::Node socketInstance() { result = API::moduleImport("socket").getMember("socket").getReturn() }
|
||||
|
||||
from DataFlow::CallCfgNode bindCall, DataFlow::Node addressArg, string hostname
|
||||
where
|
||||
call = socketBindCall() and
|
||||
call.getArg(0).pointsTo(args) and
|
||||
args.getItem(0) = getTextValue(address) and
|
||||
address = allInterfaces()
|
||||
select call.getNode(), "'" + address + "' binds a socket to all interfaces."
|
||||
bindCall = socketInstance().getMember("bind").getACall() and
|
||||
addressArg = bindCall.getArg(0) and
|
||||
addressArg = vulnerableAddressTuple(hostname)
|
||||
select bindCall.asExpr(), "'" + hostname + "' binds a socket to all interfaces."
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @name Binding a socket to all network interfaces
|
||||
* @description Binding a socket to all interfaces opens it up to traffic from any IPv4 address
|
||||
* and is therefore associated with security risks.
|
||||
* @kind problem
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
Value aSocket() { result.getClass() = Value::named("socket.socket") }
|
||||
|
||||
CallNode socketBindCall() {
|
||||
result = aSocket().attr("bind").(CallableValue).getACall() and major_version() = 3
|
||||
or
|
||||
result.getFunction().(AttrNode).getObject("bind").pointsTo(aSocket()) and
|
||||
major_version() = 2
|
||||
}
|
||||
|
||||
string allInterfaces() { result = "0.0.0.0" or result = "" }
|
||||
|
||||
Value getTextValue(string address) {
|
||||
result = Value::forUnicode(address) and major_version() = 3
|
||||
or
|
||||
result = Value::forString(address) and major_version() = 2
|
||||
}
|
||||
|
||||
from CallNode call, TupleValue args, string address
|
||||
where
|
||||
call = socketBindCall() and
|
||||
call.getArg(0).pointsTo(args) and
|
||||
args.getItem(0) = getTextValue(address) and
|
||||
address = allInterfaces()
|
||||
select call.getNode(), "'" + address + "' binds a socket to all interfaces."
|
||||
@@ -216,6 +216,9 @@ module API {
|
||||
*/
|
||||
Node moduleImport(string m) { result = Impl::MkModuleImport(m) }
|
||||
|
||||
/** Gets a node corresponding to the built-in with the given name, if any. */
|
||||
Node builtin(string n) { result = moduleImport("builtins").getMember(n) }
|
||||
|
||||
/**
|
||||
* Provides the actual implementation of API graphs, cached for performance.
|
||||
*
|
||||
@@ -300,11 +303,18 @@ module API {
|
||||
MkRoot() or
|
||||
/** An abstract representative for imports of the module called `name`. */
|
||||
MkModuleImport(string name) {
|
||||
imports(_, name)
|
||||
// Ignore the following module name for Python 2, as we alias `__builtin__` to `builtins` elsewhere
|
||||
(name != "__builtin__" or major_version() = 3) and
|
||||
(
|
||||
imports(_, name)
|
||||
or
|
||||
// When we `import foo.bar.baz` we want to create API graph nodes also for the prefixes
|
||||
// `foo` and `foo.bar`:
|
||||
name = any(ImportExpr e | not e.isRelative()).getAnImportedModuleName()
|
||||
)
|
||||
or
|
||||
// When we `import foo.bar.baz` we want to create API graph nodes also for the prefixes
|
||||
// `foo` and `foo.bar`:
|
||||
name = any(ImportExpr e | not e.isRelative()).getAnImportedModuleName()
|
||||
// The `builtins` module should always be implicitly available
|
||||
name = "builtins"
|
||||
} or
|
||||
/** A use of an API member at the node `nd`. */
|
||||
MkUse(DataFlow::Node nd) { use(_, _, nd) }
|
||||
@@ -339,6 +349,24 @@ module API {
|
||||
)
|
||||
}
|
||||
|
||||
private import semmle.python.types.Builtins as Builtins
|
||||
|
||||
/**
|
||||
* Gets a data flow node that is likely to refer to a built-in with the name `name`.
|
||||
*
|
||||
* Currently this is an over-approximation, and does not account for things like overwriting a
|
||||
* built-in with a different value.
|
||||
*/
|
||||
private DataFlow::Node likely_builtin(string name) {
|
||||
result.asCfgNode() =
|
||||
any(NameNode n |
|
||||
n.isGlobal() and
|
||||
n.isLoad() and
|
||||
name = n.getId() and
|
||||
name = any(Builtins::Builtin b).getName()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled
|
||||
* `lbl` in the API graph.
|
||||
@@ -369,6 +397,10 @@ module API {
|
||||
ref.asExpr().(ClassExpr).getABase() = superclass.asExpr()
|
||||
)
|
||||
)
|
||||
or
|
||||
// Built-ins, treated as members of the module `builtins`
|
||||
base = MkModuleImport("builtins") and
|
||||
lbl = Label::member(any(string name | ref = likely_builtin(name)))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,6 +413,11 @@ module API {
|
||||
imports(ref, name)
|
||||
)
|
||||
or
|
||||
// Ensure the Python 2 `__builtin__` module gets the name of the Python 3 `builtins` module.
|
||||
major_version() = 2 and
|
||||
nd = MkModuleImport("builtins") and
|
||||
imports(ref, "__builtin__")
|
||||
or
|
||||
nd = MkUse(ref)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: --lang=2 --max-import-depth=1
|
||||
@@ -0,0 +1,3 @@
|
||||
def python2_style():
|
||||
from __builtin__ import open #$ use=moduleImport("builtins").getMember("open")
|
||||
open("hello.txt") #$ use=moduleImport("builtins").getMember("open").getReturn()
|
||||
30
python/ql/test/experimental/dataflow/ApiGraphs-py2/use.ql
Normal file
30
python/ql/test/experimental/dataflow/ApiGraphs-py2/use.ql
Normal file
@@ -0,0 +1,30 @@
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
class ApiUseTest extends InlineExpectationsTest {
|
||||
ApiUseTest() { this = "ApiUseTest" }
|
||||
|
||||
override string getARelevantTag() { result = "use" }
|
||||
|
||||
private predicate relevant_node(API::Node a, DataFlow::Node n, Location l) {
|
||||
n = a.getAUse() and l = n.getLocation()
|
||||
}
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(API::Node a, DataFlow::Node n | relevant_node(a, n, location) |
|
||||
tag = "use" and
|
||||
// Only report the longest path on this line:
|
||||
value =
|
||||
max(API::Node a2, Location l2 |
|
||||
relevant_node(a2, _, l2) and
|
||||
l2.getFile() = location.getFile() and
|
||||
l2.getStartLine() = location.getStartLine()
|
||||
|
|
||||
a2.getPath()
|
||||
) and
|
||||
element = n.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -100,3 +100,42 @@ def internal():
|
||||
pass
|
||||
|
||||
int_instance = IntMyView() #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getReturn()
|
||||
|
||||
|
||||
# Built-ins
|
||||
|
||||
def use_of_builtins():
|
||||
for x in range(5): #$ use=moduleImport("builtins").getMember("range").getReturn()
|
||||
if x < len([]): #$ use=moduleImport("builtins").getMember("len").getReturn()
|
||||
print("Hello") #$ use=moduleImport("builtins").getMember("print").getReturn()
|
||||
raise Exception("Farewell") #$ use=moduleImport("builtins").getMember("Exception").getReturn()
|
||||
|
||||
def imported_builtins():
|
||||
import builtins #$ use=moduleImport("builtins")
|
||||
def open(f):
|
||||
return builtins.open(f) #$ MISSING: use=moduleImport("builtins").getMember("open").getReturn()
|
||||
|
||||
def redefine_print():
|
||||
def my_print(x):
|
||||
import builtins #$ use=moduleImport("builtins")
|
||||
builtins.print("I'm printing", x) #$ use=moduleImport("builtins").getMember("print").getReturn()
|
||||
print = my_print
|
||||
print("these words")
|
||||
|
||||
def local_redefine_range():
|
||||
range = 5
|
||||
return range
|
||||
|
||||
def global_redefine_range():
|
||||
global range
|
||||
range = 6
|
||||
return range #$ SPURIOUS: use=moduleImport("builtins").getMember("range")
|
||||
|
||||
def obscured_print():
|
||||
p = print #$ use=moduleImport("builtins").getMember("print")
|
||||
p("Can you see me?") #$ use=moduleImport("builtins").getMember("print").getReturn()
|
||||
|
||||
def python2_style():
|
||||
# In Python 3, `__builtin__` has no special meaning.
|
||||
from __builtin__ import open #$ use=moduleImport("__builtin__").getMember("open")
|
||||
open("hello.txt") #$ use=moduleImport("__builtin__").getMember("open").getReturn()
|
||||
|
||||
@@ -2,5 +2,5 @@ import mypkg #$ use=moduleImport("mypkg")
|
||||
print(mypkg.foo) #$ use=moduleImport("mypkg").getMember("foo") // 42
|
||||
try:
|
||||
print(mypkg.bar) #$ use=moduleImport("mypkg").getMember("bar")
|
||||
except AttributeError as e:
|
||||
print(e) # module 'mypkg' has no attribute 'bar'
|
||||
except AttributeError as e: #$ use=moduleImport("builtins").getMember("AttributeError")
|
||||
print(e) #$ use=moduleImport("builtins").getMember("print").getReturn() // module 'mypkg' has no attribute 'bar'
|
||||
|
||||
@@ -3,8 +3,8 @@ import mypkg #$ use=moduleImport("mypkg")
|
||||
print(mypkg.foo) #$ use=moduleImport("mypkg").getMember("foo") // 42
|
||||
try:
|
||||
print(mypkg.bar) #$ use=moduleImport("mypkg").getMember("bar")
|
||||
except AttributeError as e:
|
||||
print(e) # module 'mypkg' has no attribute 'bar'
|
||||
except AttributeError as e: #$ use=moduleImport("builtins").getMember("AttributeError")
|
||||
print(e) #$ use=moduleImport("builtins").getMember("print").getReturn() // module 'mypkg' has no attribute 'bar'
|
||||
|
||||
from mypkg import bar as _bar #$ use=moduleImport("mypkg").getMember("bar")
|
||||
print(mypkg.bar) #$ use=moduleImport("mypkg").getMember("bar") // <module 'mypkg.bar' ...
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
| BindToAllInterfaces_test.py:5:1:5:26 | Attribute() | '0.0.0.0' binds a socket to all interfaces. |
|
||||
| BindToAllInterfaces_test.py:9:1:9:18 | Attribute() | '' binds a socket to all interfaces. |
|
||||
| BindToAllInterfaces_test.py:17:1:17:26 | Attribute() | '0.0.0.0' binds a socket to all interfaces. |
|
||||
| BindToAllInterfaces_test.py:21:1:21:11 | Attribute() | '0.0.0.0' binds a socket to all interfaces. |
|
||||
| BindToAllInterfaces_test.py:26:1:26:20 | Attribute() | '::' binds a socket to all interfaces. |
|
||||
|
||||
@@ -15,3 +15,12 @@ s.bind(('84.68.10.12', 8080))
|
||||
# binds to all interfaces, insecure
|
||||
ALL_LOCALS = "0.0.0.0"
|
||||
s.bind((ALL_LOCALS, 9090))
|
||||
|
||||
# binds to all interfaces, insecure
|
||||
tup = (ALL_LOCALS, 8080)
|
||||
s.bind(tup)
|
||||
|
||||
|
||||
# IPv6
|
||||
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
s.bind(("::", 8080)) # NOT OK
|
||||
|
||||
Reference in New Issue
Block a user