mirror of
https://github.com/github/codeql.git
synced 2026-04-25 08:45:14 +02:00
Merge branch 'main' into henrymercer/rc-3.11-mergeback
This commit is contained in:
4
python/ql/lib/change-notes/2023-09-22-regex-prefix.md
Normal file
4
python/ql/lib/change-notes/2023-09-22-regex-prefix.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: fix
|
||||
---
|
||||
* Subterms of regular expressions encoded as single-line string literals now have better source-location information.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Regular expression fragments residing inside implicitly concatenated strings now have better location information.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Django Rest Framework better handles custom `ModelViewSet` classes functions
|
||||
@@ -154,6 +154,28 @@ class StringPart extends StringPart_, AstNode {
|
||||
override string toString() { result = StringPart_.super.toString() }
|
||||
|
||||
override Location getLocation() { result = StringPart_.super.getLocation() }
|
||||
|
||||
/**
|
||||
* Holds if the content of string `StringPart` is surrounded by
|
||||
* a prefix (including a quote) of length `prefixLength` and
|
||||
* a quote of length `quoteLength`.
|
||||
*/
|
||||
predicate contextSize(int prefixLength, int quoteLength) {
|
||||
exists(int occurrenceOffset |
|
||||
quoteLength = this.getText().regexpFind("\"{3}|\"{1}|'{3}|'{1}", 0, occurrenceOffset).length() and
|
||||
prefixLength = occurrenceOffset + quoteLength
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the length of the content, that is the text between the prefix and the quote.
|
||||
* See `context` for obtaining the prefix and the quote.
|
||||
*/
|
||||
int getContentLength() {
|
||||
exists(int prefixLength, int quoteLength | this.contextSize(prefixLength, quoteLength) |
|
||||
result = this.getText().length() - prefixLength - quoteLength
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class StringPartList extends StringPartList_ { }
|
||||
|
||||
@@ -378,6 +378,68 @@ module SqlExecution {
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a class for modeling NoSQL execution APIs. */
|
||||
module NoSqlExecution {
|
||||
/**
|
||||
* A data-flow node that executes NoSQL queries.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `NoSqlExecution` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the NoSQL query to be executed. */
|
||||
abstract DataFlow::Node getQuery();
|
||||
|
||||
/** Holds if this query will unpack/interpret a dictionary */
|
||||
abstract predicate interpretsDict();
|
||||
|
||||
/** Holds if this query can be dangerous when run on a user-controlled string */
|
||||
abstract predicate vulnerableToStrings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that executes NoSQL queries.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `NoSqlExecution::Range` instead.
|
||||
*/
|
||||
class NoSqlExecution extends DataFlow::Node instanceof NoSqlExecution::Range {
|
||||
/** Gets the argument that specifies the NoSQL query to be executed. */
|
||||
DataFlow::Node getQuery() { result = super.getQuery() }
|
||||
|
||||
/** Holds if this query will unpack/interpret a dictionary */
|
||||
predicate interpretsDict() { super.interpretsDict() }
|
||||
|
||||
/** Holds if this query can be dangerous when run on a user-controlled string */
|
||||
predicate vulnerableToStrings() { super.vulnerableToStrings() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling NoSql sanitization-related APIs. */
|
||||
module NoSqlSanitizer {
|
||||
/**
|
||||
* A data-flow node that collects functions sanitizing NoSQL queries.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `NoSQLSanitizer` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the NoSql query to be sanitized. */
|
||||
abstract DataFlow::Node getAnInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that collects functions sanitizing NoSQL queries.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `NoSQLSanitizer::Range` instead.
|
||||
*/
|
||||
class NoSqlSanitizer extends DataFlow::Node instanceof NoSqlSanitizer::Range {
|
||||
/** Gets the argument that specifies the NoSql query to be sanitized. */
|
||||
DataFlow::Node getAnInput() { result = super.getAnInput() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that executes a regular expression.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,32 @@
|
||||
/** Provides classes for working with files and folders. */
|
||||
|
||||
import python
|
||||
private import codeql.util.FileSystem
|
||||
|
||||
private module Input implements InputSig {
|
||||
abstract class ContainerBase extends @container {
|
||||
abstract string getAbsolutePath();
|
||||
|
||||
ContainerBase getParentContainer() { containerparent(result, this) }
|
||||
|
||||
string toString() { result = this.getAbsolutePath() }
|
||||
}
|
||||
|
||||
class FolderBase extends ContainerBase, @folder {
|
||||
override string getAbsolutePath() { folders(this, result) }
|
||||
}
|
||||
|
||||
class FileBase extends ContainerBase, @file {
|
||||
override string getAbsolutePath() { files(this, result) }
|
||||
}
|
||||
|
||||
predicate hasSourceLocationPrefix = sourceLocationPrefix/1;
|
||||
}
|
||||
|
||||
private module Impl = Make<Input>;
|
||||
|
||||
/** A file */
|
||||
class File extends Container, @file {
|
||||
class File extends Container, Impl::File {
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
@@ -45,11 +70,6 @@ class File extends Container, @file {
|
||||
)
|
||||
}
|
||||
|
||||
override string getAbsolutePath() { files(this, result) }
|
||||
|
||||
/** Gets the URL of this file. */
|
||||
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
|
||||
|
||||
override Container getImportRoot(int n) {
|
||||
/* File stem must be a legal Python identifier */
|
||||
this.getStem().regexpMatch("[^\\d\\W]\\w*") and
|
||||
@@ -108,7 +128,7 @@ private predicate occupied_line(File f, int n) {
|
||||
}
|
||||
|
||||
/** A folder (directory) */
|
||||
class Folder extends Container, @folder {
|
||||
class Folder extends Container, Impl::Folder {
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
@@ -126,11 +146,6 @@ class Folder extends Container, @folder {
|
||||
endcolumn = 0
|
||||
}
|
||||
|
||||
override string getAbsolutePath() { folders(this, result) }
|
||||
|
||||
/** Gets the URL of this folder. */
|
||||
override string getURL() { result = "folder://" + this.getAbsolutePath() }
|
||||
|
||||
override Container getImportRoot(int n) {
|
||||
this.isImportRoot(n) and result = this
|
||||
or
|
||||
@@ -144,34 +159,8 @@ class Folder extends Container, @folder {
|
||||
* A container is an abstract representation of a file system object that can
|
||||
* hold elements of interest.
|
||||
*/
|
||||
abstract class Container extends @container {
|
||||
Container getParent() { containerparent(result, this) }
|
||||
|
||||
/**
|
||||
* Gets a textual representation of the path of this container.
|
||||
*
|
||||
* This is the absolute path of the container.
|
||||
*/
|
||||
string toString() { result = this.getAbsolutePath() }
|
||||
|
||||
/**
|
||||
* Gets the relative path of this file or folder from the root folder of the
|
||||
* analyzed source location. The relative path of the root folder itself is
|
||||
* the empty string.
|
||||
*
|
||||
* This has no result if the container is outside the source root, that is,
|
||||
* if the root folder is not a reflexive, transitive parent of this container.
|
||||
*/
|
||||
string getRelativePath() {
|
||||
exists(string absPath, string pref |
|
||||
absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
|
||||
|
|
||||
absPath = pref and result = ""
|
||||
or
|
||||
absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
|
||||
not result.matches("/%")
|
||||
)
|
||||
}
|
||||
class Container extends Impl::Container {
|
||||
Container getParent() { result = this.getParentContainer() }
|
||||
|
||||
/** Whether this file or folder is part of the standard library */
|
||||
predicate inStdlib() { this.inStdlib(_, _) }
|
||||
@@ -187,135 +176,13 @@ abstract class Container extends @container {
|
||||
)
|
||||
}
|
||||
|
||||
/* Standard cross-language API */
|
||||
/** Gets a file or sub-folder in this container. */
|
||||
Container getAChildContainer() { containerparent(this, result) }
|
||||
|
||||
/** Gets a file in this container. */
|
||||
File getAFile() { result = this.getAChildContainer() }
|
||||
|
||||
/** Gets a sub-folder in this container. */
|
||||
Folder getAFolder() { result = this.getAChildContainer() }
|
||||
|
||||
/**
|
||||
* Gets the absolute, canonical path of this container, using forward slashes
|
||||
* as path separator.
|
||||
*
|
||||
* The path starts with a _root prefix_ followed by zero or more _path
|
||||
* segments_ separated by forward slashes.
|
||||
*
|
||||
* The root prefix is of one of the following forms:
|
||||
*
|
||||
* 1. A single forward slash `/` (Unix-style)
|
||||
* 2. An upper-case drive letter followed by a colon and a forward slash,
|
||||
* such as `C:/` (Windows-style)
|
||||
* 3. Two forward slashes, a computer name, and then another forward slash,
|
||||
* such as `//FileServer/` (UNC-style)
|
||||
*
|
||||
* Path segments are never empty (that is, absolute paths never contain two
|
||||
* contiguous slashes, except as part of a UNC-style root prefix). Also, path
|
||||
* segments never contain forward slashes, and no path segment is of the
|
||||
* form `.` (one dot) or `..` (two dots).
|
||||
*
|
||||
* Note that an absolute path never ends with a forward slash, except if it is
|
||||
* a bare root prefix, that is, the path has no path segments. A container
|
||||
* whose absolute path has no segments is always a `Folder`, not a `File`.
|
||||
*/
|
||||
abstract string getAbsolutePath();
|
||||
|
||||
/**
|
||||
* Gets the base name of this container including extension, that is, the last
|
||||
* segment of its absolute path, or the empty string if it has no segments.
|
||||
*
|
||||
* Here are some examples of absolute paths and the corresponding base names
|
||||
* (surrounded with quotes to avoid ambiguity):
|
||||
*
|
||||
* <table border="1">
|
||||
* <tr><th>Absolute path</th><th>Base name</th></tr>
|
||||
* <tr><td>"/tmp/tst.py"</td><td>"tst.py"</td></tr>
|
||||
* <tr><td>"C:/Program Files (x86)"</td><td>"Program Files (x86)"</td></tr>
|
||||
* <tr><td>"/"</td><td>""</td></tr>
|
||||
* <tr><td>"C:/"</td><td>""</td></tr>
|
||||
* <tr><td>"D:/"</td><td>""</td></tr>
|
||||
* <tr><td>"//FileServer/"</td><td>""</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getBaseName() {
|
||||
result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of this container, that is, the suffix of its base name
|
||||
* after the last dot character, if any.
|
||||
*
|
||||
* In particular,
|
||||
*
|
||||
* - if the name does not include a dot, there is no extension, so this
|
||||
* predicate has no result;
|
||||
* - if the name ends in a dot, the extension is the empty string;
|
||||
* - if the name contains multiple dots, the extension follows the last dot.
|
||||
*
|
||||
* Here are some examples of absolute paths and the corresponding extensions
|
||||
* (surrounded with quotes to avoid ambiguity):
|
||||
*
|
||||
* <table border="1">
|
||||
* <tr><th>Absolute path</th><th>Extension</th></tr>
|
||||
* <tr><td>"/tmp/tst.py"</td><td>"py"</td></tr>
|
||||
* <tr><td>"/tmp/.gitignore"</td><td>"gitignore"</td></tr>
|
||||
* <tr><td>"/bin/bash"</td><td>not defined</td></tr>
|
||||
* <tr><td>"/tmp/tst2."</td><td>""</td></tr>
|
||||
* <tr><td>"/tmp/x.tar.gz"</td><td>"gz"</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getExtension() {
|
||||
result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stem of this container, that is, the prefix of its base name up to
|
||||
* (but not including) the last dot character if there is one, or the entire
|
||||
* base name if there is not.
|
||||
*
|
||||
* Here are some examples of absolute paths and the corresponding stems
|
||||
* (surrounded with quotes to avoid ambiguity):
|
||||
*
|
||||
* <table border="1">
|
||||
* <tr><th>Absolute path</th><th>Stem</th></tr>
|
||||
* <tr><td>"/tmp/tst.py"</td><td>"tst"</td></tr>
|
||||
* <tr><td>"/tmp/.gitignore"</td><td>""</td></tr>
|
||||
* <tr><td>"/bin/bash"</td><td>"bash"</td></tr>
|
||||
* <tr><td>"/tmp/tst2."</td><td>"tst2"</td></tr>
|
||||
* <tr><td>"/tmp/x.tar.gz"</td><td>"x.tar"</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getStem() {
|
||||
result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
|
||||
}
|
||||
|
||||
File getFile(string baseName) {
|
||||
result = this.getAFile() and
|
||||
result.getBaseName() = baseName
|
||||
}
|
||||
|
||||
Folder getFolder(string baseName) {
|
||||
result = this.getAFolder() and
|
||||
result.getBaseName() = baseName
|
||||
}
|
||||
|
||||
Container getParentContainer() { this = result.getAChildContainer() }
|
||||
override Container getParentContainer() { result = super.getParentContainer() }
|
||||
|
||||
Container getChildContainer(string baseName) {
|
||||
result = this.getAChildContainer() and
|
||||
result.getBaseName() = baseName
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a URL representing the location of this container.
|
||||
*
|
||||
* For more information see [Providing URLs](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls).
|
||||
*/
|
||||
abstract string getURL();
|
||||
|
||||
/** Holds if this folder is on the import path. */
|
||||
predicate isImportRoot() { this.isImportRoot(_) }
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ private import semmle.python.frameworks.Aiomysql
|
||||
private import semmle.python.frameworks.Aiosqlite
|
||||
private import semmle.python.frameworks.Aiopg
|
||||
private import semmle.python.frameworks.Asyncpg
|
||||
private import semmle.python.frameworks.BSon
|
||||
private import semmle.python.frameworks.CassandraDriver
|
||||
private import semmle.python.frameworks.ClickhouseDriver
|
||||
private import semmle.python.frameworks.Cryptodome
|
||||
@@ -42,6 +43,7 @@ private import semmle.python.frameworks.Phoenixdb
|
||||
private import semmle.python.frameworks.Psycopg2
|
||||
private import semmle.python.frameworks.Pycurl
|
||||
private import semmle.python.frameworks.Pydantic
|
||||
private import semmle.python.frameworks.PyMongo
|
||||
private import semmle.python.frameworks.Pymssql
|
||||
private import semmle.python.frameworks.PyMySQL
|
||||
private import semmle.python.frameworks.Pyodbc
|
||||
|
||||
@@ -1639,13 +1639,3 @@ private module OutNodes {
|
||||
* `kind`.
|
||||
*/
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
|
||||
|
||||
/**
|
||||
* Holds if flow from `call`'s argument `arg` to parameter `p` is permissible.
|
||||
*
|
||||
* This is a temporary hook to support technical debt in the Go language; do not use.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate golangSpecificParamArgFilter(DataFlowCall call, ParameterNode p, ArgumentNode arg) {
|
||||
any()
|
||||
}
|
||||
|
||||
@@ -297,6 +297,10 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,6 +297,10 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,6 +297,10 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -297,6 +297,10 @@ private module Config implements FullStateConfigSig {
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isBarrierIn(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isBarrierOut(Node node, FlowState state) { none() }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
|
||||
@@ -47,6 +47,10 @@ private module Input implements InputSig<PythonDataFlow> {
|
||||
predicate identityLocalStepExclude(Node n) {
|
||||
not exists(n.getLocation().getFile().getRelativePath())
|
||||
}
|
||||
|
||||
predicate multipleArgumentCallExclude(ArgumentNode arg, DataFlowCall call) {
|
||||
isArgumentNode(arg, call, _)
|
||||
}
|
||||
}
|
||||
|
||||
module Consistency = MakeConsistency<PythonDataFlow, PythonTaintTracking, Input>;
|
||||
|
||||
@@ -539,6 +539,8 @@ predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { any() }
|
||||
|
||||
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
|
||||
|
||||
predicate localMustFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Gets the type of `node`.
|
||||
*/
|
||||
@@ -1000,12 +1002,3 @@ class ContentApprox = Unit;
|
||||
/** Gets an approximated value for content `c`. */
|
||||
pragma[inline]
|
||||
ContentApprox getContentApprox(Content c) { any() }
|
||||
|
||||
/**
|
||||
* Gets an additional term that is added to the `join` and `branch` computations to reflect
|
||||
* an additional forward or backwards branching factor that is not taken into account
|
||||
* when calculating the (virtual) dispatch cost.
|
||||
*
|
||||
* Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter.
|
||||
*/
|
||||
int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() }
|
||||
|
||||
38
python/ql/lib/semmle/python/frameworks/BSon.qll
Normal file
38
python/ql/lib/semmle/python/frameworks/BSon.qll
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `bson` PyPI package.
|
||||
* See
|
||||
* - https://pypi.org/project/bson/
|
||||
* - https://github.com/py-bson/bson
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides models for the `bson` PyPI package.
|
||||
* See
|
||||
* - https://pypi.org/project/bson/
|
||||
* - https://github.com/py-bson/bson
|
||||
*/
|
||||
private module BSon {
|
||||
/**
|
||||
* ObjectId returns a string representing an id.
|
||||
* If at any time ObjectId can't parse it's input (like when a tainted dict in passed in),
|
||||
* then ObjectId will throw an error preventing the query from running.
|
||||
*/
|
||||
private class BsonObjectIdCall extends DataFlow::CallCfgNode, NoSqlSanitizer::Range {
|
||||
BsonObjectIdCall() {
|
||||
exists(API::Node mod |
|
||||
mod = API::moduleImport("bson")
|
||||
or
|
||||
mod = API::moduleImport("bson").getMember(["objectid", "json_util"])
|
||||
|
|
||||
this = mod.getMember("ObjectId").getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ private import semmle.python.regex
|
||||
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
|
||||
private import semmle.python.frameworks.internal.SelfRefMixin
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.security.dataflow.UrlRedirectCustomizations
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -2788,4 +2789,31 @@ module PrivateDjango {
|
||||
|
||||
override predicate csrfEnabled() { decoratorName in ["csrf_protect", "requires_csrf_token"] }
|
||||
}
|
||||
|
||||
private predicate djangoUrlHasAllowedHostAndScheme(
|
||||
DataFlow::GuardNode g, ControlFlowNode node, boolean branch
|
||||
) {
|
||||
exists(API::CallNode call |
|
||||
call =
|
||||
API::moduleImport("django")
|
||||
.getMember("utils")
|
||||
.getMember("http")
|
||||
.getMember("url_has_allowed_host_and_scheme")
|
||||
.getACall() and
|
||||
g = call.asCfgNode() and
|
||||
node = call.getParameter(0, "url").asSink().asCfgNode() and
|
||||
branch = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `django.utils.http.url_has_allowed_host_and_scheme`, considered as a sanitizer-guard for URL redirection.
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/4.2/_modules/django/utils/http/
|
||||
*/
|
||||
private class DjangoAllowedUrl extends UrlRedirect::Sanitizer {
|
||||
DjangoAllowedUrl() {
|
||||
this = DataFlow::BarrierGuard<djangoUrlHasAllowedHostAndScheme/3>::getABarrierNode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
299
python/ql/lib/semmle/python/frameworks/PyMongo.qll
Normal file
299
python/ql/lib/semmle/python/frameworks/PyMongo.qll
Normal file
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the PyMongo bindings.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
private module PyMongo {
|
||||
// API Nodes returning `Mongo` instances.
|
||||
/** Gets a reference to `pymongo.MongoClient` */
|
||||
private API::Node pyMongo() {
|
||||
result = API::moduleImport("pymongo").getMember("MongoClient").getReturn()
|
||||
or
|
||||
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient
|
||||
result =
|
||||
API::moduleImport("pymongo").getMember("mongo_client").getMember("MongoClient").getReturn()
|
||||
}
|
||||
|
||||
/** Gets a reference to `flask_pymongo.PyMongo` */
|
||||
private API::Node flask_PyMongo() {
|
||||
result = API::moduleImport("flask_pymongo").getMember("PyMongo").getReturn()
|
||||
}
|
||||
|
||||
/** Gets a reference to `mongoengine` */
|
||||
private API::Node mongoEngine() { result = API::moduleImport("mongoengine") }
|
||||
|
||||
/** Gets a reference to `flask_mongoengine.MongoEngine` */
|
||||
private API::Node flask_MongoEngine() {
|
||||
result = API::moduleImport("flask_mongoengine").getMember("MongoEngine").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to an initialized `Mongo` instance.
|
||||
* See `pyMongo()`, `flask_PyMongo()`
|
||||
*/
|
||||
private API::Node mongoClientInstance() {
|
||||
result = pyMongo() or
|
||||
result = flask_PyMongo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a `Mongo` DB instance.
|
||||
*
|
||||
* ```py
|
||||
* from flask_pymongo import PyMongo
|
||||
* mongo = PyMongo(app)
|
||||
* mongo.db.user.find({'name': safe_search})
|
||||
* ```
|
||||
*
|
||||
* `mongo.db` would be a `Mongo` instance.
|
||||
*/
|
||||
private API::Node mongoDBInstance() {
|
||||
result = mongoClientInstance().getASubscript()
|
||||
or
|
||||
result = mongoClientInstance().getAMember()
|
||||
or
|
||||
result = mongoEngine().getMember(["get_db", "connect"]).getReturn()
|
||||
or
|
||||
result = mongoEngine().getMember("connection").getMember(["get_db", "connect"]).getReturn()
|
||||
or
|
||||
result = flask_MongoEngine().getMember("get_db").getReturn()
|
||||
or
|
||||
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.get_default_database
|
||||
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.get_database
|
||||
result = mongoClientInstance().getMember(["get_default_database", "get_database"]).getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a `Mongo` collection.
|
||||
*
|
||||
* ```py
|
||||
* from flask_pymongo import PyMongo
|
||||
* mongo = PyMongo(app)
|
||||
* mongo.db.user.find({'name': safe_search})
|
||||
* ```
|
||||
*
|
||||
* `mongo.db.user` would be a `Mongo` collection.
|
||||
*/
|
||||
private API::Node mongoCollection() {
|
||||
result = mongoDBInstance().getASubscript()
|
||||
or
|
||||
result = mongoDBInstance().getAMember()
|
||||
or
|
||||
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/database.html#pymongo.database.Database.get_collection
|
||||
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/database.html#pymongo.database.Database.create_collection
|
||||
result = mongoDBInstance().getMember(["get_collection", "create_collection"]).getReturn()
|
||||
}
|
||||
|
||||
/** Gets the name of a find_* relevant `Mongo` collection-level operation method. */
|
||||
private string mongoCollectionMethodName() {
|
||||
result in [
|
||||
"find", "find_raw_batches", "find_one", "find_one_and_delete", "find_and_modify",
|
||||
"find_one_and_replace", "find_one_and_update", "find_one_or_404"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a `Mongo` collection method call
|
||||
*
|
||||
* ```py
|
||||
* from flask_pymongo import PyMongo
|
||||
* mongo = PyMongo(app)
|
||||
* mongo.db.user.find({'name': safe_search})
|
||||
* ```
|
||||
*
|
||||
* `mongo.db.user.find({'name': safe_search})` would be a collection method call.
|
||||
*/
|
||||
private class MongoCollectionCall extends API::CallNode, NoSqlExecution::Range {
|
||||
MongoCollectionCall() {
|
||||
this = mongoCollection().getMember(mongoCollectionMethodName()).getACall()
|
||||
}
|
||||
|
||||
/** Gets the argument that specifies the NoSQL query to be executed, as an API::node */
|
||||
pragma[inline]
|
||||
API::Node getQueryAsApiNode() {
|
||||
// 'filter' is allowed keyword in pymongo, see https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.find
|
||||
result = this.getParameter(0, "filter")
|
||||
}
|
||||
|
||||
override DataFlow::Node getQuery() { result = this.getQueryAsApiNode().asSink() }
|
||||
|
||||
override predicate interpretsDict() { any() }
|
||||
|
||||
override predicate vulnerableToStrings() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.aggregate
|
||||
*/
|
||||
private class MongoCollectionAggregation extends API::CallNode, NoSqlExecution::Range {
|
||||
MongoCollectionAggregation() { this = mongoCollection().getMember("aggregate").getACall() }
|
||||
|
||||
override DataFlow::Node getQuery() {
|
||||
result = this.getParameter(0, "pipeline").getASubscript().asSink()
|
||||
}
|
||||
|
||||
override predicate interpretsDict() { any() }
|
||||
|
||||
override predicate vulnerableToStrings() { none() }
|
||||
}
|
||||
|
||||
private class MongoMapReduce extends API::CallNode, NoSqlExecution::Range {
|
||||
MongoMapReduce() { this = mongoCollection().getMember("map_reduce").getACall() }
|
||||
|
||||
override DataFlow::Node getQuery() { result in [this.getArg(0), this.getArg(1)] }
|
||||
|
||||
override predicate interpretsDict() { none() }
|
||||
|
||||
override predicate vulnerableToStrings() { any() }
|
||||
}
|
||||
|
||||
private class MongoMapReduceQuery extends API::CallNode, NoSqlExecution::Range {
|
||||
MongoMapReduceQuery() { this = mongoCollection().getMember("map_reduce").getACall() }
|
||||
|
||||
override DataFlow::Node getQuery() { result = this.getArgByName("query") }
|
||||
|
||||
override predicate interpretsDict() { any() }
|
||||
|
||||
override predicate vulnerableToStrings() { none() }
|
||||
}
|
||||
|
||||
/** The `$where` query operator executes a string as JavaScript. */
|
||||
private class WhereQueryOperator extends DataFlow::Node, Decoding::Range {
|
||||
DataFlow::Node query;
|
||||
|
||||
WhereQueryOperator() {
|
||||
exists(API::Node dictionary |
|
||||
dictionary = any(MongoCollectionCall c).getQueryAsApiNode() and
|
||||
query = dictionary.getSubscript("$where").asSink() and
|
||||
this = dictionary.getAValueReachingSink()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = query }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getFormat() { result = "NoSQL" }
|
||||
|
||||
override predicate mayExecuteInput() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `$function` query operator executes its `body` string as JavaScript.
|
||||
*
|
||||
* See https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/#mongodb-expression-exp.-function
|
||||
*/
|
||||
private class FunctionQueryOperator extends DataFlow::Node, Decoding::Range {
|
||||
DataFlow::Node query;
|
||||
|
||||
FunctionQueryOperator() {
|
||||
exists(API::Node dictionary |
|
||||
dictionary =
|
||||
any(MongoCollectionCall c).getQueryAsApiNode().getASubscript*().getSubscript("$function") and
|
||||
query = dictionary.getSubscript("body").asSink() and
|
||||
this = dictionary.getAValueReachingSink()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = query }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getFormat() { result = "NoSQL" }
|
||||
|
||||
override predicate mayExecuteInput() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `$accumulator` query operator executes strings in some of its fields as JavaScript.
|
||||
*
|
||||
* See https://www.mongodb.com/docs/manual/reference/operator/aggregation/accumulator/#mongodb-group-grp.-accumulator
|
||||
*/
|
||||
private class AccumulatorQueryOperator extends DataFlow::Node, Decoding::Range {
|
||||
DataFlow::Node query;
|
||||
|
||||
AccumulatorQueryOperator() {
|
||||
exists(API::Node dictionary |
|
||||
dictionary =
|
||||
mongoCollection()
|
||||
.getMember("aggregate")
|
||||
.getACall()
|
||||
.getParameter(0)
|
||||
.getASubscript*()
|
||||
.getSubscript("$accumulator") and
|
||||
query = dictionary.getSubscript(["init", "accumulate", "merge", "finalize"]).asSink() and
|
||||
this = dictionary.getAValueReachingSink()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = query }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getFormat() { result = "NoSQL" }
|
||||
|
||||
override predicate mayExecuteInput() { any() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a call from a class whose base is a reference to `mongoEngine()` or `flask_MongoEngine()`'s
|
||||
* `Document` or `EmbeddedDocument` objects and its attribute is `objects`.
|
||||
*
|
||||
* ```py
|
||||
* from flask_mongoengine import MongoEngine
|
||||
* db = MongoEngine(app)
|
||||
* class Movie(db.Document):
|
||||
* title = db.StringField(required=True)
|
||||
*
|
||||
* Movie.objects(__raw__=json_search)
|
||||
* ```
|
||||
*
|
||||
* `Movie.objects(__raw__=json_search)` would be the result.
|
||||
*/
|
||||
private class MongoEngineObjectsCall extends DataFlow::CallCfgNode, NoSqlExecution::Range {
|
||||
MongoEngineObjectsCall() {
|
||||
this =
|
||||
[mongoEngine(), flask_MongoEngine()]
|
||||
.getMember(["Document", "EmbeddedDocument"])
|
||||
.getASubclass()
|
||||
.getMember("objects")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getQuery() { result = this.getArgByName(_) }
|
||||
|
||||
override predicate interpretsDict() { any() }
|
||||
|
||||
override predicate vulnerableToStrings() { none() }
|
||||
}
|
||||
|
||||
/** Gets a reference to `mongosanitizer.sanitizer.sanitize` */
|
||||
private class MongoSanitizerCall extends DataFlow::CallCfgNode, NoSqlSanitizer::Range {
|
||||
MongoSanitizerCall() {
|
||||
this =
|
||||
API::moduleImport("mongosanitizer").getMember("sanitizer").getMember("sanitize").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An equality operator can protect against dictionary interpretation.
|
||||
* For instance, in `{'password': {"$eq": password} }`, if a dictionary is injected into
|
||||
* `password`, it will not match.
|
||||
*/
|
||||
private class EqualityOperator extends DataFlow::Node, NoSqlSanitizer::Range {
|
||||
EqualityOperator() {
|
||||
this =
|
||||
any(MongoCollectionCall c).getQueryAsApiNode().getASubscript*().getSubscript("$eq").asSink()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this }
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,10 @@ private module RestFramework {
|
||||
"initial", "http_method_not_allowed", "permission_denied", "throttled",
|
||||
"get_authenticate_header", "perform_content_negotiation", "perform_authentication",
|
||||
"check_permissions", "check_object_permissions", "check_throttles", "determine_version",
|
||||
"initialize_request", "finalize_response", "dispatch", "options"
|
||||
"initialize_request", "finalize_response", "dispatch", "options",
|
||||
// ModelViewSet
|
||||
// https://github.com/encode/django-rest-framework/blob/master/rest_framework/viewsets.py
|
||||
"create", "retrieve", "update", "partial_update", "destroy", "list"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1815,51 +1815,95 @@ private module StdlibPrivate {
|
||||
// ---------------------------------------------------------------------------
|
||||
// BaseHTTPServer (Python 2 only)
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `BaseHttpServer` module. */
|
||||
API::Node baseHttpServer() { result = API::moduleImport("BaseHTTPServer") }
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `BaseHttpServer` module.
|
||||
*/
|
||||
deprecated API::Node baseHttpServer() { result = API::moduleImport("BaseHTTPServer") }
|
||||
|
||||
/** Provides models for the `BaseHttpServer` module. */
|
||||
module BaseHttpServer {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `BaseHttpServer` module.
|
||||
*/
|
||||
deprecated module BaseHttpServer {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `BaseHTTPServer.BaseHTTPRequestHandler` class (Python 2 only).
|
||||
*/
|
||||
module BaseHttpRequestHandler {
|
||||
/** Gets a reference to the `BaseHttpServer.BaseHttpRequestHandler` class. */
|
||||
API::Node classRef() { result = baseHttpServer().getMember("BaseHTTPRequestHandler") }
|
||||
deprecated module BaseHttpRequestHandler {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `BaseHttpServer.BaseHttpRequestHandler` class.
|
||||
*/
|
||||
deprecated API::Node classRef() {
|
||||
result = baseHttpServer().getMember("BaseHTTPRequestHandler")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SimpleHTTPServer (Python 2 only)
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `SimpleHttpServer` module. */
|
||||
API::Node simpleHttpServer() { result = API::moduleImport("SimpleHTTPServer") }
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `SimpleHttpServer` module.
|
||||
*/
|
||||
deprecated API::Node simpleHttpServer() { result = API::moduleImport("SimpleHTTPServer") }
|
||||
|
||||
/** Provides models for the `SimpleHttpServer` module. */
|
||||
module SimpleHttpServer {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `SimpleHttpServer` module.
|
||||
*/
|
||||
deprecated module SimpleHttpServer {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `SimpleHTTPServer.SimpleHTTPRequestHandler` class (Python 2 only).
|
||||
*/
|
||||
module SimpleHttpRequestHandler {
|
||||
/** Gets a reference to the `SimpleHttpServer.SimpleHttpRequestHandler` class. */
|
||||
API::Node classRef() { result = simpleHttpServer().getMember("SimpleHTTPRequestHandler") }
|
||||
deprecated module SimpleHttpRequestHandler {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `SimpleHttpServer.SimpleHttpRequestHandler` class.
|
||||
*/
|
||||
deprecated API::Node classRef() {
|
||||
result = simpleHttpServer().getMember("SimpleHTTPRequestHandler")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CGIHTTPServer (Python 2 only)
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `CGIHTTPServer` module. */
|
||||
API::Node cgiHttpServer() { result = API::moduleImport("CGIHTTPServer") }
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `CGIHTTPServer` module.
|
||||
*/
|
||||
deprecated API::Node cgiHttpServer() { result = API::moduleImport("CGIHTTPServer") }
|
||||
|
||||
/** Provides models for the `CGIHTTPServer` module. */
|
||||
module CgiHttpServer {
|
||||
deprecated module CgiHttpServer {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `CGIHTTPServer.CGIHTTPRequestHandler` class (Python 2 only).
|
||||
*/
|
||||
module CgiHttpRequestHandler {
|
||||
/** Gets a reference to the `CGIHTTPServer.CgiHttpRequestHandler` class. */
|
||||
API::Node classRef() { result = cgiHttpServer().getMember("CGIHTTPRequestHandler") }
|
||||
deprecated module CgiHttpRequestHandler {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `CGIHTTPServer.CgiHttpRequestHandler` class.
|
||||
*/
|
||||
deprecated API::Node classRef() {
|
||||
result = cgiHttpServer().getMember("CGIHTTPRequestHandler")
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for CgiHttpRequestHandler */
|
||||
@@ -1872,47 +1916,69 @@ private module StdlibPrivate {
|
||||
// ---------------------------------------------------------------------------
|
||||
// http (Python 3 only)
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `http` module. */
|
||||
API::Node http() { result = API::moduleImport("http") }
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `http` module.
|
||||
*/
|
||||
deprecated API::Node http() { result = API::moduleImport("http") }
|
||||
|
||||
/** Provides models for the `http` module. */
|
||||
module StdlibHttp {
|
||||
deprecated module StdlibHttp {
|
||||
// -------------------------------------------------------------------------
|
||||
// http.server
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `http.server` module. */
|
||||
API::Node server() { result = http().getMember("server") }
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `http.server` module.
|
||||
*/
|
||||
deprecated API::Node server() { result = http().getMember("server") }
|
||||
|
||||
/** Provides models for the `http.server` module */
|
||||
module Server {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `http.server` module
|
||||
*/
|
||||
deprecated module Server {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `http.server.BaseHTTPRequestHandler` class (Python 3 only).
|
||||
*
|
||||
* See https://docs.python.org/3.9/library/http.server.html#http.server.BaseHTTPRequestHandler.
|
||||
*/
|
||||
module BaseHttpRequestHandler {
|
||||
deprecated module BaseHttpRequestHandler {
|
||||
/** Gets a reference to the `http.server.BaseHttpRequestHandler` class. */
|
||||
API::Node classRef() { result = server().getMember("BaseHTTPRequestHandler") }
|
||||
deprecated API::Node classRef() { result = server().getMember("BaseHTTPRequestHandler") }
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `http.server.SimpleHTTPRequestHandler` class (Python 3 only).
|
||||
*
|
||||
* See https://docs.python.org/3.9/library/http.server.html#http.server.SimpleHTTPRequestHandler.
|
||||
*/
|
||||
module SimpleHttpRequestHandler {
|
||||
deprecated module SimpleHttpRequestHandler {
|
||||
/** Gets a reference to the `http.server.SimpleHttpRequestHandler` class. */
|
||||
API::Node classRef() { result = server().getMember("SimpleHTTPRequestHandler") }
|
||||
deprecated API::Node classRef() { result = server().getMember("SimpleHTTPRequestHandler") }
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Provides models for the `http.server.CGIHTTPRequestHandler` class (Python 3 only).
|
||||
*
|
||||
* See https://docs.python.org/3.9/library/http.server.html#http.server.CGIHTTPRequestHandler.
|
||||
*/
|
||||
module CgiHttpRequestHandler {
|
||||
/** Gets a reference to the `http.server.CGIHTTPRequestHandler` class. */
|
||||
API::Node classRef() { result = server().getMember("CGIHTTPRequestHandler") }
|
||||
deprecated module CgiHttpRequestHandler {
|
||||
/**
|
||||
* DEPRECATED: Use API-graphs directly instead.
|
||||
*
|
||||
* Gets a reference to the `http.server.CGIHTTPRequestHandler` class.
|
||||
*/
|
||||
deprecated API::Node classRef() { result = server().getMember("CGIHTTPRequestHandler") }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for CgiHttpRequestHandler */
|
||||
@@ -1933,13 +1999,13 @@ private module StdlibPrivate {
|
||||
result =
|
||||
[
|
||||
// Python 2
|
||||
BaseHttpServer::BaseHttpRequestHandler::classRef(),
|
||||
SimpleHttpServer::SimpleHttpRequestHandler::classRef(),
|
||||
CgiHttpServer::CgiHttpRequestHandler::classRef(),
|
||||
API::moduleImport("BaseHTTPServer").getMember("BaseHTTPRequestHandler"),
|
||||
API::moduleImport("SimpleHTTPServer").getMember("SimpleHTTPRequestHandler"),
|
||||
API::moduleImport("CGIHTTPServer").getMember("CGIHTTPRequestHandler"),
|
||||
// Python 3
|
||||
StdlibHttp::Server::BaseHttpRequestHandler::classRef(),
|
||||
StdlibHttp::Server::SimpleHttpRequestHandler::classRef(),
|
||||
StdlibHttp::Server::CgiHttpRequestHandler::classRef()
|
||||
API::moduleImport("http").getMember("server").getMember("BaseHTTPRequestHandler"),
|
||||
API::moduleImport("http").getMember("server").getMember("SimpleHTTPRequestHandler"),
|
||||
API::moduleImport("http").getMember("server").getMember("CGIHTTPRequestHandler"),
|
||||
].getASubclass*()
|
||||
}
|
||||
|
||||
@@ -4396,6 +4462,117 @@ private module StdlibPrivate {
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// asyncio
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Provides models for the `asyncio` module. */
|
||||
module AsyncIO {
|
||||
/**
|
||||
* A call to the `asyncio.create_subprocess_exec` function (also accessible via the `subprocess` module of `asyncio`)
|
||||
*
|
||||
* See https://docs.python.org/3/library/asyncio-subprocess.html#creating-subprocesses
|
||||
*/
|
||||
private class CreateSubprocessExec extends SystemCommandExecution::Range,
|
||||
FileSystemAccess::Range, API::CallNode
|
||||
{
|
||||
CreateSubprocessExec() {
|
||||
this = API::moduleImport("asyncio").getMember("create_subprocess_exec").getACall()
|
||||
or
|
||||
this =
|
||||
API::moduleImport("asyncio")
|
||||
.getMember("subprocess")
|
||||
.getMember("create_subprocess_exec")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(0, "program").asSink() }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
none() // this is a safe API.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `asyncio.create_subprocess_shell` function (also accessible via the `subprocess` module of `asyncio`)
|
||||
*
|
||||
* See https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.create_subprocess_shell
|
||||
*/
|
||||
private class CreateSubprocessShell extends SystemCommandExecution::Range,
|
||||
FileSystemAccess::Range, API::CallNode
|
||||
{
|
||||
CreateSubprocessShell() {
|
||||
this = API::moduleImport("asyncio").getMember("create_subprocess_shell").getACall()
|
||||
or
|
||||
this =
|
||||
API::moduleImport("asyncio")
|
||||
.getMember("subprocess")
|
||||
.getMember("create_subprocess_shell")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(0, "cmd").asSink() }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an asyncio event loop (an object with basetype `AbstractEventLoop`).
|
||||
*
|
||||
* See https://docs.python.org/3/library/asyncio-eventloop.html
|
||||
*/
|
||||
private API::Node getAsyncioEventLoop() {
|
||||
result = API::moduleImport("asyncio").getMember("get_running_loop").getReturn()
|
||||
or
|
||||
result = API::moduleImport("asyncio").getMember("get_event_loop").getReturn() // deprecated in Python 3.10.0 and later
|
||||
or
|
||||
result = API::moduleImport("asyncio").getMember("new_event_loop").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `subprocess_exec` on an event loop instance.
|
||||
*
|
||||
* See https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.subprocess_exec
|
||||
*/
|
||||
private class EventLoopSubprocessExec extends API::CallNode, SystemCommandExecution::Range,
|
||||
FileSystemAccess::Range
|
||||
{
|
||||
EventLoopSubprocessExec() {
|
||||
this = getAsyncioEventLoop().getMember("subprocess_exec").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getArg(1) }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
none() // this is a safe API.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `subprocess_shell` on an event loop instance.
|
||||
*
|
||||
* See https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.subprocess_shell
|
||||
*/
|
||||
private class EventLoopSubprocessShell extends API::CallNode, SystemCommandExecution::Range,
|
||||
FileSystemAccess::Range
|
||||
{
|
||||
EventLoopSubprocessShell() {
|
||||
this = getAsyncioEventLoop().getMember("subprocess_shell").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(1, "cmd").asSink() }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -223,14 +223,54 @@ module Impl implements RegexTreeViewSig {
|
||||
*/
|
||||
Location getLocation() { result = re.getLocation() }
|
||||
|
||||
/** Gets the accumulated length of string parts with lower index than `index`, if any. */
|
||||
private int getPartOffset(int index) {
|
||||
index = 0 and result = 0
|
||||
or
|
||||
index > 0 and
|
||||
exists(int previousOffset | previousOffset = this.getPartOffset(index - 1) |
|
||||
result =
|
||||
previousOffset + re.(StrConst).getImplicitlyConcatenatedPart(index - 1).getContentLength()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `StringPart` in which this `RegExpTerm` resides, if any.
|
||||
* `localOffset` will be the offset of this `RegExpTerm` inside `result`.
|
||||
*/
|
||||
StringPart getPart(int localOffset) {
|
||||
exists(int index, int prefixLength | index = max(int i | this.getPartOffset(i) <= start) |
|
||||
result = re.(StrConst).getImplicitlyConcatenatedPart(index) and
|
||||
result.contextSize(prefixLength, _) and
|
||||
// Example:
|
||||
// re.compile('...' r"""...this..""")
|
||||
// - `start` is the offset from `(` to `this` as counted after concatenating all parts.
|
||||
// - we subtract the length of the previous `StringPart`s, `'...'`, to know how far into this `StringPart` we go.
|
||||
// - as the prefix 'r"""' is part of the `StringPart`, `this` is found that much further in.
|
||||
localOffset = start - this.getPartOffset(index) + prefixLength
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this term is found at the specified location offsets. */
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
exists(int re_start |
|
||||
re.getLocation().hasLocationInfo(filepath, startline, re_start, endline, _) and
|
||||
startcolumn = re_start + start + 4 and
|
||||
endcolumn = re_start + end + 3
|
||||
not exists(this.getPart(_)) and
|
||||
exists(int re_start, int prefix_len | prefix_len = re.getPrefix().length() |
|
||||
re.getLocation().hasLocationInfo(filepath, startline, re_start, _, _) and
|
||||
startcolumn = re_start + start + prefix_len and
|
||||
endline = startline and
|
||||
endcolumn = re_start + end + prefix_len - 1
|
||||
/* inclusive vs exclusive */
|
||||
)
|
||||
or
|
||||
exists(StringPart part, int localOffset, int partStartColumn |
|
||||
part = this.getPart(localOffset)
|
||||
|
|
||||
part.getLocation().hasLocationInfo(filepath, startline, partStartColumn, _, _) and
|
||||
startcolumn = partStartColumn + localOffset and
|
||||
endline = startline and
|
||||
endcolumn = (end - start) + startcolumn
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ private module FindRegexMode {
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Regex` instead.
|
||||
* DEPRECATED: Use `RegExp` instead.
|
||||
*/
|
||||
deprecated class Regex = RegExp;
|
||||
|
||||
@@ -327,6 +327,17 @@ class RegExp extends Expr instanceof StrConst {
|
||||
/** Gets the text of this regex */
|
||||
string getText() { result = super.getText() }
|
||||
|
||||
/**
|
||||
* Gets the prefix of this regex
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - The prefix of `'x*y'` is `'`.
|
||||
* - The prefix of `r''` is `r'`.
|
||||
* - The prefix of `r"""x*y"""` is `r"""`.
|
||||
*/
|
||||
string getPrefix() { result = super.getPrefix() }
|
||||
|
||||
/** Gets the `i`th character of this regex */
|
||||
string getChar(int i) { result = this.getText().charAt(i) }
|
||||
|
||||
|
||||
@@ -16,9 +16,11 @@ private import semmle.python.dataflow.new.SensitiveDataSources
|
||||
import CleartextLoggingCustomizations::CleartextLogging
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `CleartextLoggingFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "Clear-text logging of sensitive information".
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CleartextLogging" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -31,3 +33,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
private module CleartextLoggingConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "Clear-text logging of sensitive information" vulnerabilities. */
|
||||
module CleartextLoggingFlow = TaintTracking::Global<CleartextLoggingConfig>;
|
||||
|
||||
@@ -16,9 +16,11 @@ private import semmle.python.dataflow.new.SensitiveDataSources
|
||||
import CleartextStorageCustomizations::CleartextStorage
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `CleartextStorageFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "Clear-text storage of sensitive information".
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CleartextStorage" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -31,3 +33,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
private module CleartextStorageConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "Clear-text storage of sensitive information" vulnerabilities. */
|
||||
module CleartextStorageFlow = TaintTracking::Global<CleartextStorageConfig>;
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import CodeInjectionCustomizations::CodeInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `CodeInjectionFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "code injection" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CodeInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -27,3 +29,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module CodeInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "code injection" vulnerabilities. */
|
||||
module CodeInjectionFlow = TaintTracking::Global<CodeInjectionConfig>;
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import CommandInjectionCustomizations::CommandInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `CommandInjectionFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "command injection" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CommandInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -27,3 +29,17 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "command injection" vulnerabilities.
|
||||
*/
|
||||
module CommandInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "command injection" vulnerabilities. */
|
||||
module CommandInjectionFlow = TaintTracking::Global<CommandInjectionConfig>;
|
||||
|
||||
@@ -14,10 +14,12 @@ import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import LdapInjectionCustomizations::LdapInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `LdapInjectionDnFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting LDAP injection vulnerabilities
|
||||
* via the distinguished name (DN) parameter of an LDAP search.
|
||||
*/
|
||||
class DnConfiguration extends TaintTracking::Configuration {
|
||||
deprecated class DnConfiguration extends TaintTracking::Configuration {
|
||||
DnConfiguration() { this = "LdapDnInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -31,11 +33,24 @@ class DnConfiguration extends TaintTracking::Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
private module LdapInjectionDnConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof DnSink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof DnSanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "LDAP injection via the distinguished name (DN) parameter" vulnerabilities. */
|
||||
module LdapInjectionDnFlow = TaintTracking::Global<LdapInjectionDnConfig>;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `LdapInjectionFilterFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting LDAP injection vulnerabilities
|
||||
* via the filter parameter of an LDAP search.
|
||||
*/
|
||||
class FilterConfiguration extends TaintTracking::Configuration {
|
||||
deprecated class FilterConfiguration extends TaintTracking::Configuration {
|
||||
FilterConfiguration() { this = "LdapFilterInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -48,3 +63,19 @@ class FilterConfiguration extends TaintTracking::Configuration {
|
||||
guard instanceof FilterSanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module LdapInjectionFilterConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof FilterSink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof FilterSanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "LDAP injection via the filter parameter" vulnerabilities. */
|
||||
module LdapInjectionFilterFlow = TaintTracking::Global<LdapInjectionFilterConfig>;
|
||||
|
||||
/** Global taint-tracking for detecting "LDAP injection" vulnerabilities. */
|
||||
module LdapInjectionFlow =
|
||||
DataFlow::MergePathGraph<LdapInjectionDnFlow::PathNode, LdapInjectionFilterFlow::PathNode,
|
||||
LdapInjectionDnFlow::PathGraph, LdapInjectionFilterFlow::PathGraph>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for tracking untrusted user input used in log entries.
|
||||
* Provides a taint-tracking configuration for tracking "log injection" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `LogInjection::Configuration` is needed, otherwise
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import LogInjectionCustomizations::LogInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `LogInjectionFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for tracking untrusted user input used in log entries.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "LogInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -27,3 +29,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module LogInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "log injection" vulnerabilities. */
|
||||
module LogInjectionFlow = TaintTracking::Global<LogInjectionConfig>;
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "NoSql injection"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "NoSql injection"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module NoSqlInjection {
|
||||
private newtype TFlowState =
|
||||
TString() or
|
||||
TDict()
|
||||
|
||||
/** A flow state, tracking the structure of the data. */
|
||||
abstract class FlowState extends TFlowState {
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
}
|
||||
|
||||
/** A state where the tracked data is only a string. */
|
||||
class String extends FlowState, TString {
|
||||
override string toString() { result = "String" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A state where the tracked data has been converted to
|
||||
* a dictionary.
|
||||
*
|
||||
* We include cases where data represent JSON objects, so
|
||||
* it could actually still be just a string. It could
|
||||
* also contain query operators, or even JavaScript code.
|
||||
*/
|
||||
class Dict extends FlowState, TDict {
|
||||
override string toString() { result = "Dict" }
|
||||
}
|
||||
|
||||
/** A source allowing string inputs. */
|
||||
abstract class StringSource extends DataFlow::Node { }
|
||||
|
||||
/** A source of allowing dictionaries. */
|
||||
abstract class DictSource extends DataFlow::Node { }
|
||||
|
||||
/** A sink vulnerable to user controlled strings. */
|
||||
abstract class StringSink extends DataFlow::Node { }
|
||||
|
||||
/** A sink vulnerable to user controlled dictionaries. */
|
||||
abstract class DictSink extends DataFlow::Node { }
|
||||
|
||||
/** A data flow node where a string is converted into a dictionary. */
|
||||
abstract class StringToDictConversion extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the string to be converted. */
|
||||
abstract DataFlow::Node getAnInput();
|
||||
|
||||
/** Gets the resulting dictionary. */
|
||||
abstract DataFlow::Node getOutput();
|
||||
}
|
||||
|
||||
/** A remote flow source considered a source of user controlled strings. */
|
||||
class RemoteFlowSourceAsStringSource extends RemoteFlowSource, StringSource { }
|
||||
|
||||
/** A NoSQL query that is vulnerable to user controlled strings. */
|
||||
class NoSqlExecutionAsStringSink extends StringSink {
|
||||
NoSqlExecutionAsStringSink() {
|
||||
exists(NoSqlExecution noSqlExecution | this = noSqlExecution.getQuery() |
|
||||
noSqlExecution.vulnerableToStrings()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A NoSQL query that is vulnerable to user controlled dictionaries. */
|
||||
class NoSqlExecutionAsDictSink extends DictSink {
|
||||
NoSqlExecutionAsDictSink() {
|
||||
exists(NoSqlExecution noSqlExecution | this = noSqlExecution.getQuery() |
|
||||
noSqlExecution.interpretsDict()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A JSON decoding converts a string to a dictionary. */
|
||||
class JsonDecoding extends Decoding, StringToDictConversion {
|
||||
JsonDecoding() { this.getFormat() = "JSON" }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = Decoding.super.getAnInput() }
|
||||
|
||||
override DataFlow::Node getOutput() { result = Decoding.super.getOutput() }
|
||||
}
|
||||
|
||||
/** A NoSQL decoding interprets a string as a dictionary. */
|
||||
class NoSqlDecoding extends Decoding, StringToDictConversion {
|
||||
NoSqlDecoding() { this.getFormat() = "NoSQL" }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = Decoding.super.getAnInput() }
|
||||
|
||||
override DataFlow::Node getOutput() { result = Decoding.super.getOutput() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting NoSQL injection vulnerabilities
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.Concepts
|
||||
private import NoSqlInjectionCustomizations::NoSqlInjection as C
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting NoSQL injection vulnerabilities.
|
||||
*/
|
||||
module NoSqlInjectionConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = C::FlowState;
|
||||
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
source instanceof C::StringSource and
|
||||
state instanceof C::String
|
||||
or
|
||||
source instanceof C::DictSource and
|
||||
state instanceof C::Dict
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) {
|
||||
sink instanceof C::StringSink and
|
||||
(
|
||||
state instanceof C::String
|
||||
or
|
||||
// since Dicts can include strings,
|
||||
// e.g. JSON objects can encode strings.
|
||||
state instanceof C::Dict
|
||||
)
|
||||
or
|
||||
sink instanceof C::DictSink and
|
||||
state instanceof C::Dict
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node, FlowState state) {
|
||||
// Block `String` paths here, since they change state to `Dict`
|
||||
exists(C::StringToDictConversion c | node = c.getOutput()) and
|
||||
state instanceof C::String
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(
|
||||
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
|
||||
) {
|
||||
exists(C::StringToDictConversion c |
|
||||
nodeFrom = c.getAnInput() and
|
||||
nodeTo = c.getOutput()
|
||||
) and
|
||||
stateFrom instanceof C::String and
|
||||
stateTo instanceof C::Dict
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node = any(NoSqlSanitizer noSqlSanitizer).getAnInput()
|
||||
}
|
||||
}
|
||||
|
||||
module NoSqlInjectionFlow = TaintTracking::GlobalWithState<NoSqlInjectionConfig>;
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import PamAuthorizationCustomizations::PamAuthorizationCustomizations
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `PamAuthorizationFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "PAM Authorization" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "PamAuthorization" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof Source }
|
||||
@@ -37,3 +39,28 @@ class Configuration extends TaintTracking::Configuration {
|
||||
exists(VulnPamAuthCall c | c.getArg(0) = node1 | node2 = c)
|
||||
}
|
||||
}
|
||||
|
||||
private module PamAuthorizationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
// Models flow from a remotely supplied username field to a PAM `handle`.
|
||||
// `retval = pam_start(service, username, byref(conv), byref(handle))`
|
||||
exists(API::CallNode pamStart, DataFlow::Node handle, API::CallNode pointer |
|
||||
pointer = API::moduleImport("ctypes").getMember(["pointer", "byref"]).getACall() and
|
||||
pamStart = libPam().getMember("pam_start").getACall() and
|
||||
pointer = pamStart.getArg(3) and
|
||||
handle = pointer.getArg(0) and
|
||||
pamStart.getArg(1) = node1 and
|
||||
handle = node2
|
||||
)
|
||||
or
|
||||
// Flow from handle to the authenticate call in the final step
|
||||
exists(VulnPamAuthCall c | c.getArg(0) = node1 | node2 = c)
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "PAM Authorization" vulnerabilities. */
|
||||
module PamAuthorizationFlow = TaintTracking::Global<PamAuthorizationConfig>;
|
||||
|
||||
@@ -13,6 +13,8 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import PathInjectionCustomizations::PathInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `PathInjectionFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "path injection" vulnerabilities.
|
||||
*
|
||||
* This configuration uses two flow states, `NotNormalized` and `NormalizedUnchecked`,
|
||||
@@ -25,7 +27,7 @@ import PathInjectionCustomizations::PathInjection
|
||||
*
|
||||
* Such checks are ineffective in the `NotNormalized` state.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "PathInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
|
||||
@@ -74,3 +76,52 @@ class NotNormalized extends DataFlow::FlowState {
|
||||
class NormalizedUnchecked extends DataFlow::FlowState {
|
||||
NormalizedUnchecked() { this = "NormalizedUnchecked" }
|
||||
}
|
||||
|
||||
/**
|
||||
* This configuration uses two flow states, `NotNormalized` and `NormalizedUnchecked`,
|
||||
* to track the requirement that a file path must be first normalized and then checked
|
||||
* before it is safe to use.
|
||||
*
|
||||
* At sources, paths are assumed not normalized. At normalization points, they change
|
||||
* state to `NormalizedUnchecked` after which they can be made safe by an appropriate
|
||||
* check of the prefix.
|
||||
*
|
||||
* Such checks are ineffective in the `NotNormalized` state.
|
||||
*/
|
||||
module PathInjectionConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = DataFlow::FlowState;
|
||||
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
source instanceof Source and state instanceof NotNormalized
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) {
|
||||
sink instanceof Sink and
|
||||
(
|
||||
state instanceof NotNormalized or
|
||||
state instanceof NormalizedUnchecked
|
||||
)
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node, FlowState state) {
|
||||
// Block `NotNormalized` paths here, since they change state to `NormalizedUnchecked`
|
||||
node instanceof Path::PathNormalization and
|
||||
state instanceof NotNormalized
|
||||
or
|
||||
node instanceof Path::SafeAccessCheck and
|
||||
state instanceof NormalizedUnchecked
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(
|
||||
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
|
||||
) {
|
||||
nodeFrom = nodeTo.(Path::PathNormalization).getPathArg() and
|
||||
stateFrom instanceof NotNormalized and
|
||||
stateTo instanceof NormalizedUnchecked
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "path injection" vulnerabilities. */
|
||||
module PathInjectionFlow = TaintTracking::GlobalWithState<PathInjectionConfig>;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.DataFlow2
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import PolynomialReDoSCustomizations::PolynomialReDoS
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `PolynomialReDoSFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "polynomial regular expression denial of service (ReDoS)" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "PolynomialReDoS" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -27,3 +29,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module PolynomialReDoSConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "polynomial regular expression denial of service (ReDoS)" vulnerabilities. */
|
||||
module PolynomialReDoSFlow = TaintTracking::Global<PolynomialReDoSConfig>;
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import ReflectedXSSCustomizations::ReflectedXss
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `ReflectedXssFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ReflectedXSS" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -27,3 +29,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module ReflectedXssConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "reflected server-side cross-site scripting" vulnerabilities. */
|
||||
module ReflectedXssFlow = TaintTracking::Global<ReflectedXssConfig>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting regular expression injection
|
||||
* Provides a taint-tracking configuration for detecting "regular expression injection"
|
||||
* vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
@@ -13,9 +13,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import RegexInjectionCustomizations::RegexInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `RegexInjectionFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "RegexInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -28,3 +30,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module RegexInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "regular expression injection" vulnerabilities. */
|
||||
module RegexInjectionFlow = TaintTracking::Global<RegexInjectionConfig>;
|
||||
|
||||
@@ -13,6 +13,8 @@ import semmle.python.Concepts
|
||||
import ServerSideRequestForgeryCustomizations::ServerSideRequestForgery
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FullServerSideRequestForgeryFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "Server-side request forgery" vulnerabilities.
|
||||
*
|
||||
* This configuration has a sanitizer to limit results to cases where attacker has full control of URL.
|
||||
@@ -21,7 +23,7 @@ import ServerSideRequestForgeryCustomizations::ServerSideRequestForgery
|
||||
* You should use the `fullyControlledRequest` to only select results where all
|
||||
* URL parts are fully controlled.
|
||||
*/
|
||||
class FullServerSideRequestForgeryConfiguration extends TaintTracking::Configuration {
|
||||
deprecated class FullServerSideRequestForgeryConfiguration extends TaintTracking::Configuration {
|
||||
FullServerSideRequestForgeryConfiguration() { this = "FullServerSideRequestForgery" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -39,24 +41,51 @@ class FullServerSideRequestForgeryConfiguration extends TaintTracking::Configura
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This configuration has a sanitizer to limit results to cases where attacker has full control of URL.
|
||||
* See `PartialServerSideRequestForgery` for a variant without this requirement.
|
||||
*
|
||||
* You should use the `fullyControlledRequest` to only select results where all
|
||||
* URL parts are fully controlled.
|
||||
*/
|
||||
private module FullServerSideRequestForgeryConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof Sanitizer
|
||||
or
|
||||
node instanceof FullUrlControlSanitizer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global taint-tracking for detecting "Full server-side request forgery" vulnerabilities.
|
||||
*
|
||||
* You should use the `fullyControlledRequest` to only select results where all
|
||||
* URL parts are fully controlled.
|
||||
*/
|
||||
module FullServerSideRequestForgeryFlow = TaintTracking::Global<FullServerSideRequestForgeryConfig>;
|
||||
|
||||
/**
|
||||
* Holds if all URL parts of `request` is fully user controlled.
|
||||
*/
|
||||
predicate fullyControlledRequest(Http::Client::Request request) {
|
||||
exists(FullServerSideRequestForgeryConfiguration fullConfig |
|
||||
forall(DataFlow::Node urlPart | urlPart = request.getAUrlPart() |
|
||||
fullConfig.hasFlow(_, urlPart)
|
||||
)
|
||||
forall(DataFlow::Node urlPart | urlPart = request.getAUrlPart() |
|
||||
FullServerSideRequestForgeryFlow::flow(_, urlPart)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FullServerSideRequestForgeryFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "Server-side request forgery" vulnerabilities.
|
||||
*
|
||||
* This configuration has results, even when the attacker does not have full control over the URL.
|
||||
* See `FullServerSideRequestForgeryConfiguration`, and the `fullyControlledRequest` predicate.
|
||||
*/
|
||||
class PartialServerSideRequestForgeryConfiguration extends TaintTracking::Configuration {
|
||||
deprecated class PartialServerSideRequestForgeryConfiguration extends TaintTracking::Configuration {
|
||||
PartialServerSideRequestForgeryConfiguration() { this = "PartialServerSideRequestForgery" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -69,3 +98,21 @@ class PartialServerSideRequestForgeryConfiguration extends TaintTracking::Config
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This configuration has results, even when the attacker does not have full control over the URL.
|
||||
* See `FullServerSideRequestForgeryConfiguration`, and the `fullyControlledRequest` predicate.
|
||||
*/
|
||||
private module PartialServerSideRequestForgeryConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Global taint-tracking for detecting "partial server-side request forgery" vulnerabilities.
|
||||
*/
|
||||
module PartialServerSideRequestForgeryFlow =
|
||||
TaintTracking::Global<PartialServerSideRequestForgeryConfig>;
|
||||
|
||||
@@ -9,7 +9,6 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
private import semmle.python.frameworks.SqlAlchemy
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import SqlInjectionCustomizations::SqlInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `SqlInjectionFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "SQL injection" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "SqlInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -27,3 +29,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module SqlInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "SQL injection" vulnerabilities. */
|
||||
module SqlInjectionFlow = TaintTracking::Global<SqlInjectionConfig>;
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import StackTraceExposureCustomizations::StackTraceExposure
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `StackTraceExposureFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "stack trace exposure" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "StackTraceExposure" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -36,3 +38,23 @@ class Configuration extends TaintTracking::Configuration {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private module StackTraceExposureConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
// A stack trace is accessible as the `__traceback__` attribute of a caught exception.
|
||||
// see https://docs.python.org/3/reference/datamodel.html#traceback-objects
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(DataFlow::AttrRead attr | attr.getAttributeName() = "__traceback__" |
|
||||
nodeFrom = attr.getObject() and
|
||||
nodeTo = attr
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "stack trace exposure" vulnerabilities. */
|
||||
module StackTraceExposureFlow = TaintTracking::Global<StackTraceExposureConfig>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "command injection" vulnerabilities.
|
||||
* Provides a taint-tracking configuration for detecting "tar slip" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `TarSlip::Configuration` is needed, otherwise
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import TarSlipCustomizations::TarSlip
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "command injection" vulnerabilities.
|
||||
* DEPRECATED: Use `TarSlipFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "tar slip" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "TarSlip" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -23,3 +25,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
private module TarSlipConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "tar slip" vulnerabilities. */
|
||||
module TarSlipFlow = TaintTracking::Global<TarSlipConfig>;
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import UnsafeDeserializationCustomizations::UnsafeDeserialization
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `UnsafeDeserializationFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "code execution from deserialization" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnsafeDeserialization" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -27,3 +29,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module UnsafeDeserializationConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "code execution from deserialization" vulnerabilities. */
|
||||
module UnsafeDeserializationFlow = TaintTracking::Global<UnsafeDeserializationConfig>;
|
||||
|
||||
@@ -14,9 +14,11 @@ private import CommandInjectionCustomizations::CommandInjection as CommandInject
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `UnsafeShellCommandConstructionFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting shell command constructed from library input vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnsafeShellCommandConstruction" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -33,3 +35,23 @@ class Configuration extends TaintTracking::Configuration {
|
||||
result instanceof DataFlow::FeatureHasSourceCallContext
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "shell command constructed from library input" vulnerabilities.
|
||||
*/
|
||||
module UnsafeShellCommandConstructionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof CommandInjection::Sanitizer // using all sanitizers from `py/command-injection`
|
||||
}
|
||||
|
||||
// override to require the path doesn't have unmatched return steps
|
||||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "shell command constructed from library input" vulnerabilities. */
|
||||
module UnsafeShellCommandConstructionFlow =
|
||||
TaintTracking::Global<UnsafeShellCommandConstructionConfig>;
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import UrlRedirectCustomizations::UrlRedirect
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `UrlRedirectFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "URL redirection" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UrlRedirect" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -27,3 +29,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module UrlRedirectConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "URL redirection" vulnerabilities. */
|
||||
module UrlRedirectFlow = TaintTracking::Global<UrlRedirectConfig>;
|
||||
|
||||
@@ -24,10 +24,12 @@ module NormalHashFunction {
|
||||
import WeakSensitiveDataHashingCustomizations::NormalHashFunction
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting use of a broken or weak
|
||||
* cryptographic hashing algorithm on sensitive data.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "NormalHashFunction" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -44,6 +46,21 @@ module NormalHashFunction {
|
||||
sensitiveDataExtraStepForCalls(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
sensitiveDataExtraStepForCalls(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "use of a broken or weak cryptographic hashing algorithm on sensitive data" vulnerabilities. */
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,13 +74,15 @@ module ComputationallyExpensiveHashFunction {
|
||||
import WeakSensitiveDataHashingCustomizations::ComputationallyExpensiveHashFunction
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting use of a broken or weak
|
||||
* cryptographic hashing algorithm on passwords.
|
||||
*
|
||||
* Passwords has stricter requirements on the hashing algorithm used (must be
|
||||
* computationally expensive to prevent brute-force attacks).
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ComputationallyExpensiveHashFunction" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -80,4 +99,49 @@ module ComputationallyExpensiveHashFunction {
|
||||
sensitiveDataExtraStepForCalls(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passwords has stricter requirements on the hashing algorithm used (must be
|
||||
* computationally expensive to prevent brute-force attacks).
|
||||
*/
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
sensitiveDataExtraStepForCalls(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "use of a broken or weak cryptographic hashing algorithm on passwords" vulnerabilities. */
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global taint-tracking for detecting both variants of "use of a broken or weak
|
||||
* cryptographic hashing algorithm on sensitive data" vulnerabilities.
|
||||
*
|
||||
* See convenience predicates `normalHashFunctionFlowPath` and
|
||||
* `computationallyExpensiveHashFunctionFlowPath`.
|
||||
*/
|
||||
module WeakSensitiveDataHashingFlow =
|
||||
DataFlow::MergePathGraph<NormalHashFunction::Flow::PathNode,
|
||||
ComputationallyExpensiveHashFunction::Flow::PathNode, NormalHashFunction::Flow::PathGraph,
|
||||
ComputationallyExpensiveHashFunction::Flow::PathGraph>;
|
||||
|
||||
/** Holds if data can flow from `source` to `sink` with `NormalHashFunction::Flow`. */
|
||||
predicate normalHashFunctionFlowPath(
|
||||
WeakSensitiveDataHashingFlow::PathNode source, WeakSensitiveDataHashingFlow::PathNode sink
|
||||
) {
|
||||
NormalHashFunction::Flow::flowPath(source.asPathNode1(), sink.asPathNode1())
|
||||
}
|
||||
|
||||
/** Holds if data can flow from `source` to `sink` with `ComputationallyExpensiveHashFunction::Flow`. */
|
||||
predicate computationallyExpensiveHashFunctionFlowPath(
|
||||
WeakSensitiveDataHashingFlow::PathNode source, WeakSensitiveDataHashingFlow::PathNode sink
|
||||
) {
|
||||
ComputationallyExpensiveHashFunction::Flow::flowPath(source.asPathNode2(), sink.asPathNode2())
|
||||
}
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import XmlBombCustomizations::XmlBomb
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `XmlBombFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "XML bomb" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "XmlBomb" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -26,3 +28,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
private module XmlBombConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "XML bomb" vulnerabilities. */
|
||||
module XmlBombFlow = TaintTracking::Global<XmlBombConfig>;
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import XpathInjectionCustomizations::XpathInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `XpathInjectionFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "Xpath Injection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -27,3 +29,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module XpathInjectionConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "Xpath Injection" vulnerabilities. */
|
||||
module XpathInjectionFlow = TaintTracking::Global<XpathInjectionConfig>;
|
||||
|
||||
@@ -12,9 +12,11 @@ import semmle.python.dataflow.new.TaintTracking
|
||||
import XxeCustomizations::Xxe
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `XxeFlow` module instead.
|
||||
*
|
||||
* A taint-tracking configuration for detecting "XML External Entity (XXE)" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "Xxe" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -26,3 +28,14 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
private module XxeConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "XML External Entity (XXE)" vulnerabilities. */
|
||||
module XxeFlow = TaintTracking::Global<XxeConfig>;
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
|
||||
import python
|
||||
import semmle.python.functions.ModificationOfParameterWithDefault
|
||||
import DataFlow::PathGraph
|
||||
import ModificationOfParameterWithDefault::Flow::PathGraph
|
||||
|
||||
from
|
||||
ModificationOfParameterWithDefault::Configuration config, DataFlow::PathNode source,
|
||||
DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
ModificationOfParameterWithDefault::Flow::PathNode source,
|
||||
ModificationOfParameterWithDefault::Flow::PathNode sink
|
||||
where ModificationOfParameterWithDefault::Flow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This expression mutates a $@.", source.getNode(),
|
||||
"default value"
|
||||
|
||||
@@ -167,8 +167,12 @@ class ExternalApiDataNode extends DataFlow::Node {
|
||||
}
|
||||
}
|
||||
|
||||
/** A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s. */
|
||||
class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration {
|
||||
/**
|
||||
* DEPRECATED: Use `XmlBombFlow` module instead.
|
||||
*
|
||||
* A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s.
|
||||
*/
|
||||
deprecated class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration {
|
||||
UntrustedDataToExternalApiConfig() { this = "UntrustedDataToExternalAPIConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
@@ -176,14 +180,21 @@ class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration {
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
|
||||
}
|
||||
|
||||
private module UntrustedDataToExternalApiConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
|
||||
}
|
||||
|
||||
/** Global taint-tracking from `RemoteFlowSource`s to `ExternalApiDataNode`s. */
|
||||
module UntrustedDataToExternalApiFlow = TaintTracking::Global<UntrustedDataToExternalApiConfig>;
|
||||
|
||||
/** A node representing untrusted data being passed to an external API. */
|
||||
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
|
||||
UntrustedExternalApiDataNode() { any(UntrustedDataToExternalApiConfig c).hasFlow(_, this) }
|
||||
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flow(_, this) }
|
||||
|
||||
/** Gets a source of untrusted data which is passed to this external API data node. */
|
||||
DataFlow::Node getAnUntrustedSource() {
|
||||
any(UntrustedDataToExternalApiConfig c).hasFlow(result, this)
|
||||
}
|
||||
DataFlow::Node getAnUntrustedSource() { UntrustedDataToExternalApiFlow::flow(result, this) }
|
||||
}
|
||||
|
||||
/** An external API which is used with untrusted data. */
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
|
||||
import python
|
||||
import ExternalAPIs
|
||||
import DataFlow::PathGraph
|
||||
import UntrustedDataToExternalApiFlow::PathGraph
|
||||
|
||||
from
|
||||
UntrustedDataToExternalApiConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
UntrustedDataToExternalApiFlow::PathNode source, UntrustedDataToExternalApiFlow::PathNode sink,
|
||||
ExternalApiUsedWithUntrustedData externalApi
|
||||
where
|
||||
sink.getNode() = externalApi.getUntrustedDataNode() and
|
||||
config.hasFlowPath(source, sink)
|
||||
UntrustedDataToExternalApiFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Call to " + externalApi.toString() + " with untrusted data from $@.", source.getNode(),
|
||||
source.toString()
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.PathInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import PathInjectionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from PathInjectionFlow::PathNode source, PathInjectionFlow::PathNode sink
|
||||
where PathInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This path depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.TarSlipQuery
|
||||
import DataFlow::PathGraph
|
||||
import TarSlipFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from TarSlipFlow::PathNode source, TarSlipFlow::PathNode sink
|
||||
where TarSlipFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This file extraction depends on a $@.", source.getNode(),
|
||||
"potentially untrusted source"
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.CommandInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import CommandInjectionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from CommandInjectionFlow::PathNode source, CommandInjectionFlow::PathNode sink
|
||||
where CommandInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This command line depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.UnsafeShellCommandConstructionQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeShellCommandConstructionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
|
||||
from
|
||||
UnsafeShellCommandConstructionFlow::PathNode source,
|
||||
UnsafeShellCommandConstructionFlow::PathNode sink, Sink sinkNode
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
UnsafeShellCommandConstructionFlow::flowPath(source, sink) and
|
||||
sinkNode = sink.getNode()
|
||||
select sinkNode.getStringConstruction(), source, sink,
|
||||
"This " + sinkNode.describe() + " which depends on $@ is later used in a $@.", source.getNode(),
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.ReflectedXssQuery
|
||||
import DataFlow::PathGraph
|
||||
import ReflectedXssFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from ReflectedXssFlow::PathNode source, ReflectedXssFlow::PathNode sink
|
||||
where ReflectedXssFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to a $@.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.SqlInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import SqlInjectionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from SqlInjectionFlow::PathNode source, SqlInjectionFlow::PathNode sink
|
||||
where SqlInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This SQL query depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
// Determine precision above
|
||||
import python
|
||||
import semmle.python.security.dataflow.LdapInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import LdapInjectionFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, string parameterName
|
||||
from LdapInjectionFlow::PathNode source, LdapInjectionFlow::PathNode sink, string parameterName
|
||||
where
|
||||
any(DnConfiguration dnConfig).hasFlowPath(source, sink) and
|
||||
LdapInjectionDnFlow::flowPath(source.asPathNode1(), sink.asPathNode1()) and
|
||||
parameterName = "DN"
|
||||
or
|
||||
any(FilterConfiguration filterConfig).hasFlowPath(source, sink) and
|
||||
LdapInjectionFilterFlow::flowPath(source.asPathNode2(), sink.asPathNode2()) and
|
||||
parameterName = "filter"
|
||||
select sink.getNode(), source, sink,
|
||||
"LDAP query parameter (" + parameterName + ") depends on a $@.", source.getNode(),
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.CodeInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import CodeInjectionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from CodeInjectionFlow::PathNode source, CodeInjectionFlow::PathNode sink
|
||||
where CodeInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.LogInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import LogInjectionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from LogInjectionFlow::PathNode source, LogInjectionFlow::PathNode sink
|
||||
where LogInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This log entry depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.StackTraceExposureQuery
|
||||
import DataFlow::PathGraph
|
||||
import StackTraceExposureFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from StackTraceExposureFlow::PathNode source, StackTraceExposureFlow::PathNode sink
|
||||
where StackTraceExposureFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"$@ flows to this location and may be exposed to an external user.", source.getNode(),
|
||||
"Stack trace information"
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import DataFlow::PathGraph
|
||||
import PamAuthorizationFlow::PathGraph
|
||||
import semmle.python.ApiGraphs
|
||||
import semmle.python.security.dataflow.PamAuthorizationQuery
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from PamAuthorizationFlow::PathNode source, PamAuthorizationFlow::PathNode sink
|
||||
where PamAuthorizationFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"This PAM authentication depends on a $@, and 'pam_acct_mgmt' is not called afterwards.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -2,4 +2,33 @@
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<include src="CleartextStorage.qhelp" /></qhelp>
|
||||
|
||||
<overview>
|
||||
|
||||
<p>If sensitive data is written to a log entry it could be exposed to an attacker
|
||||
who gains access to the logs.</p>
|
||||
|
||||
<p>Potential attackers can obtain sensitive user data when the log output is displayed. Additionally that data may
|
||||
expose system information such as full path names, system information, and sometimes usernames and passwords.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Sensitive data should not be logged.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>In the example the entire process environment is logged using `print`. Regular users of the production deployed application
|
||||
should not have access to this much information about the environment configuration.
|
||||
</p>
|
||||
<sample src="examples/CleartextLogging.py" />
|
||||
|
||||
<p> In the second example the data that is logged is not sensitive.</p>
|
||||
<sample src="examples/CleartextLoggingGood.py" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/">Insertion of Sensitive Information into Log File</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
|
||||
import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
import DataFlow::PathGraph
|
||||
import CleartextLoggingFlow::PathGraph
|
||||
import semmle.python.security.dataflow.CleartextLoggingQuery
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, string classification
|
||||
from
|
||||
CleartextLoggingFlow::PathNode source, CleartextLoggingFlow::PathNode sink, string classification
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
CleartextLoggingFlow::flowPath(source, sink) and
|
||||
classification = source.getNode().(Source).getClassification()
|
||||
select sink.getNode(), source, sink, "This expression logs $@ as clear text.", source.getNode(),
|
||||
"sensitive data (" + classification + ")"
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
|
||||
import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
import DataFlow::PathGraph
|
||||
import CleartextStorageFlow::PathGraph
|
||||
import semmle.python.security.dataflow.CleartextStorageQuery
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, string classification
|
||||
from
|
||||
CleartextStorageFlow::PathNode source, CleartextStorageFlow::PathNode sink, string classification
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
CleartextStorageFlow::flowPath(source, sink) and
|
||||
classification = source.getNode().(Source).getClassification()
|
||||
select sink.getNode(), source, sink, "This expression stores $@ as clear text.", source.getNode(),
|
||||
"sensitive data (" + classification + ")"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# BAD: Logging cleartext sensitive data
|
||||
import os
|
||||
print(f"[INFO] Environment: {os.environ}")
|
||||
@@ -0,0 +1,3 @@
|
||||
not_sensitive_data = {'a': 1, 'b': 2}
|
||||
# GOOD: it is fine to log data that is not sensitive
|
||||
print(f"[INFO] Some object contains: {not_sensitive_data}")
|
||||
@@ -16,33 +16,29 @@ import python
|
||||
import semmle.python.security.dataflow.WeakSensitiveDataHashingQuery
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
import WeakSensitiveDataHashingFlow::PathGraph
|
||||
|
||||
from
|
||||
DataFlow::PathNode source, DataFlow::PathNode sink, string ending, string algorithmName,
|
||||
string classification
|
||||
WeakSensitiveDataHashingFlow::PathNode source, WeakSensitiveDataHashingFlow::PathNode sink,
|
||||
string ending, string algorithmName, string classification
|
||||
where
|
||||
exists(NormalHashFunction::Configuration config |
|
||||
config.hasFlowPath(source, sink) and
|
||||
algorithmName = sink.getNode().(NormalHashFunction::Sink).getAlgorithmName() and
|
||||
classification = source.getNode().(NormalHashFunction::Source).getClassification() and
|
||||
ending = "."
|
||||
)
|
||||
normalHashFunctionFlowPath(source, sink) and
|
||||
algorithmName = sink.getNode().(NormalHashFunction::Sink).getAlgorithmName() and
|
||||
classification = source.getNode().(NormalHashFunction::Source).getClassification() and
|
||||
ending = "."
|
||||
or
|
||||
exists(ComputationallyExpensiveHashFunction::Configuration config |
|
||||
config.hasFlowPath(source, sink) and
|
||||
algorithmName = sink.getNode().(ComputationallyExpensiveHashFunction::Sink).getAlgorithmName() and
|
||||
classification =
|
||||
source.getNode().(ComputationallyExpensiveHashFunction::Source).getClassification() and
|
||||
(
|
||||
sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
|
||||
ending = "."
|
||||
or
|
||||
not sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
|
||||
ending =
|
||||
" for " + classification +
|
||||
" hashing, since it is not a computationally expensive hash function."
|
||||
)
|
||||
computationallyExpensiveHashFunctionFlowPath(source, sink) and
|
||||
algorithmName = sink.getNode().(ComputationallyExpensiveHashFunction::Sink).getAlgorithmName() and
|
||||
classification =
|
||||
source.getNode().(ComputationallyExpensiveHashFunction::Source).getClassification() and
|
||||
(
|
||||
sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
|
||||
ending = "."
|
||||
or
|
||||
not sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
|
||||
ending =
|
||||
" for " + classification +
|
||||
" hashing, since it is not a computationally expensive hash function."
|
||||
)
|
||||
select sink.getNode(), source, sink,
|
||||
"$@ is used in a hashing algorithm (" + algorithmName + ") that is insecure" + ending,
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.UnsafeDeserializationQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeDeserializationFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from UnsafeDeserializationFlow::PathNode source, UnsafeDeserializationFlow::PathNode sink
|
||||
where UnsafeDeserializationFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Unsafe deserialization depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.UrlRedirectQuery
|
||||
import DataFlow::PathGraph
|
||||
import UrlRedirectFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from UrlRedirectFlow::PathNode source, UrlRedirectFlow::PathNode sink
|
||||
where UrlRedirectFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Untrusted URL redirection depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.XxeQuery
|
||||
import DataFlow::PathGraph
|
||||
import XxeFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from XxeFlow::PathNode source, XxeFlow::PathNode sink
|
||||
where XxeFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"XML parsing depends on a $@ without guarding against external entity expansion.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.XpathInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import XpathInjectionFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from XpathInjectionFlow::PathNode source, XpathInjectionFlow::PathNode sink
|
||||
where XpathInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "XPath expression depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.PolynomialReDoSQuery
|
||||
import DataFlow::PathGraph
|
||||
import PolynomialReDoSFlow::PathGraph
|
||||
|
||||
from
|
||||
Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode,
|
||||
PolynomialReDoSFlow::PathNode source, PolynomialReDoSFlow::PathNode sink, Sink sinkNode,
|
||||
PolynomialBackTrackingTerm regexp
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
PolynomialReDoSFlow::flowPath(source, sink) and
|
||||
sinkNode = sink.getNode() and
|
||||
regexp.getRootTerm() = sinkNode.getRegExp()
|
||||
// not (
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
import python
|
||||
private import semmle.python.Concepts
|
||||
import semmle.python.security.dataflow.RegexInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
import RegexInjectionFlow::PathGraph
|
||||
|
||||
from
|
||||
Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
RegexInjectionFlow::PathNode source, RegexInjectionFlow::PathNode sink,
|
||||
RegexExecution regexExecution
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
RegexInjectionFlow::flowPath(source, sink) and
|
||||
regexExecution = sink.getNode().(Sink).getRegexExecution()
|
||||
select sink.getNode(), source, sink,
|
||||
"This regular expression depends on a $@ and is executed by $@.", source.getNode(),
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.XmlBombQuery
|
||||
import DataFlow::PathGraph
|
||||
import XmlBombFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
from XmlBombFlow::PathNode source, XmlBombFlow::PathNode sink
|
||||
where XmlBombFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"XML parsing depends on a $@ without guarding against uncontrolled entity expansion.",
|
||||
source.getNode(), "user-provided value"
|
||||
|
||||
@@ -16,7 +16,6 @@ import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.filters.Tests
|
||||
import DataFlow::PathGraph
|
||||
|
||||
bindingset[char, fraction]
|
||||
predicate fewer_characters_than(StrConst str, string char, float fraction) {
|
||||
@@ -108,17 +107,19 @@ private string getACredentialRegex() {
|
||||
result = "(?i).*(cert)(?!.*(format|name)).*"
|
||||
}
|
||||
|
||||
class HardcodedCredentialsConfiguration extends TaintTracking::Configuration {
|
||||
HardcodedCredentialsConfiguration() { this = "Hardcoded credentials configuration" }
|
||||
private module HardcodedCredentialsConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof HardcodedValueSource }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof HardcodedValueSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof CredentialSink }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof CredentialSink }
|
||||
}
|
||||
|
||||
from HardcodedCredentialsConfiguration config, DataFlow::PathNode src, DataFlow::PathNode sink
|
||||
module HardcodedCredentialsFlow = TaintTracking::Global<HardcodedCredentialsConfig>;
|
||||
|
||||
import HardcodedCredentialsFlow::PathGraph
|
||||
|
||||
from HardcodedCredentialsFlow::PathNode src, HardcodedCredentialsFlow::PathNode sink
|
||||
where
|
||||
config.hasFlowPath(src, sink) and
|
||||
HardcodedCredentialsFlow::flowPath(src, sink) and
|
||||
not any(TestScope test).contains(src.getNode().asCfgNode().getNode())
|
||||
select src.getNode(), src, sink, "This hardcoded value is $@.", sink.getNode(),
|
||||
"used as credentials"
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.ServerSideRequestForgeryQuery
|
||||
import DataFlow::PathGraph
|
||||
import FullServerSideRequestForgeryFlow::PathGraph
|
||||
|
||||
from
|
||||
FullServerSideRequestForgeryConfiguration fullConfig, DataFlow::PathNode source,
|
||||
DataFlow::PathNode sink, Http::Client::Request request
|
||||
FullServerSideRequestForgeryFlow::PathNode source,
|
||||
FullServerSideRequestForgeryFlow::PathNode sink, Http::Client::Request request
|
||||
where
|
||||
request = sink.getNode().(Sink).getRequest() and
|
||||
fullConfig.hasFlowPath(source, sink) and
|
||||
FullServerSideRequestForgeryFlow::flowPath(source, sink) and
|
||||
fullyControlledRequest(request)
|
||||
select request, source, sink, "The full URL of this request depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.ServerSideRequestForgeryQuery
|
||||
import DataFlow::PathGraph
|
||||
import PartialServerSideRequestForgeryFlow::PathGraph
|
||||
|
||||
from
|
||||
PartialServerSideRequestForgeryConfiguration partialConfig, DataFlow::PathNode source,
|
||||
DataFlow::PathNode sink, Http::Client::Request request
|
||||
PartialServerSideRequestForgeryFlow::PathNode source,
|
||||
PartialServerSideRequestForgeryFlow::PathNode sink, Http::Client::Request request
|
||||
where
|
||||
request = sink.getNode().(Sink).getRequest() and
|
||||
partialConfig.hasFlowPath(source, sink) and
|
||||
PartialServerSideRequestForgeryFlow::flowPath(source, sink) and
|
||||
not fullyControlledRequest(request)
|
||||
select request, source, sink, "Part of the URL of this request depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -8,8 +8,8 @@ def full_ssrf():
|
||||
target = request.args["target"]
|
||||
|
||||
# BAD: user has full control of URL
|
||||
resp = request.get("https://" + target + ".example.com/data/")
|
||||
resp = requests.get("https://" + target + ".example.com/data/")
|
||||
|
||||
# GOOD: `subdomain` is controlled by the server.
|
||||
subdomain = "europe" if target == "EU" else "world"
|
||||
resp = request.get("https://" + subdomain + ".example.com/data/")
|
||||
resp = requests.get("https://" + subdomain + ".example.com/data/")
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
* malicious NoSQL code by the user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 8.8
|
||||
* @id py/nosql-injection
|
||||
* @tags security
|
||||
* experimental
|
||||
* external/cwe/cwe-943
|
||||
*/
|
||||
|
||||
import python
|
||||
import experimental.semmle.python.security.injection.NoSQLInjection
|
||||
import DataFlow::PathGraph
|
||||
import semmle.python.security.dataflow.NoSqlInjectionQuery
|
||||
import NoSqlInjectionFlow::PathGraph
|
||||
|
||||
from NoSqlInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "This NoSQL query contains an unsanitized $@.", source,
|
||||
from NoSqlInjectionFlow::PathNode source, NoSqlInjectionFlow::PathNode sink
|
||||
where NoSqlInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This NoSQL query contains an unsanitized $@.", source,
|
||||
"user-provided value"
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Extended the `py/command-line-injection` query with sinks from Python's `asyncio` module.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved _URL redirection from remote source_ (`py/url-redirection`) query to not alert when URL has been checked with `django.utils.http. url_has_allowed_host_and_scheme`.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* The query `py/nosql-injection` for finding NoSQL injection vulnerabilities is now available in the default security suite.
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
import python
|
||||
import experimental.semmle.python.security.ZipSlip
|
||||
import DataFlow::PathGraph
|
||||
import ZipSlipFlow::PathGraph
|
||||
|
||||
from ZipSlipConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from ZipSlipFlow::PathNode source, ZipSlipFlow::PathNode sink
|
||||
where ZipSlipFlow::flowPath(source, sink)
|
||||
select source.getNode(), source, sink,
|
||||
"This unsanitized archive entry, which may contain '..', is used in a $@.", sink.getNode(),
|
||||
"file system operation"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
import TarSlipImprovFlow::PathGraph
|
||||
import semmle.python.ApiGraphs
|
||||
import semmle.python.dataflow.new.internal.Attributes
|
||||
import semmle.python.dataflow.new.BarrierGuards
|
||||
@@ -54,12 +54,10 @@ class AllTarfileOpens extends API::CallNode {
|
||||
/**
|
||||
* A taint-tracking configuration for detecting more "TarSlip" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "TarSlip" }
|
||||
private module TarSlipImprovConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source = tarfileOpen().getACall() }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source = tarfileOpen().getACall() }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
(
|
||||
// A sink capturing method calls to `extractall` without `members` argument.
|
||||
// For a call to `file.extractall` without `members` argument, `file` is considered a sink.
|
||||
@@ -100,7 +98,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
not sink.getScope().getLocation().getFile().inStdlib()
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
nodeTo.(MethodCallNode).calls(nodeFrom, "getmembers") and
|
||||
nodeFrom instanceof AllTarfileOpens
|
||||
or
|
||||
@@ -113,7 +111,10 @@ class Configuration extends TaintTracking::Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
/** Global taint-tracking for detecting more "TarSlip" vulnerabilities. */
|
||||
module TarSlipImprovFlow = TaintTracking::Global<TarSlipImprovConfig>;
|
||||
|
||||
from TarSlipImprovFlow::PathNode source, TarSlipImprovFlow::PathNode sink
|
||||
where TarSlipImprovFlow::flowPath(source, sink)
|
||||
select sink, source, sink, "Extraction of tarfile from $@ to a potentially untrusted source $@.",
|
||||
source.getNode(), source.getNode().toString(), sink.getNode(), sink.getNode().toString()
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
import python
|
||||
import experimental.Security.UnsafeUnpackQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnsafeUnpackFlow::PathGraph
|
||||
|
||||
from UnsafeUnpackingConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from UnsafeUnpackFlow::PathNode source, UnsafeUnpackFlow::PathNode sink
|
||||
where UnsafeUnpackFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Unsafe extraction from a malicious tarball retrieved from a remote location."
|
||||
|
||||
@@ -16,16 +16,13 @@ import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import semmle.python.ApiGraphs
|
||||
import DataFlow::PathGraph
|
||||
|
||||
private API::Node paramikoClient() {
|
||||
result = API::moduleImport("paramiko").getMember("SSHClient").getReturn()
|
||||
}
|
||||
|
||||
class ParamikoCmdInjectionConfiguration extends TaintTracking::Configuration {
|
||||
ParamikoCmdInjectionConfiguration() { this = "ParamikoCMDInjectionConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
private module ParamikoConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
/**
|
||||
* exec_command of `paramiko.SSHClient` class execute command on ssh target server
|
||||
@@ -33,7 +30,7 @@ class ParamikoCmdInjectionConfiguration extends TaintTracking::Configuration {
|
||||
* and it run CMD on current system that running the ssh command
|
||||
* the Sink related to proxy command is the `connect` method of `paramiko.SSHClient` class
|
||||
*/
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = paramikoClient().getMember("exec_command").getACall().getParameter(0, "command").asSink()
|
||||
or
|
||||
sink = paramikoClient().getMember("connect").getACall().getParameter(11, "sock").asSink()
|
||||
@@ -42,7 +39,7 @@ class ParamikoCmdInjectionConfiguration extends TaintTracking::Configuration {
|
||||
/**
|
||||
* this additional taint step help taint tracking to find the vulnerable `connect` method of `paramiko.SSHClient` class
|
||||
*/
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(API::CallNode call |
|
||||
call = API::moduleImport("paramiko").getMember("ProxyCommand").getACall() and
|
||||
nodeFrom = call.getParameter(0, "command_line").asSink() and
|
||||
@@ -51,7 +48,12 @@ class ParamikoCmdInjectionConfiguration extends TaintTracking::Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
from ParamikoCmdInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
/** Global taint-tracking for detecting "paramiko command injection" vulnerabilities. */
|
||||
module ParamikoFlow = TaintTracking::Global<ParamikoConfig>;
|
||||
|
||||
import ParamikoFlow::PathGraph
|
||||
|
||||
from ParamikoFlow::PathNode source, ParamikoFlow::PathNode sink
|
||||
where ParamikoFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
// determine precision above
|
||||
import python
|
||||
import experimental.semmle.python.security.dataflow.ReflectedXSS
|
||||
import DataFlow::PathGraph
|
||||
import experimental.semmle.python.security.dataflow.EmailXss
|
||||
import EmailXssFlow::PathGraph
|
||||
|
||||
from ReflectedXssConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from EmailXssFlow::PathNode source, EmailXssFlow::PathNode sink
|
||||
where EmailXssFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
|
||||
source.getNode(), "a user-provided value"
|
||||
@@ -14,9 +14,9 @@
|
||||
// determine precision above
|
||||
import python
|
||||
import experimental.semmle.python.security.injection.HTTPHeaders
|
||||
import DataFlow::PathGraph
|
||||
import HeaderInjectionFlow::PathGraph
|
||||
|
||||
from HeaderInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from HeaderInjectionFlow::PathNode source, HeaderInjectionFlow::PathNode sink
|
||||
where HeaderInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This HTTP header is constructed from a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import DataFlow::PathGraph
|
||||
import CsvInjectionFlow::PathGraph
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import experimental.semmle.python.security.injection.CsvInjection
|
||||
|
||||
from CsvInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from CsvInjectionFlow::PathNode source, CsvInjectionFlow::PathNode sink
|
||||
where CsvInjectionFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Csv injection might include code from $@.", source.getNode(),
|
||||
"this user input"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import python
|
||||
import UnicodeBypassValidationQuery
|
||||
import DataFlow::PathGraph
|
||||
import UnicodeBypassValidationFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
from UnicodeBypassValidationFlow::PathNode source, UnicodeBypassValidationFlow::PathNode sink
|
||||
where UnicodeBypassValidationFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters.",
|
||||
sink.getNode(), "Unicode transformation (Unicode normalization)", source.getNode(),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
private import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.ApiGraphs
|
||||
import semmle.python.Concepts
|
||||
import semmle.python.dataflow.new.internal.DataFlowPublic
|
||||
@@ -27,16 +28,15 @@ class PostValidation extends DataFlow::FlowState {
|
||||
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
|
||||
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnicodeBypassValidation" }
|
||||
private module UnicodeBypassValidationConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = DataFlow::FlowState;
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
source instanceof RemoteFlowSource and state instanceof PreValidation
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(
|
||||
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
|
||||
DataFlow::FlowState stateTo
|
||||
predicate isAdditionalFlowStep(
|
||||
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
|
||||
) {
|
||||
(
|
||||
exists(Escaping escaping | nodeFrom = escaping.getAnInput() and nodeTo = escaping.getOutput())
|
||||
@@ -51,7 +51,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
}
|
||||
|
||||
/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) {
|
||||
exists(API::CallNode cn |
|
||||
cn = API::moduleImport("unicodedata").getMember("normalize").getACall() and
|
||||
sink = cn.getArg(1)
|
||||
@@ -71,3 +71,6 @@ class Configuration extends TaintTracking::Configuration {
|
||||
state instanceof PostValidation
|
||||
}
|
||||
}
|
||||
|
||||
/** Global taint-tracking for detecting "Unicode transformation mishandling" vulnerabilities. */
|
||||
module UnicodeBypassValidationFlow = TaintTracking::GlobalWithState<UnicodeBypassValidationConfig>;
|
||||
|
||||
@@ -17,21 +17,25 @@ import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import experimental.semmle.python.security.TimingAttack
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A configuration that tracks data flow from cryptographic operations
|
||||
* to equality test
|
||||
*/
|
||||
class PossibleTimingAttackAgainstHash extends TaintTracking::Configuration {
|
||||
PossibleTimingAttackAgainstHash() { this = "PossibleTimingAttackAgainstHash" }
|
||||
private module PossibleTimingAttackAgainstHashConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
}
|
||||
|
||||
from PossibleTimingAttackAgainstHash config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
module PossibleTimingAttackAgainstHashFlow =
|
||||
TaintTracking::Global<PossibleTimingAttackAgainstHashConfig>;
|
||||
|
||||
import PossibleTimingAttackAgainstHashFlow::PathGraph
|
||||
|
||||
from
|
||||
PossibleTimingAttackAgainstHashFlow::PathNode source,
|
||||
PossibleTimingAttackAgainstHashFlow::PathNode sink
|
||||
where PossibleTimingAttackAgainstHashFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Possible Timing attack against $@ validation.",
|
||||
source.getNode().(ProduceCryptoCall).getResultType(), "message"
|
||||
|
||||
@@ -16,23 +16,24 @@ import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import experimental.semmle.python.security.TimingAttack
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A configuration that tracks data flow from cryptographic operations
|
||||
* to Equality test.
|
||||
*/
|
||||
class TimingAttackAgainsthash extends TaintTracking::Configuration {
|
||||
TimingAttackAgainsthash() { this = "TimingAttackAgainsthash" }
|
||||
private module TimingAttackAgainstHashConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
}
|
||||
|
||||
from TimingAttackAgainsthash config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
module TimingAttackAgainstHashFlow = TaintTracking::Global<TimingAttackAgainstHashConfig>;
|
||||
|
||||
import TimingAttackAgainstHashFlow::PathGraph
|
||||
|
||||
from TimingAttackAgainstHashFlow::PathNode source, TimingAttackAgainstHashFlow::PathNode sink
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
TimingAttackAgainstHashFlow::flowPath(source, sink) and
|
||||
sink.getNode().(NonConstantTimeComparisonSink).includesUserInput()
|
||||
select sink.getNode(), source, sink, "Timing attack against $@ validation.",
|
||||
source.getNode().(ProduceCryptoCall).getResultType(), "message"
|
||||
|
||||
@@ -15,20 +15,26 @@ import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import experimental.semmle.python.security.TimingAttack
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A configuration tracing flow from a client Secret obtained by an HTTP header to a unsafe Comparison.
|
||||
*/
|
||||
class ClientSuppliedSecretConfig extends TaintTracking::Configuration {
|
||||
ClientSuppliedSecretConfig() { this = "ClientSuppliedSecretConfig" }
|
||||
private module TimingAttackAgainstHeaderValueConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof ClientSuppliedSecret }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof ClientSuppliedSecret }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof CompareSink }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof CompareSink }
|
||||
}
|
||||
|
||||
from ClientSuppliedSecretConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink) and not sink.getNode().(CompareSink).flowtolen()
|
||||
module TimingAttackAgainstHeaderValueFlow =
|
||||
TaintTracking::Global<TimingAttackAgainstHeaderValueConfig>;
|
||||
|
||||
import TimingAttackAgainstHeaderValueFlow::PathGraph
|
||||
|
||||
from
|
||||
TimingAttackAgainstHeaderValueFlow::PathNode source,
|
||||
TimingAttackAgainstHeaderValueFlow::PathNode sink
|
||||
where
|
||||
TimingAttackAgainstHeaderValueFlow::flowPath(source, sink) and
|
||||
not sink.getNode().(CompareSink).flowtolen()
|
||||
select sink.getNode(), source, sink, "Timing attack against $@ validation.", source.getNode(),
|
||||
"client-supplied token"
|
||||
|
||||
@@ -15,20 +15,24 @@ import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import experimental.semmle.python.security.TimingAttack
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A configuration tracing flow from obtaining a client Secret to a unsafe Comparison.
|
||||
*/
|
||||
class ClientSuppliedSecretConfig extends TaintTracking::Configuration {
|
||||
ClientSuppliedSecretConfig() { this = "ClientSuppliedSecretConfig" }
|
||||
private module PossibleTimingAttackAgainstSensitiveInfoConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
}
|
||||
|
||||
from ClientSuppliedSecretConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
module PossibleTimingAttackAgainstSensitiveInfoFlow =
|
||||
TaintTracking::Global<PossibleTimingAttackAgainstSensitiveInfoConfig>;
|
||||
|
||||
import PossibleTimingAttackAgainstSensitiveInfoFlow::PathGraph
|
||||
|
||||
from
|
||||
PossibleTimingAttackAgainstSensitiveInfoFlow::PathNode source,
|
||||
PossibleTimingAttackAgainstSensitiveInfoFlow::PathNode sink
|
||||
where PossibleTimingAttackAgainstSensitiveInfoFlow::flowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Timing attack against $@ validation.", source.getNode(),
|
||||
"client-supplied token"
|
||||
|
||||
@@ -15,22 +15,25 @@ import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import experimental.semmle.python.security.TimingAttack
|
||||
import DataFlow::PathGraph
|
||||
import TimingAttackAgainstSensitiveInfoFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A configuration tracing flow from obtaining a client Secret to a unsafe Comparison.
|
||||
*/
|
||||
class ClientSuppliedSecretConfig extends TaintTracking::Configuration {
|
||||
ClientSuppliedSecretConfig() { this = "ClientSuppliedSecretConfig" }
|
||||
private module TimingAttackAgainstSensitiveInfoConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
}
|
||||
|
||||
from ClientSuppliedSecretConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
module TimingAttackAgainstSensitiveInfoFlow =
|
||||
TaintTracking::Global<TimingAttackAgainstSensitiveInfoConfig>;
|
||||
|
||||
from
|
||||
TimingAttackAgainstSensitiveInfoFlow::PathNode source,
|
||||
TimingAttackAgainstSensitiveInfoFlow::PathNode sink
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
TimingAttackAgainstSensitiveInfoFlow::flowPath(source, sink) and
|
||||
(
|
||||
source.getNode().(SecretSource).includesUserInput() or
|
||||
sink.getNode().(NonConstantTimeComparisonSink).includesUserInput()
|
||||
|
||||
@@ -25,7 +25,7 @@ newtype TFrameWork =
|
||||
Flask() or
|
||||
Django()
|
||||
|
||||
module WebAppConstantSecretKeyConfig implements DataFlow::StateConfigSig {
|
||||
private module WebAppConstantSecretKeyConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = TFrameWork;
|
||||
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
@@ -54,11 +54,11 @@ module WebAppConstantSecretKeyConfig implements DataFlow::StateConfigSig {
|
||||
}
|
||||
}
|
||||
|
||||
module WebAppConstantSecretKey = TaintTracking::GlobalWithState<WebAppConstantSecretKeyConfig>;
|
||||
module WebAppConstantSecretKeyFlow = TaintTracking::GlobalWithState<WebAppConstantSecretKeyConfig>;
|
||||
|
||||
import WebAppConstantSecretKey::PathGraph
|
||||
import WebAppConstantSecretKeyFlow::PathGraph
|
||||
|
||||
from WebAppConstantSecretKey::PathNode source, WebAppConstantSecretKey::PathNode sink
|
||||
where WebAppConstantSecretKey::flowPath(source, sink)
|
||||
from WebAppConstantSecretKeyFlow::PathNode source, WebAppConstantSecretKeyFlow::PathNode sink
|
||||
where WebAppConstantSecretKeyFlow::flowPath(source, sink)
|
||||
select sink, source, sink, "The SECRET_KEY config variable is assigned by $@.", source,
|
||||
" this constant String"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user