Merge branch 'main' into stdlib-optparse

This commit is contained in:
yoff
2024-09-24 20:24:00 +02:00
committed by GitHub
8853 changed files with 460851 additions and 108009 deletions

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
## 1.0.2
No user-facing changes.

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

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

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

View File

@@ -0,0 +1,3 @@
## 1.0.6
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 1.0.7
No user-facing changes.

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

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.0.1
lastReleaseVersion: 2.0.0

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

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +0,0 @@
/**
* Deprecated. Use `semmle.python.regexp.RegexTreeView` instead.
*/
deprecated import regexp.RegexTreeView as Dep
import Dep

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
import python
import semmle.python.dataflow.TaintTracking
abstract deprecated class SqlInjectionSink extends TaintSink { }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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