mirror of
https://github.com/github/codeql.git
synced 2026-04-29 10:45:15 +02:00
Merge branch 'main' into stdlib-optparse
This commit is contained in:
@@ -1,3 +1,44 @@
|
||||
## 2.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Deleted the deprecated `explorationLimit` predicate from `DataFlow::Configuration`, use `FlowExploration<explorationLimit>` instead.
|
||||
* Deleted the deprecated `semmle.python.RegexTreeView` module, use `semmle.python.regexp.RegexTreeView` instead.
|
||||
* Deleted the deprecated `RegexString` class from `regex.qll`.
|
||||
* Deleted the deprecated `Regex` class, use `RegExp` instead.
|
||||
* Deleted the deprecated `semmle/python/security/SQL.qll` file.
|
||||
* Deleted the deprecated `useSSL` predicates from the LDAP libraries, use `useSsl` instead.
|
||||
|
||||
## 1.0.7
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.0.6
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added support for `DictionaryElement[<key>]` and `DictionaryElementAny` when Customizing Library Models for `sourceModel` (see https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-python/)
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Additional modelling to detect direct writes to the `Set-Cookie` header has been added for several web frameworks.
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* A number of Python queries now support sinks defined using data extensions. The format of data extensions for Python has been documented.
|
||||
|
||||
## 1.0.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.0.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The common sanitizer guard `StringConstCompareBarrier` has been renamed to `ConstCompareBarrier` and expanded to cover comparisons with other constant values such as `None`. This may result in fewer false positive results for several queries.
|
||||
3
python/ql/lib/change-notes/released/1.0.2.md
Normal file
3
python/ql/lib/change-notes/released/1.0.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.0.2
|
||||
|
||||
No user-facing changes.
|
||||
5
python/ql/lib/change-notes/released/1.0.3.md
Normal file
5
python/ql/lib/change-notes/released/1.0.3.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 1.0.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* A number of Python queries now support sinks defined using data extensions. The format of data extensions for Python has been documented.
|
||||
5
python/ql/lib/change-notes/released/1.0.4.md
Normal file
5
python/ql/lib/change-notes/released/1.0.4.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 1.0.4
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Additional modelling to detect direct writes to the `Set-Cookie` header has been added for several web frameworks.
|
||||
5
python/ql/lib/change-notes/released/1.0.5.md
Normal file
5
python/ql/lib/change-notes/released/1.0.5.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 1.0.5
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added support for `DictionaryElement[<key>]` and `DictionaryElementAny` when Customizing Library Models for `sourceModel` (see https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-python/)
|
||||
3
python/ql/lib/change-notes/released/1.0.6.md
Normal file
3
python/ql/lib/change-notes/released/1.0.6.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.0.6
|
||||
|
||||
No user-facing changes.
|
||||
3
python/ql/lib/change-notes/released/1.0.7.md
Normal file
3
python/ql/lib/change-notes/released/1.0.7.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.0.7
|
||||
|
||||
No user-facing changes.
|
||||
10
python/ql/lib/change-notes/released/2.0.0.md
Normal file
10
python/ql/lib/change-notes/released/2.0.0.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## 2.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Deleted the deprecated `explorationLimit` predicate from `DataFlow::Configuration`, use `FlowExploration<explorationLimit>` instead.
|
||||
* Deleted the deprecated `semmle.python.RegexTreeView` module, use `semmle.python.regexp.RegexTreeView` instead.
|
||||
* Deleted the deprecated `RegexString` class from `regex.qll`.
|
||||
* Deleted the deprecated `Regex` class, use `RegExp` instead.
|
||||
* Deleted the deprecated `semmle/python/security/SQL.qll` file.
|
||||
* Deleted the deprecated `useSSL` predicates from the LDAP libraries, use `useSsl` instead.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.0.1
|
||||
lastReleaseVersion: 2.0.0
|
||||
|
||||
246
python/ql/lib/modeling/ModelEditor.qll
Normal file
246
python/ql/lib/modeling/ModelEditor.qll
Normal file
@@ -0,0 +1,246 @@
|
||||
/** Provides classes and predicates related to handling APIs for the VS Code extension. */
|
||||
|
||||
private import python
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
private import semmle.python.frameworks.data.internal.ApiGraphModelsExtensions
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch as DP
|
||||
private import Util as Util
|
||||
|
||||
/**
|
||||
* An string describing the kind of source code element being modeled.
|
||||
*
|
||||
* See `EndPoint`.
|
||||
*/
|
||||
class EndpointKind extends string {
|
||||
EndpointKind() {
|
||||
this in ["Function", "InstanceMethod", "ClassMethod", "StaticMethod", "InitMethod", "Class"]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An element of the source code to be modeled.
|
||||
*
|
||||
* See `EndPointKind` for the possible kinds of elements.
|
||||
*/
|
||||
abstract class Endpoint instanceof Util::RelevantScope {
|
||||
string namespace;
|
||||
string type;
|
||||
string name;
|
||||
|
||||
Endpoint() {
|
||||
exists(string scopePath, string path, int pathIndex |
|
||||
scopePath = Util::computeScopePath(this) and
|
||||
pathIndex = scopePath.indexOf(".", 0, 0)
|
||||
|
|
||||
namespace = scopePath.prefix(pathIndex) and
|
||||
path = scopePath.suffix(pathIndex + 1) and
|
||||
(
|
||||
exists(int nameIndex | nameIndex = max(path.indexOf(".")) |
|
||||
type = path.prefix(nameIndex) and
|
||||
name = path.suffix(nameIndex + 1)
|
||||
)
|
||||
or
|
||||
not exists(path.indexOf(".")) and
|
||||
type = "" and
|
||||
name = path
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the namespace for this endpoint. This will typically be the package in which it is found. */
|
||||
string getNamespace() { result = namespace }
|
||||
|
||||
/** Gets hte basename of the file where this endpoint is found. */
|
||||
string getFileName() { result = super.getLocation().getFile().getBaseName() }
|
||||
|
||||
/** Gets a string representation of this endpoint. */
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
/** Gets the location of this endpoint. */
|
||||
Location getLocation() { result = super.getLocation() }
|
||||
|
||||
/** Gets the name of the class in which this endpoint is found, or the empty string if it is not found inside a class. */
|
||||
string getClass() { result = type }
|
||||
|
||||
/**
|
||||
* Gets the name of the endpoint if it is not a class, or the empty string if it is a class
|
||||
*
|
||||
* If this endpoint is a class, the class name can be obtained via `getType`.
|
||||
*/
|
||||
string getFunctionName() { result = name }
|
||||
|
||||
/**
|
||||
* Gets a string representation of the parameters of this endpoint.
|
||||
*
|
||||
* The string follows a specific format:
|
||||
* - Normal parameters(where arguments can be passed as either positional or keyword) are listed in order, separated by commas.
|
||||
* - Keyword-only parameters are listed in order, separated by commas, each followed by a colon.
|
||||
* - In the future, positional-only parameters will be listed in order, separated by commas, each followed by a slash.
|
||||
*/
|
||||
abstract string getParameters();
|
||||
|
||||
/**
|
||||
* Gets a boolean that is true iff this endpoint is supported by existing modeling.
|
||||
*
|
||||
* The check only takes Models as Data extension models into account.
|
||||
*/
|
||||
abstract boolean getSupportedStatus();
|
||||
|
||||
/**
|
||||
* Gets a string that describes the type of support detected this endpoint.
|
||||
*
|
||||
* The string can be one of the following:
|
||||
* - "source" if this endpoint is a known source.
|
||||
* - "sink" if this endpoint is a known sink.
|
||||
* - "summary" if this endpoint has a flow summary.
|
||||
* - "neutral" if this endpoint is a known neutral.
|
||||
* - "" if this endpoint is not detected as supported.
|
||||
*/
|
||||
abstract string getSupportedType();
|
||||
|
||||
/** Gets the kind of this endpoint. See `EndPointKind`. */
|
||||
abstract EndpointKind getKind();
|
||||
}
|
||||
|
||||
private predicate sourceModelPath(string type, string path) { sourceModel(type, path, _, _) }
|
||||
|
||||
module FindSourceModel = Util::FindModel<sourceModelPath/2>;
|
||||
|
||||
private predicate sinkModelPath(string type, string path) { sinkModel(type, path, _, _) }
|
||||
|
||||
module FindSinkModel = Util::FindModel<sinkModelPath/2>;
|
||||
|
||||
private predicate summaryModelPath(string type, string path) {
|
||||
summaryModel(type, path, _, _, _, _)
|
||||
}
|
||||
|
||||
module FindSummaryModel = Util::FindModel<summaryModelPath/2>;
|
||||
|
||||
private predicate neutralModelPath(string type, string path) { neutralModel(type, path, _) }
|
||||
|
||||
module FindNeutralModel = Util::FindModel<neutralModelPath/2>;
|
||||
|
||||
/**
|
||||
* A callable function or method from source code.
|
||||
*/
|
||||
class FunctionEndpoint extends Endpoint instanceof Function {
|
||||
/**
|
||||
* Gets the parameter types of this endpoint.
|
||||
*/
|
||||
override string getParameters() {
|
||||
// For now, return the names of positional and keyword parameters. We don't always have type information, so we can't return type names.
|
||||
// We don't yet handle splat params or dict splat params.
|
||||
//
|
||||
// In Python, there are three types of parameters:
|
||||
// 1. Positional-only parameters: These are parameters that can only be passed by position and not by keyword.
|
||||
// 2. Positional-or-keyword parameters: These are parameters that can be passed by position or by keyword.
|
||||
// 3. Keyword-only parameters: These are parameters that can only be passed by keyword.
|
||||
//
|
||||
// The syntax for defining these parameters is as follows:
|
||||
// ```python
|
||||
// def f(a, /, b, *, c):
|
||||
// pass
|
||||
// ```
|
||||
// In this example, `a` is a positional-only parameter, `b` is a positional-or-keyword parameter, and `c` is a keyword-only parameter.
|
||||
//
|
||||
// We handle positional-only parameters by adding a "/" to the parameter name, reminiscient of the syntax above.
|
||||
// Note that we don't yet have information about positional-only parameters.
|
||||
// We handle keyword-only parameters by adding a ":" to the parameter name, to be consistent with the MaD syntax and the other languages.
|
||||
exists(int nrPosOnly, Function f |
|
||||
f = this and
|
||||
nrPosOnly = f.getPositionalParameterCount()
|
||||
|
|
||||
result =
|
||||
"(" +
|
||||
concat(string key, string value |
|
||||
// TODO: Once we have information about positional-only parameters:
|
||||
// Handle positional-only parameters by adding a "/"
|
||||
value = any(int i | i.toString() = key | f.getArgName(i))
|
||||
or
|
||||
exists(Name param | param = f.getAKeywordOnlyArg() |
|
||||
param.getId() = key and
|
||||
value = key + ":"
|
||||
)
|
||||
|
|
||||
value, "," order by key
|
||||
) + ")"
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this API has a supported summary. */
|
||||
pragma[nomagic]
|
||||
predicate hasSummary() { FindSummaryModel::hasModel(this) }
|
||||
|
||||
/** Holds if this API is a known source. */
|
||||
pragma[nomagic]
|
||||
predicate isSource() { FindSourceModel::hasModel(this) }
|
||||
|
||||
/** Holds if this API is a known sink. */
|
||||
pragma[nomagic]
|
||||
predicate isSink() { FindSinkModel::hasModel(this) }
|
||||
|
||||
/** Holds if this API is a known neutral. */
|
||||
pragma[nomagic]
|
||||
predicate isNeutral() { FindNeutralModel::hasModel(this) }
|
||||
|
||||
/**
|
||||
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
|
||||
* recognized source, sink or neutral or it has a flow summary.
|
||||
*/
|
||||
predicate isSupported() {
|
||||
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
|
||||
}
|
||||
|
||||
override boolean getSupportedStatus() {
|
||||
if this.isSupported() then result = true else result = false
|
||||
}
|
||||
|
||||
override string getSupportedType() {
|
||||
this.isSink() and result = "sink"
|
||||
or
|
||||
this.isSource() and result = "source"
|
||||
or
|
||||
this.hasSummary() and result = "summary"
|
||||
or
|
||||
this.isNeutral() and result = "neutral"
|
||||
or
|
||||
not this.isSupported() and result = ""
|
||||
}
|
||||
|
||||
override EndpointKind getKind() {
|
||||
if this.(Function).isMethod()
|
||||
then
|
||||
result = this.methodKind()
|
||||
or
|
||||
not exists(this.methodKind()) and result = "InstanceMethod"
|
||||
else result = "Function"
|
||||
}
|
||||
|
||||
private EndpointKind methodKind() {
|
||||
this.(Function).isMethod() and
|
||||
(
|
||||
DP::isClassmethod(this) and result = "ClassMethod"
|
||||
or
|
||||
DP::isStaticmethod(this) and result = "StaticMethod"
|
||||
or
|
||||
this.(Function).isInitMethod() and result = "InitMethod"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class from source code.
|
||||
*/
|
||||
class ClassEndpoint extends Endpoint instanceof Class {
|
||||
override string getClass() { result = type + "." + name }
|
||||
|
||||
override string getFunctionName() { result = "" }
|
||||
|
||||
override string getParameters() { result = "" }
|
||||
|
||||
override boolean getSupportedStatus() { result = false }
|
||||
|
||||
override string getSupportedType() { result = "" }
|
||||
|
||||
override EndpointKind getKind() { result = "Class" }
|
||||
}
|
||||
86
python/ql/lib/modeling/Util.qll
Normal file
86
python/ql/lib/modeling/Util.qll
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Contains utility methods and classes to assist with generating data extensions models.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.filters.Tests
|
||||
|
||||
/**
|
||||
* A file that probably contains tests.
|
||||
*/
|
||||
class TestFile extends File {
|
||||
TestFile() {
|
||||
this.getRelativePath().regexpMatch(".*(test|spec|examples).+") and
|
||||
not this.getAbsolutePath().matches("%/ql/test/%") // allows our test cases to work
|
||||
}
|
||||
}
|
||||
|
||||
/** A class to represent scopes that the user might want to model. */
|
||||
class RelevantScope extends Scope {
|
||||
RelevantScope() {
|
||||
this.isPublic() and
|
||||
not this instanceof TestScope and
|
||||
not this.getLocation().getFile() instanceof TestFile and
|
||||
exists(this.getLocation().getFile().getRelativePath())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dotted path of a scope.
|
||||
*/
|
||||
string computeScopePath(RelevantScope scope) {
|
||||
// base case
|
||||
if scope instanceof Module
|
||||
then
|
||||
scope.(Module).isPackageInit() and
|
||||
result = scope.(Module).getPackageName()
|
||||
or
|
||||
not scope.(Module).isPackageInit() and
|
||||
result = scope.(Module).getName()
|
||||
else
|
||||
//recursive cases
|
||||
if scope instanceof Class or scope instanceof Function
|
||||
then result = computeScopePath(scope.getEnclosingScope()) + "." + scope.getName()
|
||||
else result = "unknown: " + scope.toString()
|
||||
}
|
||||
|
||||
signature predicate modelSig(string type, string path);
|
||||
|
||||
/**
|
||||
* A utility module for finding models of endpoints.
|
||||
*
|
||||
* Chiefly the `hasModel` predicate is used to determine if a scope has a model.
|
||||
*/
|
||||
module FindModel<modelSig/2 model> {
|
||||
/**
|
||||
* Holds if the given scope has a model as identified by the provided predicate `model`.
|
||||
*/
|
||||
predicate hasModel(RelevantScope scope) {
|
||||
exists(string type, string path, string searchPath | model(type, path) |
|
||||
searchPath = possibleMemberPathPrefix(path, scope.getName()) and
|
||||
pathToScope(scope, type, searchPath)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the prefix of `path` that might be a path to `member`
|
||||
*/
|
||||
bindingset[path, member]
|
||||
string possibleMemberPathPrefix(string path, string member) {
|
||||
exists(int index | index = path.indexOf(["Member", "Method"] + "[" + member + "]") |
|
||||
result = path.prefix(index)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `(type,path)` identifies `scope`.
|
||||
*/
|
||||
bindingset[type, path]
|
||||
predicate pathToScope(RelevantScope scope, string type, string path) {
|
||||
computeScopePath(scope) =
|
||||
type.replaceAll("!", "") + "." +
|
||||
path.replaceAll("Member[", "").replaceAll("]", "").replaceAll("Instance.", "") +
|
||||
scope.getName()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/python-all
|
||||
version: 1.0.2-dev
|
||||
version: 2.0.1-dev
|
||||
groups: python
|
||||
dbscheme: semmlecode.python.dbscheme
|
||||
extractor: python
|
||||
|
||||
@@ -1134,6 +1134,54 @@ module Http {
|
||||
}
|
||||
}
|
||||
|
||||
/** A key-value pair in a literal for a bulk header update, considered as a single header update. */
|
||||
private class HeaderBulkWriteDictLiteral extends Http::Server::ResponseHeaderWrite::Range instanceof Http::Server::ResponseHeaderBulkWrite
|
||||
{
|
||||
KeyValuePair item;
|
||||
|
||||
HeaderBulkWriteDictLiteral() {
|
||||
exists(Dict dict | DataFlow::localFlow(DataFlow::exprNode(dict), super.getBulkArg()) |
|
||||
item = dict.getAnItem()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result.asExpr() = item.getKey() }
|
||||
|
||||
override DataFlow::Node getValueArg() { result.asExpr() = item.getValue() }
|
||||
|
||||
override predicate nameAllowsNewline() {
|
||||
Http::Server::ResponseHeaderBulkWrite.super.nameAllowsNewline()
|
||||
}
|
||||
|
||||
override predicate valueAllowsNewline() {
|
||||
Http::Server::ResponseHeaderBulkWrite.super.valueAllowsNewline()
|
||||
}
|
||||
}
|
||||
|
||||
/** A tuple in a list for a bulk header update, considered as a single header update. */
|
||||
private class HeaderBulkWriteListLiteral extends Http::Server::ResponseHeaderWrite::Range instanceof Http::Server::ResponseHeaderBulkWrite
|
||||
{
|
||||
Tuple item;
|
||||
|
||||
HeaderBulkWriteListLiteral() {
|
||||
exists(List list | DataFlow::localFlow(DataFlow::exprNode(list), super.getBulkArg()) |
|
||||
item = list.getAnElt()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result.asExpr() = item.getElt(0) }
|
||||
|
||||
override DataFlow::Node getValueArg() { result.asExpr() = item.getElt(1) }
|
||||
|
||||
override predicate nameAllowsNewline() {
|
||||
Http::Server::ResponseHeaderBulkWrite.super.nameAllowsNewline()
|
||||
}
|
||||
|
||||
override predicate valueAllowsNewline() {
|
||||
Http::Server::ResponseHeaderBulkWrite.super.valueAllowsNewline()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that sets a cookie in an HTTP response.
|
||||
*
|
||||
@@ -1155,6 +1203,77 @@ module Http {
|
||||
* Gets the argument, if any, specifying the cookie value.
|
||||
*/
|
||||
DataFlow::Node getValueArg() { result = super.getValueArg() }
|
||||
|
||||
/**
|
||||
* Holds if the `Secure` flag of the cookie is known to have a value of `b`.
|
||||
*/
|
||||
predicate hasSecureFlag(boolean b) { super.hasSecureFlag(b) }
|
||||
|
||||
/**
|
||||
* Holds if the `HttpOnly` flag of the cookie is known to have a value of `b`.
|
||||
*/
|
||||
predicate hasHttpOnlyFlag(boolean b) { super.hasHttpOnlyFlag(b) }
|
||||
|
||||
/**
|
||||
* Holds if the `SameSite` attribute of the cookie is known to have a value of `v`.
|
||||
*/
|
||||
predicate hasSameSiteAttribute(CookieWrite::SameSiteValue v) { super.hasSameSiteAttribute(v) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dataflow call node to a method that sets a cookie in an http response,
|
||||
* and has common keyword arguments `secure`, `httponly`, and `samesite` to set the attributes of the cookie.
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
||||
*/
|
||||
abstract class SetCookieCall extends CookieWrite::Range, DataFlow::CallCfgNode {
|
||||
override predicate hasSecureFlag(boolean b) {
|
||||
super.hasSecureFlag(b)
|
||||
or
|
||||
exists(DataFlow::Node arg, BooleanLiteral bool | arg = this.getArgByName("secure") |
|
||||
DataFlow::localFlow(DataFlow::exprNode(bool), arg) and
|
||||
b = bool.booleanValue()
|
||||
)
|
||||
or
|
||||
not exists(this.getArgByName("secure")) and
|
||||
not exists(this.getKwargs()) and
|
||||
b = false
|
||||
}
|
||||
|
||||
override predicate hasHttpOnlyFlag(boolean b) {
|
||||
super.hasHttpOnlyFlag(b)
|
||||
or
|
||||
exists(DataFlow::Node arg, BooleanLiteral bool | arg = this.getArgByName("httponly") |
|
||||
DataFlow::localFlow(DataFlow::exprNode(bool), arg) and
|
||||
b = bool.booleanValue()
|
||||
)
|
||||
or
|
||||
not exists(this.getArgByName("httponly")) and
|
||||
not exists(this.getKwargs()) and
|
||||
b = false
|
||||
}
|
||||
|
||||
override predicate hasSameSiteAttribute(CookieWrite::SameSiteValue v) {
|
||||
super.hasSameSiteAttribute(v)
|
||||
or
|
||||
exists(DataFlow::Node arg, StringLiteral str | arg = this.getArgByName("samesite") |
|
||||
DataFlow::localFlow(DataFlow::exprNode(str), arg) and
|
||||
(
|
||||
str.getText().toLowerCase() = "strict" and
|
||||
v instanceof CookieWrite::SameSiteStrict
|
||||
or
|
||||
str.getText().toLowerCase() = "lax" and
|
||||
v instanceof CookieWrite::SameSiteLax
|
||||
or
|
||||
str.getText().toLowerCase() = "none" and
|
||||
v instanceof CookieWrite::SameSiteNone
|
||||
)
|
||||
)
|
||||
or
|
||||
not exists(this.getArgByName("samesite")) and
|
||||
not exists(this.getKwargs()) and
|
||||
v instanceof CookieWrite::SameSiteLax // Lax is the default
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new cookie writes on HTTP responses. */
|
||||
@@ -1183,6 +1302,165 @@ module Http {
|
||||
* Gets the argument, if any, specifying the cookie value.
|
||||
*/
|
||||
abstract DataFlow::Node getValueArg();
|
||||
|
||||
/**
|
||||
* Holds if the `Secure` flag of the cookie is known to have a value of `b`.
|
||||
*/
|
||||
predicate hasSecureFlag(boolean b) {
|
||||
exists(StringLiteral sl |
|
||||
// `sl` is likely a substring of the header
|
||||
TaintTracking::localTaint(DataFlow::exprNode(sl), this.getHeaderArg()) and
|
||||
sl.getText().regexpMatch("(?i).*;\\s*secure(;.*|\\s*)") and
|
||||
b = true
|
||||
or
|
||||
// `sl` is the entire header
|
||||
DataFlow::localFlow(DataFlow::exprNode(sl), this.getHeaderArg()) and
|
||||
not sl.getText().regexpMatch("(?i).*;\\s*secure(;.*|\\s*)") and
|
||||
b = false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `HttpOnly` flag of the cookie is known to have a value of `b`.
|
||||
*/
|
||||
predicate hasHttpOnlyFlag(boolean b) {
|
||||
exists(StringLiteral sl |
|
||||
// `sl` is likely a substring of the header
|
||||
TaintTracking::localTaint(DataFlow::exprNode(sl), this.getHeaderArg()) and
|
||||
sl.getText().regexpMatch("(?i).*;\\s*httponly(;.*|\\s*)") and
|
||||
b = true
|
||||
or
|
||||
// `sl` is the entire header
|
||||
DataFlow::localFlow(DataFlow::exprNode(sl), this.getHeaderArg()) and
|
||||
not sl.getText().regexpMatch("(?i).*;\\s*httponly(;.*|\\s*)") and
|
||||
b = false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `SameSite` flag of the cookie is known to have a value of `v`.
|
||||
*/
|
||||
predicate hasSameSiteAttribute(SameSiteValue v) {
|
||||
exists(StringLiteral sl |
|
||||
// `sl` is likely a substring of the header
|
||||
TaintTracking::localTaint(DataFlow::exprNode(sl), this.getHeaderArg()) and
|
||||
(
|
||||
sl.getText().regexpMatch("(?i).*;\\s*samesite=strict(;.*|\\s*)") and
|
||||
v instanceof SameSiteStrict
|
||||
or
|
||||
sl.getText().regexpMatch("(?i).*;\\s*samesite=lax(;.*|\\s*)") and
|
||||
v instanceof SameSiteLax
|
||||
or
|
||||
sl.getText().regexpMatch("(?i).*;\\s*samesite=none(;.*|\\s*)") and
|
||||
v instanceof SameSiteNone
|
||||
)
|
||||
or
|
||||
// `sl` is the entire header
|
||||
DataFlow::localFlow(DataFlow::exprNode(sl), this.getHeaderArg()) and
|
||||
not sl.getText().regexpMatch("(?i).*;\\s*samesite=(strict|lax|none)(;.*|\\s*)") and
|
||||
v instanceof SameSiteLax // Lax is the default
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TSameSiteValue =
|
||||
TSameSiteStrict() or
|
||||
TSameSiteLax() or
|
||||
TSameSiteNone()
|
||||
|
||||
/** A possible value for the SameSite attribute of a cookie. */
|
||||
class SameSiteValue extends TSameSiteValue {
|
||||
/** Gets a string representation of this value. */
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/** A `Strict` value of the `SameSite` attribute. */
|
||||
class SameSiteStrict extends SameSiteValue, TSameSiteStrict {
|
||||
override string toString() { result = "Strict" }
|
||||
}
|
||||
|
||||
/** A `Lax` value of the `SameSite` attribute. */
|
||||
class SameSiteLax extends SameSiteValue, TSameSiteLax {
|
||||
override string toString() { result = "Lax" }
|
||||
}
|
||||
|
||||
/** A `None` value of the `SameSite` attribute. */
|
||||
class SameSiteNone extends SameSiteValue, TSameSiteNone {
|
||||
override string toString() { result = "None" }
|
||||
}
|
||||
}
|
||||
|
||||
/** A write to a `Set-Cookie` header that sets a cookie directly. */
|
||||
private class CookieHeaderWrite extends CookieWrite::Range instanceof Http::Server::ResponseHeaderWrite
|
||||
{
|
||||
CookieHeaderWrite() {
|
||||
exists(StringLiteral str |
|
||||
str.getText().toLowerCase() = "set-cookie" and
|
||||
DataFlow::exprNode(str)
|
||||
.(DataFlow::LocalSourceNode)
|
||||
.flowsTo(this.(Http::Server::ResponseHeaderWrite).getNameArg())
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { none() }
|
||||
|
||||
override DataFlow::Node getHeaderArg() {
|
||||
result = this.(Http::Server::ResponseHeaderWrite).getValueArg()
|
||||
}
|
||||
|
||||
override DataFlow::Node getValueArg() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that enables or disables CORS
|
||||
* in a global manner.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `CorsMiddleware::Range` instead.
|
||||
*/
|
||||
class CorsMiddleware extends DataFlow::Node instanceof CorsMiddleware::Range {
|
||||
/**
|
||||
* Gets the string corresponding to the middleware
|
||||
*/
|
||||
string getMiddlewareName() { result = super.getMiddlewareName() }
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS origins
|
||||
*/
|
||||
DataFlow::Node getOrigins() { result = super.getOrigins() }
|
||||
|
||||
/**
|
||||
* Gets the boolean value corresponding to if CORS credentials is enabled
|
||||
* (`true`) or disabled (`false`) by this node.
|
||||
*/
|
||||
DataFlow::Node getCredentialsAllowed() { result = super.getCredentialsAllowed() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new CORS middleware APIs. */
|
||||
module CorsMiddleware {
|
||||
/**
|
||||
* A data-flow node that enables or disables Cross-site request forgery protection
|
||||
* in a global manner.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `CorsMiddleware` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the name corresponding to the middleware
|
||||
*/
|
||||
abstract string getMiddlewareName();
|
||||
|
||||
/**
|
||||
* Gets the strings corresponding to the origins allowed by the cors policy
|
||||
*/
|
||||
abstract DataFlow::Node getOrigins();
|
||||
|
||||
/**
|
||||
* Gets the boolean value corresponding to if CORS credentials is enabled
|
||||
* (`true`) or disabled (`false`) by this node.
|
||||
*/
|
||||
abstract DataFlow::Node getCredentialsAllowed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ private import semmle.python.frameworks.Simplejson
|
||||
private import semmle.python.frameworks.SqlAlchemy
|
||||
private import semmle.python.frameworks.Starlette
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
private import semmle.python.frameworks.Streamlit
|
||||
private import semmle.python.frameworks.Toml
|
||||
private import semmle.python.frameworks.Torch
|
||||
private import semmle.python.frameworks.Tornado
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Deprecated. Use `semmle.python.regexp.RegexTreeView` instead.
|
||||
*/
|
||||
|
||||
deprecated import regexp.RegexTreeView as Dep
|
||||
import Dep
|
||||
@@ -85,9 +85,10 @@ class Scope extends Scope_ {
|
||||
this instanceof Module
|
||||
or
|
||||
exists(Module m | m = this.getEnclosingScope() and m.isPublic() |
|
||||
/* If the module has an __all__, is this in it */
|
||||
// The module is implicitly exported
|
||||
not exists(getAModuleExport(m))
|
||||
or
|
||||
// The module is explicitly exported
|
||||
getAModuleExport(m) = this.getName()
|
||||
)
|
||||
or
|
||||
|
||||
@@ -3,34 +3,45 @@
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
private predicate stringConstCompare(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
|
||||
private predicate constCompare(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
|
||||
exists(CompareNode cn | cn = g |
|
||||
exists(StringLiteral str_const, Cmpop op |
|
||||
exists(ImmutableLiteral const, Cmpop op |
|
||||
op = any(Eq eq) and branch = true
|
||||
or
|
||||
op = any(NotEq ne) and branch = false
|
||||
|
|
||||
cn.operands(str_const.getAFlowNode(), op, node)
|
||||
cn.operands(const.getAFlowNode(), op, node)
|
||||
or
|
||||
cn.operands(node, op, str_const.getAFlowNode())
|
||||
cn.operands(node, op, const.getAFlowNode())
|
||||
)
|
||||
or
|
||||
exists(IterableNode str_const_iterable, Cmpop op |
|
||||
exists(NameConstant const, Cmpop op |
|
||||
op = any(Is is_) and branch = true
|
||||
or
|
||||
op = any(IsNot isn) and branch = false
|
||||
|
|
||||
cn.operands(const.getAFlowNode(), op, node)
|
||||
or
|
||||
cn.operands(node, op, const.getAFlowNode())
|
||||
)
|
||||
or
|
||||
exists(IterableNode const_iterable, Cmpop op |
|
||||
op = any(In in_) and branch = true
|
||||
or
|
||||
op = any(NotIn ni) and branch = false
|
||||
|
|
||||
forall(ControlFlowNode elem | elem = str_const_iterable.getAnElement() |
|
||||
elem.getNode() instanceof StringLiteral
|
||||
forall(ControlFlowNode elem | elem = const_iterable.getAnElement() |
|
||||
elem.getNode() instanceof ImmutableLiteral
|
||||
) and
|
||||
cn.operands(node, op, str_const_iterable)
|
||||
cn.operands(node, op, const_iterable)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** A validation of unknown node by comparing with a constant string value. */
|
||||
class StringConstCompareBarrier extends DataFlow::Node {
|
||||
StringConstCompareBarrier() {
|
||||
this = DataFlow::BarrierGuard<stringConstCompare/3>::getABarrierNode()
|
||||
}
|
||||
/** A validation of unknown node by comparing with a constant value. */
|
||||
class ConstCompareBarrier extends DataFlow::Node {
|
||||
ConstCompareBarrier() { this = DataFlow::BarrierGuard<constCompare/3>::getABarrierNode() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Use ConstCompareBarrier instead. */
|
||||
deprecated class StringConstCompareBarrier = ConstCompareBarrier;
|
||||
|
||||
@@ -344,16 +344,6 @@ abstract class DataFlowCallable extends TDataFlowCallable {
|
||||
|
||||
/** Gets the location of this dataflow callable. */
|
||||
abstract Location getLocation();
|
||||
|
||||
/** Gets a best-effort total ordering. */
|
||||
int totalorder() {
|
||||
this =
|
||||
rank[result](DataFlowCallable c, string file, int startline, int startcolumn |
|
||||
c.getLocation().hasLocationInfo(file, startline, startcolumn, _, _)
|
||||
|
|
||||
c order by file, startline, startcolumn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A callable function. */
|
||||
@@ -829,10 +819,16 @@ Function findFunctionAccordingToMro(Class cls, string name) {
|
||||
result = cls.getAMethod() and
|
||||
result.getName() = name
|
||||
or
|
||||
not cls.getAMethod().getName() = name and
|
||||
not class_has_method(cls, name) and
|
||||
result = findFunctionAccordingToMro(getNextClassInMro(cls), name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Join-order helper for `findFunctionAccordingToMro` and `findFunctionAccordingToMroKnownStartingClass`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate class_has_method(Class cls, string name) { cls.getAMethod().getName() = name }
|
||||
|
||||
/**
|
||||
* Gets a class that, from an approximated MRO calculation, might be the next class
|
||||
* after `cls` in the MRO for `startingClass`.
|
||||
@@ -860,7 +856,7 @@ private Function findFunctionAccordingToMroKnownStartingClass(
|
||||
result.getName() = name and
|
||||
cls = getADirectSuperclass*(startingClass)
|
||||
or
|
||||
not cls.getAMethod().getName() = name and
|
||||
not class_has_method(cls, name) and
|
||||
result =
|
||||
findFunctionAccordingToMroKnownStartingClass(getNextClassInMroKnownStartingClass(cls,
|
||||
startingClass), startingClass, name)
|
||||
@@ -1429,16 +1425,6 @@ abstract class DataFlowCall extends TDataFlowCall {
|
||||
) {
|
||||
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/** Gets a best-effort total ordering. */
|
||||
int totalorder() {
|
||||
this =
|
||||
rank[result](DataFlowCall c, int startline, int startcolumn |
|
||||
c.hasLocationInfo(_, startline, startcolumn, _, _)
|
||||
|
|
||||
c order by startline, startcolumn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A call found in the program source (as opposed to a synthesised call). */
|
||||
|
||||
@@ -168,14 +168,6 @@ abstract deprecated class Configuration extends string {
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
|
||||
*
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
deprecated int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
@@ -290,15 +282,9 @@ deprecated private module Config implements FullStateConfigSig {
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
predicate sourceGrouping(Node source, string sourceGroup) {
|
||||
any(Configuration config).sourceGrouping(source, sourceGroup)
|
||||
}
|
||||
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) {
|
||||
any(Configuration config).sinkGrouping(sink, sinkGroup)
|
||||
}
|
||||
|
||||
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { none() }
|
||||
}
|
||||
|
||||
deprecated private import Impl<Config> as I
|
||||
|
||||
@@ -168,14 +168,6 @@ abstract deprecated class Configuration extends string {
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
|
||||
*
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
deprecated int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
@@ -290,15 +282,9 @@ deprecated private module Config implements FullStateConfigSig {
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
predicate sourceGrouping(Node source, string sourceGroup) {
|
||||
any(Configuration config).sourceGrouping(source, sourceGroup)
|
||||
}
|
||||
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) {
|
||||
any(Configuration config).sinkGrouping(sink, sinkGroup)
|
||||
}
|
||||
|
||||
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { none() }
|
||||
}
|
||||
|
||||
deprecated private import Impl<Config> as I
|
||||
|
||||
@@ -168,14 +168,6 @@ abstract deprecated class Configuration extends string {
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
|
||||
*
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
deprecated int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
@@ -290,15 +282,9 @@ deprecated private module Config implements FullStateConfigSig {
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
predicate sourceGrouping(Node source, string sourceGroup) {
|
||||
any(Configuration config).sourceGrouping(source, sourceGroup)
|
||||
}
|
||||
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) {
|
||||
any(Configuration config).sinkGrouping(sink, sinkGroup)
|
||||
}
|
||||
|
||||
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { none() }
|
||||
}
|
||||
|
||||
deprecated private import Impl<Config> as I
|
||||
|
||||
@@ -168,14 +168,6 @@ abstract deprecated class Configuration extends string {
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
|
||||
*
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
deprecated int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
@@ -290,15 +282,9 @@ deprecated private module Config implements FullStateConfigSig {
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
predicate sourceGrouping(Node source, string sourceGroup) {
|
||||
any(Configuration config).sourceGrouping(source, sourceGroup)
|
||||
}
|
||||
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) {
|
||||
any(Configuration config).sinkGrouping(sink, sinkGroup)
|
||||
}
|
||||
|
||||
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { none() }
|
||||
}
|
||||
|
||||
deprecated private import Impl<Config> as I
|
||||
|
||||
@@ -534,7 +534,7 @@ newtype TDataFlowType = TAnyFlow()
|
||||
|
||||
class DataFlowType extends TDataFlowType {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "DataFlowType" }
|
||||
string toString() { result = "" }
|
||||
}
|
||||
|
||||
/** A node that performs a type cast. */
|
||||
@@ -578,9 +578,6 @@ DataFlowType getNodeType(Node node) {
|
||||
exists(node)
|
||||
}
|
||||
|
||||
/** Gets a string representation of a type returned by `getErasedRepr`. */
|
||||
string ppReprType(DataFlowType t) { none() }
|
||||
|
||||
//--------
|
||||
// Extra flow
|
||||
//--------
|
||||
@@ -1025,8 +1022,6 @@ class NodeRegion instanceof Unit {
|
||||
string toString() { result = "NodeRegion" }
|
||||
|
||||
predicate contains(Node n) { none() }
|
||||
|
||||
int totalOrder() { result = 1 }
|
||||
}
|
||||
|
||||
//--------
|
||||
|
||||
@@ -219,6 +219,12 @@ class CallCfgNode extends CfgNode, LocalSourceNode {
|
||||
|
||||
/** Gets the data-flow node corresponding to the named argument of the call corresponding to this data-flow node */
|
||||
Node getArgByName(string name) { result.asCfgNode() = node.getArgByName(name) }
|
||||
|
||||
/** Gets the data-flow node corresponding to the first tuple (*) argument of the call corresponding to this data-flow node, if any. */
|
||||
Node getStarArg() { result.asCfgNode() = node.getStarArg() }
|
||||
|
||||
/** Gets the data-flow node corresponding to a dictionary (**) argument of the call corresponding to this data-flow node, if any. */
|
||||
Node getKwargs() { result.asCfgNode() = node.getKwargs() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -278,6 +278,12 @@ module ImportResolution {
|
||||
)
|
||||
}
|
||||
|
||||
/** Join-order helper for `getImmediateModuleReference`. */
|
||||
pragma[nomagic]
|
||||
private predicate module_reference_accesses(DataFlow::AttrRead ar, Module p, string attr_name) {
|
||||
ar.accesses(getModuleReference(p), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a dataflow node that is an immediate reference to the module `m`.
|
||||
*
|
||||
@@ -294,16 +300,13 @@ module ImportResolution {
|
||||
)
|
||||
or
|
||||
// Reading an attribute on a module may return a submodule (or subpackage).
|
||||
exists(DataFlow::AttrRead ar, Module p, string attr_name |
|
||||
ar.accesses(getModuleReference(p), attr_name) and
|
||||
result = ar
|
||||
|
|
||||
exists(Module p, string attr_name | module_reference_accesses(result, p, attr_name) |
|
||||
m = getModuleFromName(p.getPackageName() + "." + attr_name)
|
||||
)
|
||||
or
|
||||
// This is also true for attributes that come from reexports.
|
||||
exists(Module reexporter, string attr_name |
|
||||
result.(DataFlow::AttrRead).accesses(getModuleReference(reexporter), attr_name) and
|
||||
module_reference_accesses(result, reexporter, attr_name) and
|
||||
module_reexport(reexporter, attr_name, m)
|
||||
)
|
||||
or
|
||||
|
||||
@@ -24,6 +24,8 @@ private module CaptureInput implements Shared::InputSig<Location> {
|
||||
}
|
||||
|
||||
class BasicBlock extends PY::BasicBlock {
|
||||
int length() { result = count(int i | exists(this.getNode(i))) }
|
||||
|
||||
Callable getEnclosingCallable() { result = this.getScope() }
|
||||
|
||||
// Note `PY:BasicBlock` does not have a `getLocation`.
|
||||
@@ -34,6 +36,8 @@ private module CaptureInput implements Shared::InputSig<Location> {
|
||||
Location getLocation() { result = super.getNode(0).getLocation() }
|
||||
}
|
||||
|
||||
class ControlFlowNode = PY::ControlFlowNode;
|
||||
|
||||
BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() }
|
||||
|
||||
BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() }
|
||||
|
||||
@@ -664,14 +664,6 @@ module DataFlow {
|
||||
}
|
||||
}
|
||||
|
||||
deprecated private class DataFlowType extends TaintKind {
|
||||
// this only exists to avoid an empty recursion error in the type checker
|
||||
DataFlowType() {
|
||||
this = "Data flow" and
|
||||
1 = 2
|
||||
}
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate dict_construct(ControlFlowNode itemnode, ControlFlowNode dictnode) {
|
||||
dictnode.(DictNode).getAValue() = itemnode
|
||||
|
||||
@@ -653,8 +653,7 @@ module AiohttpWebModel {
|
||||
/**
|
||||
* A call to `set_cookie` on a HTTP Response.
|
||||
*/
|
||||
class AiohttpResponseSetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::CallCfgNode
|
||||
{
|
||||
class AiohttpResponseSetCookieCall extends Http::Server::SetCookieCall {
|
||||
AiohttpResponseSetCookieCall() {
|
||||
this = aiohttpResponseInstance().getMember("set_cookie").getACall()
|
||||
}
|
||||
@@ -706,6 +705,33 @@ module AiohttpWebModel {
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of the `headers` attribute on a HTTP response, such as
|
||||
* `response.headers[name] = value`.
|
||||
*/
|
||||
class AiohttpResponseHeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
AiohttpResponseHeaderSubscriptWrite() {
|
||||
exists(API::Node i |
|
||||
value = aiohttpResponseInstance().getMember("headers").getSubscriptAt(i).asSink() and
|
||||
index = i.asSink() and
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the RHS as it is readily available
|
||||
this = value
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2170,7 +2170,7 @@ module PrivateDjango {
|
||||
/**
|
||||
* A call to `set_cookie` on a HTTP Response.
|
||||
*/
|
||||
class DjangoResponseSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
class DjangoResponseSetCookieCall extends Http::Server::SetCookieCall,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
DjangoResponseSetCookieCall() {
|
||||
@@ -2239,6 +2239,71 @@ module PrivateDjango {
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of the `headers` attribute on a HTTP response, such as
|
||||
* `response.headers[name] = value`.
|
||||
*/
|
||||
class DjangoResponseHeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
DjangoResponseHeaderSubscriptWrite() {
|
||||
exists(SubscriptNode subscript, DataFlow::AttrRead headerLookup |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
headerLookup
|
||||
.accesses(DjangoImpl::DjangoHttp::Response::HttpResponse::instance(), "headers") and
|
||||
exists(DataFlow::Node subscriptObj |
|
||||
subscriptObj.asCfgNode() = subscript.getObject()
|
||||
|
|
||||
headerLookup.flowsTo(subscriptObj)
|
||||
) and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of an HTTP response, which is treated as a header write,
|
||||
* such as `response[headerName] = value`
|
||||
*/
|
||||
class DjangoResponseSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
DjangoResponseSubscriptWrite() {
|
||||
exists(SubscriptNode subscript |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
subscript.getObject() =
|
||||
DjangoImpl::DjangoHttp::Response::HttpResponse::instance().asCfgNode() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ private module FabricV1 {
|
||||
// -------------------------------------------------------------------------
|
||||
// fabric.api
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `fabric.api` module. */
|
||||
API::Node api() { result = fabric().getMember("api") }
|
||||
/** Gets a reference to the `fabric.api` module. Also known as `fabric.operations` */
|
||||
API::Node api() { result = fabric().getMember(["api", "operations"]) }
|
||||
|
||||
/** Provides models for the `fabric.api` module */
|
||||
module Api {
|
||||
|
||||
@@ -30,6 +30,51 @@ module FastApi {
|
||||
API::Node instance() { result = cls().getReturn() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `app.add_middleware` adding a generic middleware.
|
||||
*/
|
||||
private class AddMiddlewareCall extends DataFlow::CallCfgNode {
|
||||
AddMiddlewareCall() { this = App::instance().getMember("add_middleware").getACall() }
|
||||
|
||||
/**
|
||||
* Gets the string corresponding to the middleware
|
||||
*/
|
||||
string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `app.add_middleware` adding CORSMiddleware.
|
||||
*/
|
||||
class AddCorsMiddlewareCall extends Http::Server::CorsMiddleware::Range, AddMiddlewareCall {
|
||||
/**
|
||||
* Gets the string corresponding to the middleware
|
||||
*/
|
||||
override string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() }
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS origins
|
||||
*/
|
||||
override DataFlow::Node getOrigins() { result = this.getArgByName("allow_origins") }
|
||||
|
||||
/**
|
||||
* Gets the boolean value corresponding to if CORS credentials is enabled
|
||||
* (`true`) or disabled (`false`) by this node.
|
||||
*/
|
||||
override DataFlow::Node getCredentialsAllowed() {
|
||||
result = this.getArgByName("allow_credentials")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS methods
|
||||
*/
|
||||
DataFlow::Node getMethods() { result = this.getArgByName("allow_methods") }
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS headers
|
||||
*/
|
||||
DataFlow::Node getHeaders() { result = this.getArgByName("allow_headers") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `fastapi.APIRouter` class
|
||||
*
|
||||
@@ -348,7 +393,7 @@ module FastApi {
|
||||
/**
|
||||
* A call to `set_cookie` on a FastAPI Response.
|
||||
*/
|
||||
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
|
||||
private class SetCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
|
||||
SetCookieCall() { this.calls(instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
@@ -361,28 +406,59 @@ module FastApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `append` on a `headers` of a FastAPI Response, with the `Set-Cookie`
|
||||
* header-key.
|
||||
* A call to `append` on a `headers` of a FastAPI Response.
|
||||
*/
|
||||
private class HeadersAppendCookie extends Http::Server::CookieWrite::Range,
|
||||
private class HeadersAppend extends Http::Server::ResponseHeaderWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
HeadersAppendCookie() {
|
||||
exists(DataFlow::AttrRead headers, DataFlow::Node keyArg |
|
||||
HeadersAppend() {
|
||||
exists(DataFlow::AttrRead headers |
|
||||
headers.accesses(instance(), "headers") and
|
||||
this.calls(headers, "append") and
|
||||
keyArg in [this.getArg(0), this.getArgByName("key")] and
|
||||
keyArg.getALocalSource().asExpr().(StringLiteral).getText().toLowerCase() = "set-cookie"
|
||||
this.calls(headers, "append")
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() {
|
||||
override DataFlow::Node getNameArg() { result = [this.getArg(0), this.getArgByName("key")] }
|
||||
|
||||
override DataFlow::Node getValueArg() {
|
||||
result in [this.getArg(1), this.getArgByName("value")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { none() }
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override DataFlow::Node getValueArg() { none() }
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of the `headers` attribute on a HTTP response, such as
|
||||
* `response.headers[name] = value`.
|
||||
*/
|
||||
class HeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
HeaderSubscriptWrite() {
|
||||
exists(SubscriptNode subscript, DataFlow::AttrRead headerLookup |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
headerLookup.accesses(instance(), "headers") and
|
||||
exists(DataFlow::Node subscriptObj | subscriptObj.asCfgNode() = subscript.getObject() |
|
||||
headerLookup.flowsTo(subscriptObj)
|
||||
) and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,9 +583,7 @@ module Flask {
|
||||
*
|
||||
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.Response.set_cookie
|
||||
*/
|
||||
class FlaskResponseSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
class FlaskResponseSetCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
|
||||
FlaskResponseSetCookieCall() { this.calls(Flask::Response::instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
@@ -255,7 +255,7 @@ module Pyramid {
|
||||
}
|
||||
|
||||
/** A call to `response.set_cookie`. */
|
||||
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
|
||||
private class SetCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
|
||||
SetCookieCall() { this.calls(instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
@@ -25,6 +25,74 @@ private import semmle.python.frameworks.data.ModelsAsData
|
||||
* - https://www.starlette.io/
|
||||
*/
|
||||
module Starlette {
|
||||
/**
|
||||
* Provides models for the `starlette.app` class
|
||||
*/
|
||||
module App {
|
||||
/** Gets import of `starlette.app`. */
|
||||
API::Node cls() { result = API::moduleImport("starlette").getMember("app") }
|
||||
|
||||
/** Gets a reference to a Starlette application (an instance of `starlette.app`). */
|
||||
API::Node instance() { result = cls().getAnInstance() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to any of the execute methods on a `app.add_middleware`.
|
||||
*/
|
||||
class AddMiddlewareCall extends DataFlow::CallCfgNode {
|
||||
AddMiddlewareCall() {
|
||||
this = [App::instance().getMember("add_middleware").getACall(), Middleware::instance()]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string corresponding to the middleware
|
||||
*/
|
||||
string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to any of the execute methods on a `app.add_middleware` with CORSMiddleware.
|
||||
*/
|
||||
class AddCorsMiddlewareCall extends AddMiddlewareCall, Http::Server::CorsMiddleware::Range {
|
||||
/**
|
||||
* Gets the string corresponding to the middleware
|
||||
*/
|
||||
override string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() }
|
||||
|
||||
override DataFlow::Node getOrigins() { result = this.getArgByName("allow_origins") }
|
||||
|
||||
override DataFlow::Node getCredentialsAllowed() {
|
||||
result = this.getArgByName("allow_credentials")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS methods
|
||||
*/
|
||||
DataFlow::Node getMethods() { result = this.getArgByName("allow_methods") }
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS headers
|
||||
*/
|
||||
DataFlow::Node getHeaders() { result = this.getArgByName("allow_headers") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `starlette.middleware.Middleware` class
|
||||
*
|
||||
* See https://www.starlette.io/.
|
||||
*/
|
||||
module Middleware {
|
||||
/** Gets a reference to the `starlette.middleware.Middleware` class. */
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("starlette").getMember("middleware").getMember("Middleware")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("starlette.middleware.Middleware~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `starlette.middleware.Middleware`. */
|
||||
DataFlow::Node instance() { result = classRef().getACall() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `starlette.websockets.WebSocket` class
|
||||
*
|
||||
|
||||
95
python/ql/lib/semmle/python/frameworks/Streamlit.qll
Normal file
95
python/ql/lib/semmle/python/frameworks/Streamlit.qll
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `streamlit` PyPI package.
|
||||
* See https://pypi.org/project/streamlit/.
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.ApiGraphs
|
||||
import semmle.python.Concepts
|
||||
private import semmle.python.frameworks.SqlAlchemy
|
||||
|
||||
/**
|
||||
* Provides models for the `streamlit` PyPI package.
|
||||
* See https://pypi.org/project/streamlit/.
|
||||
*/
|
||||
module Streamlit {
|
||||
/**
|
||||
* The calls to the interactive streamlit widgets, which take untrusted input.
|
||||
*/
|
||||
private class StreamlitInput extends RemoteFlowSource::Range {
|
||||
StreamlitInput() {
|
||||
this =
|
||||
API::moduleImport("streamlit")
|
||||
.getMember(["text_input", "text_area", "chat_input"])
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Streamlit user input" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The Streamlit SQLConnection class, which is used to create a connection to a SQL Database.
|
||||
* Streamlit wraps around SQL Alchemy for most database functionality, and adds some on top of it, such as the `query` method.
|
||||
* Streamlit can also connect to Snowflake and Snowpark databases, but the modeling is not the same, so we need to limit the scope to SQL databases.
|
||||
* https://docs.streamlit.io/develop/api-reference/connections/st.connections.sqlconnection#:~:text=to%20data.-,st.connections.SQLConnection,-Streamlit%20Version
|
||||
* We can connect to SQL databases for example with `import streamlit as st; conn = st.connection('pets_db', type='sql')`
|
||||
*/
|
||||
private class StreamlitSqlConnection extends API::CallNode {
|
||||
StreamlitSqlConnection() {
|
||||
exists(StringLiteral str, API::CallNode n |
|
||||
str.getText() = "sql" and
|
||||
n = API::moduleImport("streamlit").getMember("connection").getACall() and
|
||||
DataFlow::exprNode(str)
|
||||
.(DataFlow::LocalSourceNode)
|
||||
.flowsTo([n.getArg(1), n.getArgByName("type")]) and
|
||||
this = n
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `query` call that can execute raw queries on a connection to a SQL database.
|
||||
* https://docs.streamlit.io/develop/api-reference/connections/st.connection
|
||||
*/
|
||||
private class QueryMethodCall extends DataFlow::CallCfgNode, SqlExecution::Range {
|
||||
QueryMethodCall() {
|
||||
exists(StreamlitSqlConnection s | this = s.getReturn().getMember("query").getACall())
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("sql")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* The Streamlit SQLConnection.connect() call, which returns a a new sqlalchemy.engine.Connection object.
|
||||
* Streamlit creates a connection to a SQL database basing off SQL Alchemy, so we can reuse the models that we already have.
|
||||
*/
|
||||
private class StreamlitSqlAlchemyConnection extends SqlAlchemy::Connection::InstanceSource {
|
||||
StreamlitSqlAlchemyConnection() {
|
||||
exists(StreamlitSqlConnection s | this = s.getReturn().getMember("connect").getACall())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The underlying SQLAlchemy Engine, accessed via `st.connection().engine`.
|
||||
* Streamlit creates an engine to a SQL database basing off SQL Alchemy, so we can reuse the models that we already have.
|
||||
*/
|
||||
private class StreamlitSqlAlchemyEngine extends SqlAlchemy::Engine::InstanceSource {
|
||||
StreamlitSqlAlchemyEngine() {
|
||||
exists(StreamlitSqlConnection s | this = s.getReturn().getMember("engine").asSource())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The SQLAlchemy Session, accessed via `st.connection().session`.
|
||||
* Streamlit can create a session to a SQL database basing off SQL Alchemy, so we can reuse the models that we already have.
|
||||
* For example, the modeling for `session` includes an `execute` method, which is used to execute raw SQL queries.
|
||||
* https://docs.streamlit.io/develop/api-reference/connections/st.connections.sqlconnection#:~:text=SQLConnection.engine-,SQLConnection.session,-Streamlit%20Version
|
||||
*/
|
||||
private class StreamlitSqlSession extends SqlAlchemy::Session::InstanceSource {
|
||||
StreamlitSqlSession() {
|
||||
exists(StreamlitSqlConnection s | this = s.getReturn().getMember("session").asSource())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,50 @@ module Tornado {
|
||||
|
||||
override string getAsyncMethodName() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of an `HTTPHeaders` object.
|
||||
*/
|
||||
private class TornadoHeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
TornadoHeaderSubscriptWrite() {
|
||||
exists(SubscriptNode subscript |
|
||||
subscript.getObject() = instance().asCfgNode() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex() and
|
||||
this.asCfgNode() = subscript
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `HTTPHeaders.add`.
|
||||
*/
|
||||
private class TornadoHeadersAppendCall extends Http::Server::ResponseHeaderWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TornadoHeadersAppendCall() { this.calls(instance(), "add") }
|
||||
|
||||
override DataFlow::Node getNameArg() { result = [this.getArg(0), this.getArgByName("name")] }
|
||||
|
||||
override DataFlow::Node getValueArg() {
|
||||
result in [this.getArg(1), this.getArgByName("value")]
|
||||
}
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -209,6 +253,25 @@ module Tornado {
|
||||
this.(DataFlow::AttrRead).getAttributeName() = "request"
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `RequestHandler.set_header` or `RequestHandler.add_header` */
|
||||
private class TornadoSetHeaderCall extends Http::Server::ResponseHeaderWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TornadoSetHeaderCall() { this.calls(instance(), ["set_header", "add_header"]) }
|
||||
|
||||
override DataFlow::Node getNameArg() {
|
||||
result = [this.getArg(0), this.getArgByName("name")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getValueArg() {
|
||||
result in [this.getArg(1), this.getArgByName("value")]
|
||||
}
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -529,7 +592,7 @@ module Tornado {
|
||||
*
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.set_cookie
|
||||
*/
|
||||
class TornadoRequestHandlerSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
class TornadoRequestHandlerSetCookieCall extends Http::Server::SetCookieCall,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TornadoRequestHandlerSetCookieCall() {
|
||||
|
||||
@@ -235,9 +235,7 @@ private module Twisted {
|
||||
*
|
||||
* See https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http.Request.html#addCookie
|
||||
*/
|
||||
class TwistedRequestAddCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
class TwistedRequestAddCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
|
||||
TwistedRequestAddCookieCall() { this.calls(Twisted::Request::instance(), "addCookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
@@ -45,3 +45,25 @@ extensible predicate typeModel(string type1, string type2, string path);
|
||||
* Holds if `path` can be substituted for a token `TypeVar[name]`.
|
||||
*/
|
||||
extensible predicate typeVariableModel(string name, string path);
|
||||
|
||||
/**
|
||||
* Holds if the given extension tuple `madId` should pretty-print as `model`.
|
||||
*
|
||||
* This predicate should only be used in tests.
|
||||
*/
|
||||
predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) {
|
||||
exists(string type, string path, string kind |
|
||||
sourceModel(type, path, kind, madId) and
|
||||
model = "Source: " + type + "; " + path + "; " + kind
|
||||
)
|
||||
or
|
||||
exists(string type, string path, string kind |
|
||||
sinkModel(type, path, kind, madId) and
|
||||
model = "Sink: " + type + "; " + path + "; " + kind
|
||||
)
|
||||
or
|
||||
exists(string type, string path, string input, string output, string kind |
|
||||
summaryModel(type, path, input, output, kind, madId) and
|
||||
model = "Summary: " + type + "; " + path + "; " + input + "; " + output + "; " + kind
|
||||
)
|
||||
}
|
||||
|
||||
@@ -134,9 +134,25 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
|
||||
token.getAnArgument() = "any-named" and
|
||||
result = node.getKeywordParameter(_)
|
||||
)
|
||||
or
|
||||
// content based steps
|
||||
//
|
||||
// note: if we want to migrate to use `FlowSummaryImpl::Input::encodeContent` like
|
||||
// they do in Ruby, be aware that we currently don't make
|
||||
// `DataFlow::DictionaryElementContent` just from seeing a subscript read, so we would
|
||||
// need to add that. (also need to handle things like `DictionaryElementAny` which
|
||||
// doesn't have any value for .getAnArgument())
|
||||
(
|
||||
token.getName() = "DictionaryElement" and
|
||||
result = node.getSubscript(token.getAnArgument())
|
||||
or
|
||||
token.getName() = "DictionaryElementAny" and
|
||||
result = node.getASubscript() and
|
||||
not exists(token.getAnArgument())
|
||||
// TODO: ListElement/SetElement/TupleElement
|
||||
)
|
||||
// Some features don't have MaD tokens yet, they would need to be added to API-graphs first.
|
||||
// - decorators ("DecoratedClass", "DecoratedMember", "DecoratedParameter")
|
||||
// - Array/Map elements ("ArrayElement", "Element", "MapKey", "MapValue")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,7 +258,11 @@ InvokeNode getAnInvocationOf(API::Node node) { result = node.getACall() }
|
||||
*/
|
||||
bindingset[name]
|
||||
predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
name = ["Member", "Instance", "Awaited", "Call", "Method", "Subclass"]
|
||||
name =
|
||||
[
|
||||
"Member", "Instance", "Awaited", "Call", "Method", "Subclass", "DictionaryElement",
|
||||
"DictionaryElementAny"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,7 +270,7 @@ predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
* in an identifying access path.
|
||||
*/
|
||||
predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
name = ["Instance", "Awaited", "Call", "Subclass"]
|
||||
name = ["Instance", "Awaited", "Call", "Subclass", "DictionaryElementAny"]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,7 +279,7 @@ predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
*/
|
||||
bindingset[name, argument]
|
||||
predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string argument) {
|
||||
name = ["Member", "Method"] and
|
||||
name = ["Member", "Method", "DictionaryElement"] and
|
||||
exists(argument)
|
||||
or
|
||||
name = ["Argument", "Parameter"] and
|
||||
|
||||
@@ -22,9 +22,10 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
/**
|
||||
* Gets the last decorator call for the function `func`, if `func` has decorators.
|
||||
*/
|
||||
private Expr lastDecoratorCall(Function func) {
|
||||
result = func.getDefinition().(FunctionExpr).getADecoratorCall() and
|
||||
not exists(Call other_decorator | other_decorator.getArg(0) = result)
|
||||
pragma[nomagic]
|
||||
private DataFlow::TypeTrackingNode lastDecoratorCall(Function func) {
|
||||
result.asExpr() = func.getDefinition().(FunctionExpr).getADecoratorCall() and
|
||||
not exists(Call other_decorator | other_decorator.getArg(0) = result.asExpr())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +57,7 @@ private DataFlow::TypeTrackingNode poorMansFunctionTracker(DataFlow::TypeTracker
|
||||
//
|
||||
// Note that this means that we blindly ignore what the decorator actually does to
|
||||
// the function, which seems like an OK tradeoff.
|
||||
result.asExpr() = lastDecoratorCall(func)
|
||||
result = pragma[only_bind_out](lastDecoratorCall(func))
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = poorMansFunctionTracker(t2, func).track(t2, t))
|
||||
|
||||
@@ -14,8 +14,3 @@ RegExpTerm getTermForExecution(Concepts::RegexExecution exec) {
|
||||
result.isRootTerm()
|
||||
)
|
||||
}
|
||||
|
||||
/** A StringLiteral used as a regular expression */
|
||||
deprecated class RegexString extends Regex {
|
||||
RegexString() { this = RegExpTracking::regExpSource(_).asExpr() }
|
||||
}
|
||||
|
||||
@@ -100,11 +100,6 @@ private module FindRegexMode {
|
||||
private string mode_from_node(DataFlow::Node node) { node = re_flag_tracker(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `RegExp` instead.
|
||||
*/
|
||||
deprecated class Regex = RegExp;
|
||||
|
||||
/** A StringLiteral used as a regular expression */
|
||||
class RegExp extends Expr instanceof StringLiteral {
|
||||
DataFlow::Node use;
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import python
|
||||
import semmle.python.dataflow.TaintTracking
|
||||
|
||||
abstract deprecated class SqlInjectionSink extends TaintSink { }
|
||||
@@ -41,7 +41,9 @@ module CleartextLogging {
|
||||
*/
|
||||
class SensitiveDataSourceAsSource extends Source, SensitiveDataSource {
|
||||
SensitiveDataSourceAsSource() {
|
||||
not SensitiveDataSource.super.getClassification() = SensitiveDataClassification::id()
|
||||
not SensitiveDataSource.super.getClassification() in [
|
||||
SensitiveDataClassification::id(), SensitiveDataClassification::certificate()
|
||||
]
|
||||
}
|
||||
|
||||
override SensitiveDataClassification getClassification() {
|
||||
|
||||
@@ -40,7 +40,9 @@ module CleartextStorage {
|
||||
*/
|
||||
class SensitiveDataSourceAsSource extends Source, SensitiveDataSource {
|
||||
SensitiveDataSourceAsSource() {
|
||||
not SensitiveDataSource.super.getClassification() = SensitiveDataClassification::id()
|
||||
not SensitiveDataSource.super.getClassification() in [
|
||||
SensitiveDataClassification::id(), SensitiveDataClassification::certificate()
|
||||
]
|
||||
}
|
||||
|
||||
override SensitiveDataClassification getClassification() {
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
@@ -43,8 +44,15 @@ module CodeInjection {
|
||||
CodeExecutionAsSink() { this = any(CodeExecution e).getCode() }
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("code-injection").asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizer extends Sanitizer, StringConstCompareBarrier { }
|
||||
class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
@@ -78,8 +79,15 @@ module CommandInjection {
|
||||
}
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("command-injection").asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "cookie injection"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "cookie injection"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module CookieInjection {
|
||||
/**
|
||||
* A data flow source for "cookie injection" vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for "cookie injection" vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for "cookie injection" vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
|
||||
|
||||
/**
|
||||
* A write to a cookie, considered as a sink.
|
||||
*/
|
||||
class CookieWriteSink extends Sink {
|
||||
CookieWriteSink() {
|
||||
exists(Http::Server::CookieWrite cw |
|
||||
this = [cw.getNameArg(), cw.getValueArg(), cw.getHeaderArg()]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "cookie injection" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `CookieInjectionFlow` is needed, otherwise
|
||||
* `CookieInjectionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import CookieInjectionCustomizations::CookieInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "cookie injection" vulnerabilities.
|
||||
*/
|
||||
module CookieInjectionConfig 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 "cookie injection" vulnerabilities. */
|
||||
module CookieInjectionFlow = TaintTracking::Global<CookieInjectionConfig>;
|
||||
@@ -51,56 +51,6 @@ module HttpHeaderInjection {
|
||||
}
|
||||
}
|
||||
|
||||
/** A key-value pair in a literal for a bulk header update, considered as a single header update. */
|
||||
// TODO: We could instead consider bulk writes as sinks with an implicit read step of DictionaryKey/DictionaryValue content as needed.
|
||||
private class HeaderBulkWriteDictLiteral extends Http::Server::ResponseHeaderWrite::Range instanceof Http::Server::ResponseHeaderBulkWrite
|
||||
{
|
||||
KeyValuePair item;
|
||||
|
||||
HeaderBulkWriteDictLiteral() {
|
||||
exists(Dict dict | DataFlow::localFlow(DataFlow::exprNode(dict), super.getBulkArg()) |
|
||||
item = dict.getAnItem()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result.asExpr() = item.getKey() }
|
||||
|
||||
override DataFlow::Node getValueArg() { result.asExpr() = item.getValue() }
|
||||
|
||||
override predicate nameAllowsNewline() {
|
||||
Http::Server::ResponseHeaderBulkWrite.super.nameAllowsNewline()
|
||||
}
|
||||
|
||||
override predicate valueAllowsNewline() {
|
||||
Http::Server::ResponseHeaderBulkWrite.super.valueAllowsNewline()
|
||||
}
|
||||
}
|
||||
|
||||
/** A tuple in a list for a bulk header update, considered as a single header update. */
|
||||
// TODO: We could instead consider bulk writes as sinks with implicit read steps as needed.
|
||||
private class HeaderBulkWriteListLiteral extends Http::Server::ResponseHeaderWrite::Range instanceof Http::Server::ResponseHeaderBulkWrite
|
||||
{
|
||||
Tuple item;
|
||||
|
||||
HeaderBulkWriteListLiteral() {
|
||||
exists(List list | DataFlow::localFlow(DataFlow::exprNode(list), super.getBulkArg()) |
|
||||
item = list.getAnElt()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result.asExpr() = item.getElt(0) }
|
||||
|
||||
override DataFlow::Node getValueArg() { result.asExpr() = item.getElt(1) }
|
||||
|
||||
override predicate nameAllowsNewline() {
|
||||
Http::Server::ResponseHeaderBulkWrite.super.nameAllowsNewline()
|
||||
}
|
||||
|
||||
override predicate valueAllowsNewline() {
|
||||
Http::Server::ResponseHeaderBulkWrite.super.valueAllowsNewline()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to replace line breaks, considered as a sanitizer.
|
||||
*/
|
||||
|
||||
@@ -61,15 +61,20 @@ module LdapInjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsDnSanitizerGuard extends DnSanitizer, StringConstCompareBarrier { }
|
||||
class ConstCompareAsDnSanitizerGuard extends DnSanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsDnSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsDnSanitizerGuard;
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsFilterSanitizerGuard extends FilterSanitizer, StringConstCompareBarrier {
|
||||
}
|
||||
class ConstCompareAsFilterSanitizerGuard extends FilterSanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsFilterSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsFilterSanitizerGuard = ConstCompareAsFilterSanitizerGuard;
|
||||
|
||||
/**
|
||||
* A call to replace line breaks functions as a sanitizer.
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
@@ -71,10 +72,17 @@ module LogInjection {
|
||||
}
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("log-injection").asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
|
||||
/**
|
||||
* A call to replace line breaks, considered as a sanitizer.
|
||||
|
||||
@@ -87,7 +87,10 @@ module PathInjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,10 @@ module PolynomialReDoS {
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,10 @@ module ReflectedXss {
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
}
|
||||
|
||||
@@ -72,9 +72,12 @@ module ServerSideRequestForgery {
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
|
||||
/**
|
||||
* A string construction (concat, format, f-string) where the left side is not
|
||||
|
||||
@@ -51,9 +51,12 @@ module SqlInjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
@@ -48,8 +49,15 @@ module UnsafeDeserialization {
|
||||
}
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("unsafe-deserialization").asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier { }
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
@@ -89,6 +90,10 @@ module UrlRedirect {
|
||||
}
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The right side of a string-concat, considered as a sanitizer.
|
||||
*/
|
||||
@@ -135,12 +140,15 @@ module UrlRedirect {
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
* A comparison with a constant, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier {
|
||||
class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier {
|
||||
override predicate sanitizes(FlowState state) {
|
||||
// sanitize all flow states
|
||||
any()
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user