fix conflict

This commit is contained in:
am0o0
2024-06-18 17:18:59 +02:00
2014 changed files with 83146 additions and 67109 deletions

View File

@@ -1,6 +1,5 @@
load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files")
load("@semmle_code//:dist.bzl", "dist", "pack_zip")
load("//:defs.bzl", "codeql_platform")
load("//misc/bazel:pkg.bzl", "codeql_pack", "codeql_pkg_files")
package(default_visibility = ["//visibility:public"])
@@ -37,25 +36,22 @@ pkg_files(
strip_prefix = None,
)
dist(
name = "extractor-generic",
codeql_pkg_files(
name = "extractor-arch",
exes = [
"//python/extractor/tsg-python",
],
prefix = "tools/{CODEQL_PLATFORM}",
)
codeql_pack(
name = "python",
srcs = [
":codeql-extractor-yml",
":dbscheme-group",
":extractor-arch",
"//python/downgrades",
"//python/extractor",
"//python/tools",
],
prefix = "python",
visibility = ["//visibility:public"],
)
pack_zip(
name = "extractor-arch",
srcs = [
"//python/extractor/tsg-python",
],
package_file_name = "extractor-" + codeql_platform + ".zip",
prefix = "python/tools/" + codeql_platform,
visibility = ["//visibility:public"],
)

View File

@@ -1,4 +1,4 @@
load("@semmle_code//:dist.bzl", "pack_zip")
load("//misc/bazel:pkg.bzl", "codeql_pkg_files", "strip_prefix")
py_binary(
name = "make-zips-py",
@@ -31,7 +31,7 @@ genrule(
tools = [":make-zips-py"],
)
pack_zip(
codeql_pkg_files(
name = "extractor",
srcs = [
"LICENSE-PSF.md", # because we distribute imp.py
@@ -44,5 +44,6 @@ pack_zip(
":python3src",
] + glob(["data/**"]),
prefix = "tools",
visibility = ["//visibility:public"],
strip_prefix = strip_prefix.from_pkg(),
visibility = ["//python:__pkg__"],
)

View File

@@ -1,5 +1,5 @@
load("@py_deps//:defs.bzl", "aliases", "all_crate_deps")
load("@semmle_code//:common.bzl", "codeql_rust_binary")
load("//misc/bazel:rust.bzl", "codeql_rust_binary")
codeql_rust_binary(
name = "tsg-python",

File diff suppressed because it is too large Load Diff

View File

@@ -4,17 +4,8 @@
name = "tsg-python"
version = "0.1.0"
authors = ["Taus Brock-Nannestad <tausbn@github.com>"]
edition = "2018"
edition = "2021"
# When changing/updating these, the `Cargo.Bazel.lock` file has to be regenerated.
# Run `CARGO_BAZEL_REPIN=true CARGO_BAZEL_REPIN_ONLY=py_deps ./build --bazel sync --only=py_deps`
# in the `semmle-code` repository to do so.
# For more information, check out the documentation at
# https://bazelbuild.github.io/rules_rust/crate_universe.html#repinning--updating-dependencies
# In the future, the hope is to move this handling of the dependencies entirely into the `codeql` repository,
# but that depends on `rules_rust` being fully compatible with bzlmod, which they aren't yet
# (c.f. https://github.com/bazelbuild/rules_rust/issues/2452).
# Warning: The process takes >5min on my M1 mac, so do wait for a while.
[dependencies]
anyhow = "1.0"
regex = "1"

View File

@@ -2,6 +2,6 @@
# extractor. It is set to the lowest version of Rust we want to support.
[toolchain]
channel = "1.68"
channel = "1.74"
profile = "minimal"
components = [ "rustfmt" ]

View File

@@ -1,3 +1,28 @@
## 1.0.1
No user-facing changes.
## 1.0.0
### Breaking Changes
* CodeQL package management is now generally available, and all GitHub-produced CodeQL packages have had their version numbers increased to 1.0.0.
### New Features
* A Python MaD (Models as Data) row may now contain a dotted path in the `type` column. Like in Ruby, a path to a class will refer to instances of that class. This means that the summary `["foo", "Member[MyClass].Instance.Member[instance_method]", "Argument[0]", "ReturnValue", "value"]` can now be written `["foo.MS_Class", "Member[instance_method]", "Argument[0]", "ReturnValue", "value"]`. To refer to an actual class, one may add a `!` at the end of the path.
### Minor Analysis Improvements
* The `request` parameter of Flask `SessionInterface.open_session` method is now modeled as a remote flow source.
* Additional heuristics for a new sensitive data classification for private information (e.g. credit card numbers) have been added to the shared `SensitiveDataHeuristics.qll` library. This may result in additional results for queries that use sensitive data such as `py/clear-text-storage-sensitive-data` and `py/clear-text-logging-sensitive-data`.
## 0.12.1
### Major Analysis Improvements
* Added modeling of the `pyramid` framework, leading to new remote flow sources and sinks.
## 0.12.0
### Breaking Changes

View File

@@ -0,0 +1,5 @@
## 0.12.1
### Major Analysis Improvements
* Added modeling of the `pyramid` framework, leading to new remote flow sources and sinks.

View File

@@ -0,0 +1,14 @@
## 1.0.0
### Breaking Changes
* CodeQL package management is now generally available, and all GitHub-produced CodeQL packages have had their version numbers increased to 1.0.0.
### New Features
* A Python MaD (Models as Data) row may now contain a dotted path in the `type` column. Like in Ruby, a path to a class will refer to instances of that class. This means that the summary `["foo", "Member[MyClass].Instance.Member[instance_method]", "Argument[0]", "ReturnValue", "value"]` can now be written `["foo.MS_Class", "Member[instance_method]", "Argument[0]", "ReturnValue", "value"]`. To refer to an actual class, one may add a `!` at the end of the path.
### Minor Analysis Improvements
* The `request` parameter of Flask `SessionInterface.open_session` method is now modeled as a remote flow source.
* Additional heuristics for a new sensitive data classification for private information (e.g. credit card numbers) have been added to the shared `SensitiveDataHeuristics.qll` library. This may result in additional results for queries that use sensitive data such as `py/clear-text-storage-sensitive-data` and `py/clear-text-logging-sensitive-data`.

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/python-all
version: 0.12.1-dev
version: 1.0.2-dev
groups: python
dbscheme: semmlecode.python.dbscheme
extractor: python

View File

@@ -226,6 +226,11 @@ module API {
*/
Node getASubclass() { result = this.getASuccessor(Label::subclass()) }
/**
* Gets a node representing an instance of the class (or a transitive subclass of the class) represented by this node.
*/
Node getAnInstance() { result = this.getASubclass*().getReturn() }
/**
* Gets a node representing the result from awaiting this node.
*/

View File

@@ -200,10 +200,11 @@ module Decoding {
}
private class DecodingAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, string model) {
exists(Decoding decoding |
nodeFrom = decoding.getAnInput() and
nodeTo = decoding.getOutput()
nodeTo = decoding.getOutput() and
model = "Decoding-" + decoding.getFormat()
)
}
}
@@ -1025,6 +1026,114 @@ module Http {
}
}
/**
* A data-flow node that sets a header in an HTTP response.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `ResponseHeaderWrite::Range` instead.
*/
class ResponseHeaderWrite extends DataFlow::Node instanceof ResponseHeaderWrite::Range {
/**
* Gets the argument containing the header name.
*/
DataFlow::Node getNameArg() { result = super.getNameArg() }
/**
* Gets the argument containing the header value.
*/
DataFlow::Node getValueArg() { result = super.getValueArg() }
/**
* Holds if newlines are accepted in the header name argument.
*/
predicate nameAllowsNewline() { super.nameAllowsNewline() }
/**
* Holds if newlines are accepted in the header value argument.
*/
predicate valueAllowsNewline() { super.valueAllowsNewline() }
}
/** Provides a class for modeling header writes on HTTP responses. */
module ResponseHeaderWrite {
/**
*A data-flow node that sets a header in an HTTP response.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `ResponseHeaderWrite` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the argument containing the header name.
*/
abstract DataFlow::Node getNameArg();
/**
* Gets the argument containing the header value.
*/
abstract DataFlow::Node getValueArg();
/**
* Holds if newlines are accepted in the header name argument.
*/
abstract predicate nameAllowsNewline();
/**
* Holds if newlines are accepted in the header value argument.
*/
abstract predicate valueAllowsNewline();
}
}
/**
* A data-flow node that sets multiple headers in an HTTP response using a dict or a list of tuples.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `ResponseHeaderBulkWrite::Range` instead.
*/
class ResponseHeaderBulkWrite extends DataFlow::Node instanceof ResponseHeaderBulkWrite::Range {
/**
* Gets the argument containing the headers dictionary.
*/
DataFlow::Node getBulkArg() { result = super.getBulkArg() }
/**
* Holds if newlines are accepted in the header name argument.
*/
predicate nameAllowsNewline() { super.nameAllowsNewline() }
/**
* Holds if newlines are accepted in the header value argument.
*/
predicate valueAllowsNewline() { super.valueAllowsNewline() }
}
/** Provides a class for modeling bulk header writes on HTTP responses. */
module ResponseHeaderBulkWrite {
/**
* A data-flow node that sets multiple headers in an HTTP response using a dict.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `ResponseHeaderBulkWrite` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the argument containing the headers dictionary.
*/
abstract DataFlow::Node getBulkArg();
/**
* Holds if newlines are accepted in the header name argument.
*/
abstract predicate nameAllowsNewline();
/**
* Holds if newlines are accepted in the header value argument.
*/
abstract predicate valueAllowsNewline();
}
}
/**
* A data-flow node that sets a cookie in an HTTP response.
*

View File

@@ -29,6 +29,7 @@ private import semmle.python.frameworks.FastApi
private import semmle.python.frameworks.Flask
private import semmle.python.frameworks.FlaskAdmin
private import semmle.python.frameworks.FlaskSqlAlchemy
private import semmle.python.frameworks.Gradio
private import semmle.python.frameworks.Httpx
private import semmle.python.frameworks.Idna
private import semmle.python.frameworks.Invoke
@@ -45,6 +46,7 @@ private import semmle.python.frameworks.Multidict
private import semmle.python.frameworks.Mysql
private import semmle.python.frameworks.MySQLdb
private import semmle.python.frameworks.Numpy
private import semmle.python.frameworks.Opml
private import semmle.python.frameworks.Oracledb
private import semmle.python.frameworks.Pandas
private import semmle.python.frameworks.Paramiko
@@ -59,6 +61,7 @@ private import semmle.python.frameworks.PyMongo
private import semmle.python.frameworks.Pymssql
private import semmle.python.frameworks.PyMySQL
private import semmle.python.frameworks.Pyodbc
private import semmle.python.frameworks.Pyramid
private import semmle.python.frameworks.Requests
private import semmle.python.frameworks.RestFramework
private import semmle.python.frameworks.Rsa

View File

@@ -344,6 +344,16 @@ 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. */
@@ -1419,6 +1429,16 @@ 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

@@ -1023,13 +1023,21 @@ predicate attributeClearStep(Node n, AttributeContent c) {
exists(PostUpdateNode post | post.getPreUpdateNode() = n | attributeStoreStep(_, c, post))
}
class NodeRegion instanceof Unit {
string toString() { result = "NodeRegion" }
predicate contains(Node n) { none() }
int totalOrder() { result = 1 }
}
//--------
// Fancy context-sensitive guards
//--------
/**
* Holds if the node `n` is unreachable when the call context is `call`.
* Holds if the nodes in `nr` are unreachable when the call context is `call`.
*/
predicate isUnreachableInCall(Node n, DataFlowCall call) { none() }
predicate isUnreachableInCall(NodeRegion nr, DataFlowCall call) { none() }
/**
* Holds if access paths with `c` at their head always should be tracked at high

View File

@@ -9,6 +9,7 @@ import Attributes
import LocalSources
private import semmle.python.essa.SsaCompute
private import semmle.python.dataflow.new.internal.ImportStar
private import semmle.python.frameworks.data.ModelsAsData
private import FlowSummaryImpl as FlowSummaryImpl
private import semmle.python.frameworks.data.ModelsAsData
@@ -125,6 +126,13 @@ newtype TNode =
f = any(VariableCapture::CapturedVariable v).getACapturingScope() and
// TODO: Remove this restriction when adding proper support for captured variables in the body of the function we generate for comprehensions
exists(TFunction(f))
} or
/** An empty, unused node type that exists to prevent unwanted dependencies on data flow nodes. */
TForbiddenRecursionGuard() {
none() and
// We want to prune irrelevant models before materialising data flow nodes, so types contributed
// directly from CodeQL must expose their pruning info without depending on data flow nodes.
(any(ModelInput::TypeModel tm).isTypeUsed("") implies any())
}
private import semmle.python.internal.CachedStages

View File

@@ -27,8 +27,7 @@ private module Cached {
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, string model) {
localAdditionalTaintStep(nodeFrom, nodeTo, model)
or
any(AdditionalTaintStep a).step(nodeFrom, nodeTo) and
model = "AdditionalTaintStep"
any(AdditionalTaintStep a).hasStep(nodeFrom, nodeTo, model)
}
/**

View File

@@ -47,6 +47,25 @@ class AdditionalTaintStep extends Unit {
/**
* Holds if the step from `nodeFrom` to `nodeTo` should be considered a taint
* step for all configurations.
*
* Note that it is now possible to also specify provenance of the taint step
* by overwriting `step/3`.
*/
abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo);
predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() }
/**
* Holds if the step from `nodeFrom` to `nodeTo` should be considered a taint
* step with provenance `model` for all configurations.
*/
predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, string model) { none() }
/**
* Holds if this `AdditionalTaintStep` defines a step from `nodeFrom` to `nodeTo`
* with provenance `model`.
*/
final predicate hasStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, string model) {
this.step(nodeFrom, nodeTo) and model = "AdditionalTaintStep"
or
this.step(nodeFrom, nodeTo, model)
}
}

View File

@@ -101,6 +101,19 @@ module Flask {
/** Gets a reference to the `flask.request` object. */
API::Node request() {
result = API::moduleImport(["flask", "flask_restful"]).getMember("request")
or
result = sessionInterfaceRequestParam()
}
/** Gets a `request` parameter of an implementation of `open_session` in a subclass of `flask.sessions.SessionInterface` */
private API::Node sessionInterfaceRequestParam() {
result =
API::moduleImport("flask")
.getMember("sessions")
.getMember("SessionInterface")
.getASubclass+()
.getMember("open_session")
.getParameter(1)
}
/**
@@ -220,6 +233,43 @@ module Flask {
/** Gets a reference to an instance of `flask.Response`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/** An `Headers` instance that is part of a Flask response. */
private class FlaskResponseHeadersInstances extends Werkzeug::Headers::InstanceSource {
FlaskResponseHeadersInstances() {
this.(DataFlow::AttrRead).getObject() = instance() and
this.(DataFlow::AttrRead).getAttributeName() = "headers"
}
}
/** A class instantiation of `Response` that sets response headers. */
private class ResponseClassHeadersWrite extends Http::Server::ResponseHeaderBulkWrite::Range,
ClassInstantiation
{
override DataFlow::Node getBulkArg() {
result = [this.getArg(2), this.getArgByName("headers")]
}
override predicate nameAllowsNewline() { any() }
override predicate valueAllowsNewline() { none() }
}
/** A call to `make_response that sets response headers. */
private class MakeResponseHeadersWrite extends Http::Server::ResponseHeaderBulkWrite::Range,
FlaskMakeResponseCall
{
override DataFlow::Node getBulkArg() {
result = this.getArg(2)
or
strictcount(this.getArg(_)) = 2 and
result = this.getArg(1)
}
override predicate nameAllowsNewline() { any() }
override predicate valueAllowsNewline() { none() }
}
}
// ---------------------------------------------------------------------------

View File

@@ -0,0 +1,123 @@
/**
* Provides classes modeling security-relevant aspects of the `gradio` PyPI package.
* See https://pypi.org/project/gradio/.
*/
import python
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.dataflow.new.TaintTracking
import semmle.python.ApiGraphs
/**
* Provides models for the `gradio` PyPI package.
* See https://pypi.org/project/gradio/.
*/
module Gradio {
/**
* The event handlers, Interface and gradio.ChatInterface classes, which take untrusted data.
*/
private class GradioInput extends API::CallNode {
GradioInput() {
this =
API::moduleImport("gradio")
.getMember([
"Button", "Textbox", "UploadButton", "Slider", "JSON", "HTML", "Markdown", "File",
"AnnotatedImage", "Audio", "BarPlot", "Chatbot", "Checkbox", "CheckboxGroup",
"ClearButton", "Code", "ColorPicker", "Dataframe", "Dataset", "DownloadButton",
"Dropdown", "DuplicateButton", "FileExplorer", "Gallery", "HighlightedText",
"Image", "ImageEditor", "Label", "LinePlot", "LoginButton", "LogoutButton",
"Model3D", "Number", "ParamViewer", "Plot", "Radio", "ScatterPlot", "SimpleImage",
"State", "Video"
])
.getReturn()
.getMember([
"change", "input", "click", "submit", "edit", "clear", "play", "pause", "stop",
"end", "start_recording", "pause_recording", "stop_recording", "focus", "blur",
"upload", "release", "select", "stream", "like", "load", "key_up",
])
.getACall()
or
this = API::moduleImport("gradio").getMember(["Interface", "ChatInterface"]).getACall()
}
}
/**
* The `inputs` parameters in Gradio event handlers, that are lists and are sources of untrusted data.
* This model allows tracking each element list back to source, f.ex. `gr.Textbox(...)`.
*/
private class GradioInputList extends RemoteFlowSource::Range {
GradioInputList() {
exists(GradioInput call |
// limit only to lists of parameters given to `inputs`.
(
(
call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
or
call.getParameter(1).asSink().asCfgNode() instanceof ListNode
) and
(
this = call.getKeywordParameter("inputs").getASubscript().getAValueReachingSink()
or
this = call.getParameter(1).getASubscript().getAValueReachingSink()
)
)
)
}
override string getSourceType() { result = "Gradio untrusted input" }
}
/**
* The `inputs` parameters in Gradio event handlers, that are not lists and are sources of untrusted data.
*/
private class GradioInputParameter extends RemoteFlowSource::Range {
GradioInputParameter() {
exists(GradioInput call |
this = call.getParameter(0, "fn").getParameter(_).asSource() and
// exclude lists of parameters given to `inputs`
not call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode and
not call.getParameter(1).asSink().asCfgNode() instanceof ListNode
)
}
override string getSourceType() { result = "Gradio untrusted input" }
}
/**
* The `inputs` parameters in Gradio decorators to event handlers, that are sources of untrusted data.
*/
private class GradioInputDecorator extends RemoteFlowSource::Range {
GradioInputDecorator() {
exists(GradioInput call |
this = call.getReturn().getACall().getParameter(0).getParameter(_).asSource()
)
}
override string getSourceType() { result = "Gradio untrusted input" }
}
/**
* Extra taint propagation for tracking `inputs` parameters in Gradio event handlers, that are lists.
*/
private class ListTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(GradioInput node |
// handle cases where there are multiple arguments passed as a list to `inputs`
(
(
node.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
or
node.getParameter(1).asSink().asCfgNode() instanceof ListNode
) and
exists(int i | nodeTo = node.getParameter(0, "fn").getParameter(i).asSource() |
nodeFrom.asCfgNode() =
node.getKeywordParameter("inputs").asSink().asCfgNode().(ListNode).getElement(i)
or
nodeFrom.asCfgNode() =
node.getParameter(1).asSink().asCfgNode().(ListNode).getElement(i)
)
)
)
}
}
}

View File

@@ -0,0 +1,64 @@
/**
* Provides classes modeling security-relevant aspects of the `opml` PyPI package.
*
* See
* - https://pypi.org/project/opml/
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
/**
* Provides classes modeling security-relevant aspects of the `opml` PyPI package
*
* See
* - https://pypi.org/project/opml/
*/
private module Opml {
/**
* A call to the `xpath` method of a parsed document.
*
* import opml
* root = opml.from_string(file(XML_DB).read())
* find_text = root.xpath("`sink`")
*/
private class XPathCall extends XML::XPathExecution::Range, DataFlow::CallCfgNode {
XPathCall() {
exists(API::Node parseResult |
parseResult = API::moduleImport("opml").getMember(["parse", "from_string"]).getReturn()
|
this = parseResult.getMember("xpath").getACall()
)
}
override DataFlow::Node getXPath() { result = this.getArg(0) }
override string getName() { result = "opml" }
}
/**
* A call to either of:
* - `opml.parse`
* - `opml.from_string`
*/
private class OpmlParsing extends DataFlow::CallCfgNode, XML::XmlParsing::Range {
OpmlParsing() {
this = API::moduleImport("opml").getMember(["parse", "from_string"]).getACall()
}
override DataFlow::Node getAnInput() { result = this.getArg(0) }
DataFlow::Node getParserArg() { none() }
/**
* The same as `Lxml::LxmlParsing::vulnerableTo`, because `opml` uses `lxml` for parsing.
*/
override predicate vulnerableTo(XML::XmlParsingVulnerabilityKind kind) { kind.isXxe() }
override predicate mayExecuteInput() { none() }
override DataFlow::Node getOutput() { result = this }
}
}

View File

@@ -0,0 +1,299 @@
/**
* Provides classes modeling security-relevant aspects of the `pyramid` PyPI package.
* See https://docs.pylonsproject.org/projects/pyramid/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.dataflow.new.FlowSummary
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.data.ModelsAsData
private import semmle.python.frameworks.Stdlib
/**
* Provides models for the `pyramid` PyPI package.
* See https://docs.pylonsproject.org/projects/pyramid/.
*/
module Pyramid {
/** Provides models for pyramid View callables. */
module View {
/** A dataflow node that sets up a route on a server using the Pyramid framework. */
abstract private class PyramidRouteSetup extends Http::Server::RouteSetup::Range {
override string getFramework() { result = "Pyramid" }
}
/**
* A Pyramid view callable, that handles incoming requests.
*/
class ViewCallable extends Function {
ViewCallable() { this = any(PyramidRouteSetup rs).getARequestHandler() }
/** Gets the `request` parameter of this callable. */
Parameter getRequestParameter() {
this.getPositionalParameterCount() = 1 and
result = this.getArg(0)
or
this.getPositionalParameterCount() = 2 and
result = this.getArg(1)
}
}
/** A pyramid route setup using the `pyramid.view.view_config` decorator. */
private class DecoratorSetup extends PyramidRouteSetup {
DecoratorSetup() {
this = API::moduleImport("pyramid").getMember("view").getMember("view_config").getACall()
}
override Function getARequestHandler() { result.getADecorator() = this.asExpr() }
override DataFlow::Node getUrlPatternArg() { none() } // there is a `route_name` arg, but that does not contain the url pattern
override Parameter getARoutedParameter() { none() }
}
/** A pyramid route setup using a call to `pyramid.config.Configurator.add_view`. */
private class ConfiguratorSetup extends PyramidRouteSetup instanceof Configurator::AddViewCall {
override Function getARequestHandler() {
this.(Configurator::AddViewCall).getViewArg() = poorMansFunctionTracker(result)
}
override DataFlow::Node getUrlPatternArg() { none() } // there is a `route_name` arg, but that does not contain the url pattern
override Parameter getARoutedParameter() { none() }
}
}
/** Provides models for `pyramid.config.Configurator` */
module Configurator {
/** Gets a reference to the class `pyramid.config.Configurator`. */
API::Node classRef() {
result = API::moduleImport("pyramid").getMember("config").getMember("Configurator")
}
/** Gets a reference to an instance of `pyramid.config.Configurator`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result = classRef().getACall()
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `pyramid.config.Configurator`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/** A call to the `add_view` method of an instance of `pyramid.config.Configurator`. */
class AddViewCall extends DataFlow::MethodCallNode {
AddViewCall() { this.calls(instance(), "add_view") }
/** Gets the `view` argument of this call. */
DataFlow::Node getViewArg() { result = [this.getArg(0), this.getArgByName("view")] }
}
}
/** Provides modeling for pyramid requests. */
module Request {
/**
* A source of instances of `pyramid.request.Request`, extend this class to model new instances.
*
* Use the predicate `Request::instance()` to get references to instances of `pyramid.request.Request`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
/** Gets a reference to an instance of `pyramid.request.Request`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `pyramid.request.Request`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
private class RequestParameter extends InstanceSource, RemoteFlowSource::Range instanceof DataFlow::ParameterNode
{
RequestParameter() { this.getParameter() = any(View::ViewCallable vc).getRequestParameter() }
override string getSourceType() { result = "Pyramid request parameter" }
}
/** Taint steps for request instances. */
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
InstanceTaintSteps() { this = "pyramid.request.Request" }
override DataFlow::Node getInstance() { result = instance() }
override string getAttributeName() {
result in [
"accept", "accept_charset", "accept_encoding", "accept_language", "application_url",
"as_bytes", "authorization", "body", "body_file", "body_file_raw", "body_file_seekable",
"cache_control", "client_addr", "content_type", "cookies", "domain", "headers", "host",
"host_port", "host_url", "GET", "if_match", "if_none_match", "if_range",
"if_none_match", "json", "json_body", "matchdict", "params", "path", "path_info",
"path_qs", "path_url", "POST", "pragma", "query_string", "range", "referer", "referrer",
"text", "url", "urlargs", "urlvars", "user_agent"
]
}
override string getMethodName() {
result in ["as_bytes", "copy", "copy_get", "path_info_peek", "path_info_pop"]
}
override string getAsyncMethodName() { none() }
}
/** A call to a method of a `request` that copies the request. */
private class RequestCopyCall extends InstanceSource, DataFlow::MethodCallNode {
RequestCopyCall() { this.calls(instance(), ["copy", "copy_get"]) }
}
/** A member of a request that is a file-like object. */
private class RequestBodyFileLike extends Stdlib::FileLikeObject::InstanceSource instanceof DataFlow::AttrRead
{
RequestBodyFileLike() {
this.getObject() = instance() and
this.getAttributeName() = ["body_file", "body_file_raw", "body_file_seekable"]
}
}
}
/** Provides modeling for pyramid responses. */
module Response {
/** A response returned by a view callable. */
private class PyramidReturnResponse extends Http::Server::HttpResponse::Range {
PyramidReturnResponse() {
this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() and
not this = instance()
}
override DataFlow::Node getBody() { result = this }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html" }
}
/** Gets a reference to the class `pyramid.response.Response`. */
API::Node classRef() {
result = API::moduleImport("pyramid").getMember("response").getMember("Response")
}
/**
* A source of instances of `pyramid.response.Response`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `Response::instance()` to get references to instances of `pyramid.response.Response`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode,
Http::Server::HttpResponse::Range
{ }
/** Gets a reference to an instance of `pyramid.response.Response`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `pyramid.response.Response`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/** An instantiation of the class `pyramid.response.Response` or a subclass. */
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
ClassInstantiation() { this = classRef().getACall() }
override DataFlow::Node getBody() { result = [this.getArg(0), this.getArgByName("body")] }
override DataFlow::Node getMimetypeOrContentTypeArg() {
result = [this.getArg(4), this.getArgByName("content_type")]
}
override string getMimetypeDefault() { result = "text/html" }
}
/** A write to a field that sets the body of a response. */
private class ResponseBodySet extends Http::Server::HttpResponse::Range instanceof DataFlow::AttrWrite
{
string attrName;
ResponseBodySet() {
this.getObject() = instance() and
this.getAttributeName() = attrName and
attrName in ["body", "body_file", "json", "json_body", "text", "ubody", "unicode_body"]
}
override DataFlow::Node getBody() { result = this.(DataFlow::AttrWrite).getValue() }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() {
if attrName in ["json", "json_body"]
then result = "application/json"
else result = "text/html"
}
}
/** A use of the `response` attribute of a `Request`. */
private class RequestResponseAttr extends InstanceSource instanceof DataFlow::AttrRead {
RequestResponseAttr() {
this.getObject() = Request::instance() and this.getAttributeName() = "response"
}
override DataFlow::Node getBody() { none() }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html" }
}
/** A call to `response.set_cookie`. */
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
SetCookieCall() { this.calls(instance(), "set_cookie") }
override DataFlow::Node getHeaderArg() { none() }
override DataFlow::Node getNameArg() { result = [this.getArg(0), this.getArgByName("name")] }
override DataFlow::Node getValueArg() {
result = [this.getArg(1), this.getArgByName("value")]
}
}
}
/** Provides models for pyramid http redirects. */
module Redirect {
/** Gets a reference to a class that represents an HTTP redirect response.. */
API::Node classRef() {
result =
API::moduleImport("pyramid")
.getMember("httpexceptions")
.getMember([
"HTTPMultipleChoices", "HTTPMovedPermanently", "HTTPFound", "HTTPSeeOther",
"HTTPUseProxy", "HTTPTemporaryRedirect", "HTTPPermanentRedirect"
])
}
/** A call to a pyramid HTTP exception class that represents an HTTP redirect response. */
class PyramidRedirect extends Http::Server::HttpRedirectResponse::Range, DataFlow::CallCfgNode {
PyramidRedirect() { this = classRef().getACall() }
override DataFlow::Node getRedirectLocation() {
result = [this.getArg(0), this.getArgByName("location")]
}
override DataFlow::Node getBody() { none() }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html" }
}
}
}

View File

@@ -2183,17 +2183,35 @@ module StdlibPrivate {
* for how a request is processed and given to an application.
*/
class WsgirefSimpleServerApplication extends Http::Server::RequestHandler::Range {
boolean validator;
WsgirefSimpleServerApplication() {
exists(DataFlow::Node appArg, DataFlow::CallCfgNode setAppCall |
(
setAppCall =
WsgirefSimpleServer::subclassRef().getReturn().getMember("set_app").getACall()
WsgirefSimpleServer::subclassRef().getReturn().getMember("set_app").getACall() and
validator = false
or
setAppCall
.(DataFlow::MethodCallNode)
.calls(any(WsgiServerSubclass cls).getASelfRef(), "set_app")
.calls(any(WsgiServerSubclass cls).getASelfRef(), "set_app") and
validator = false
or
// assume an application that is passed to `wsgiref.validate.validator` is eventually passed to `set_app`
setAppCall =
API::moduleImport("wsgiref").getMember("validate").getMember("validator").getACall() and
validator = true
) and
appArg in [setAppCall.getArg(0), setAppCall.getArgByName("application")]
or
// `make_server` calls `set_app`
setAppCall =
API::moduleImport("wsgiref")
.getMember("simple_server")
.getMember("make_server")
.getACall() and
appArg in [setAppCall.getArg(2), setAppCall.getArgByName("app")] and
validator = false
|
appArg = poorMansFunctionTracker(this)
)
@@ -2202,6 +2220,9 @@ module StdlibPrivate {
override Parameter getARoutedParameter() { none() }
override string getFramework() { result = "Stdlib: wsgiref.simple_server application" }
/** Holds if this simple server application was passed to `wsgiref.validate.validator`. */
predicate isValidated() { validator = true }
}
/**
@@ -2305,6 +2326,114 @@ module StdlibPrivate {
override string getMimetypeDefault() { none() }
}
/**
* Provides models for the `wsgiref.headers.Headers` class
*
* See https://docs.python.org/3/library/wsgiref.html#module-wsgiref.headers.
*/
module Headers {
/** Gets a reference to the `wsgiref.headers.Headers` class. */
API::Node classRef() {
result = API::moduleImport("wsgiref").getMember("headers").getMember("Headers")
or
result = ModelOutput::getATypeNode("wsgiref.headers.Headers~Subclass").getASubclass*()
}
/** Gets a reference to an instance of `wsgiref.headers.Headers`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result = classRef().getACall()
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `wsgiref.headers.Headers`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/** Holds if there exists an application that is validated by `wsgiref.validate.validator`. */
private predicate existsValidatedApplication() {
exists(WsgirefSimpleServerApplication app | app.isValidated())
}
/** A class instantiation of `wsgiref.headers.Headers`, conidered as a write to a response header. */
private class WsgirefHeadersInstantiation extends Http::Server::ResponseHeaderBulkWrite::Range,
DataFlow::CallCfgNode
{
WsgirefHeadersInstantiation() { this = classRef().getACall() }
override DataFlow::Node getBulkArg() {
result = [this.getArg(0), this.getArgByName("headers")]
}
// TODO: These checks perhaps could be made more precise.
override predicate nameAllowsNewline() { not existsValidatedApplication() }
override predicate valueAllowsNewline() { not existsValidatedApplication() }
}
/** A call to a method that writes to a response header. */
private class HeaderWriteCall extends Http::Server::ResponseHeaderWrite::Range,
DataFlow::MethodCallNode
{
HeaderWriteCall() {
this.calls(instance(), ["add_header", "set", "setdefault", "__setitem__"])
}
override DataFlow::Node getNameArg() { result = this.getArg(0) }
override DataFlow::Node getValueArg() { result = this.getArg(1) }
// TODO: These checks perhaps could be made more precise.
override predicate nameAllowsNewline() { not existsValidatedApplication() }
override predicate valueAllowsNewline() { not existsValidatedApplication() }
}
/** A dict-like write to a response header. */
private class HeaderWriteSubscript extends Http::Server::ResponseHeaderWrite::Range,
DataFlow::Node
{
DataFlow::Node name;
DataFlow::Node value;
HeaderWriteSubscript() {
exists(SubscriptNode subscript |
this.asCfgNode() = subscript and
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
name.asCfgNode() = subscript.getIndex() and
subscript.getObject() = instance().asCfgNode()
)
}
override DataFlow::Node getNameArg() { result = name }
override DataFlow::Node getValueArg() { result = value }
// TODO: These checks perhaps could be made more precise.
override predicate nameAllowsNewline() { not existsValidatedApplication() }
override predicate valueAllowsNewline() { not existsValidatedApplication() }
}
/**
* A call to a `start_response` function that sets the response headers.
*/
private class WsgirefSimpleServerSetHeaders extends Http::Server::ResponseHeaderBulkWrite::Range,
DataFlow::CallCfgNode
{
WsgirefSimpleServerSetHeaders() { this.getFunction() = startResponse() }
override DataFlow::Node getBulkArg() {
result = [this.getArg(1), this.getArgByName("headers")]
}
// TODO: These checks perhaps could be made more precise.
override predicate nameAllowsNewline() { not existsValidatedApplication() }
override predicate valueAllowsNewline() { not existsValidatedApplication() }
}
}
}
// ---------------------------------------------------------------------------

View File

@@ -12,6 +12,7 @@ private import semmle.python.ApiGraphs
private import semmle.python.frameworks.Stdlib
private import semmle.python.Concepts
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.data.ModelsAsData
/**
* Provides models for the `Werkzeug` PyPI package.
@@ -144,6 +145,18 @@ module Werkzeug {
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.
*/
module Headers {
/** Gets a reference to the `werkzeug.datastructures.Headers` class. */
API::Node classRef() {
result = API::moduleImport("werkzeug").getMember("datastructures").getMember("Headers")
or
result = ModelOutput::getATypeNode("werkzeug.datastructures.Headers~Subclass").getASubclass*()
}
/** A direct instantiation of `werkzeug.datastructures.Headers`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
ClassInstantiation() { this = classRef().getACall() }
}
/**
* A source of instances of `werkzeug.datastructures.Headers`, extend this class to model new instances.
*
@@ -182,6 +195,61 @@ module Werkzeug {
override string getAsyncMethodName() { none() }
}
/** A call to a method that writes to a header, assumed to be a response header. */
private class HeaderWriteCall extends Http::Server::ResponseHeaderWrite::Range,
DataFlow::MethodCallNode
{
HeaderWriteCall() {
this.calls(instance(), ["add", "add_header", "set", "setdefault", "__setitem__"])
}
override DataFlow::Node getNameArg() { result = this.getArg(0) }
override DataFlow::Node getValueArg() { result = this.getArg(1) }
override predicate nameAllowsNewline() { any() }
override predicate valueAllowsNewline() { none() }
}
/** A dict-like write to a header, assumed to be a response header. */
private class HeaderWriteSubscript extends Http::Server::ResponseHeaderWrite::Range,
DataFlow::Node
{
DataFlow::Node name;
DataFlow::Node value;
HeaderWriteSubscript() {
exists(SubscriptNode subscript |
this.asCfgNode() = subscript and
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
name.asCfgNode() = subscript.getIndex() and
subscript.getObject() = instance().asCfgNode()
)
}
override DataFlow::Node getNameArg() { result = name }
override DataFlow::Node getValueArg() { result = value }
override predicate nameAllowsNewline() { any() }
override predicate valueAllowsNewline() { none() }
}
/** A call to `Headers.extend`, assumed to be a response header. */
private class HeaderExtendCall extends Http::Server::ResponseHeaderBulkWrite::Range,
DataFlow::MethodCallNode
{
HeaderExtendCall() { this.calls(instance(), "extend") }
override DataFlow::Node getBulkArg() { result = this.getArg(0) }
override predicate nameAllowsNewline() { any() }
override predicate valueAllowsNewline() { none() }
}
}
/**

View File

@@ -168,9 +168,20 @@ module ModelInput {
* A unit class for adding additional type model rows from CodeQL models.
*/
class TypeModel extends Unit {
/**
* Holds if any of the other predicates in this class might have a result
* for the given `type`.
*
* The implementation of this predicate should not depend on `DataFlow::Node`.
*/
bindingset[type]
predicate isTypeUsed(string type) { none() }
/**
* Gets a data-flow node that is a source of the given `type`.
*
* Note that `type` should also be included in `isTypeUsed`.
*
* This must not depend on API graphs, but ensures that an API node is generated for
* the source.
*/
@@ -180,6 +191,8 @@ module ModelInput {
* Gets a data-flow node that is a sink of the given `type`,
* usually because it is an argument passed to a parameter of that type.
*
* Note that `type` should also be included in `isTypeUsed`.
*
* This must not depend on API graphs, but ensures that an API node is generated for
* the sink.
*/
@@ -188,6 +201,8 @@ module ModelInput {
/**
* Gets an API node that is a source or sink of the given `type`.
*
* Note that `type` should also be included in `isTypeUsed`.
*
* Unlike `getASource` and `getASink`, this may depend on API graphs.
*/
API::Node getAnApiNode(string type) { none() }
@@ -354,6 +369,28 @@ private predicate typeVariableModel(string name, string path) {
Extensions::typeVariableModel(name, 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 |
Extensions::sourceModel(type, path, kind, madId) and
model = "Source: " + type + "; " + path + "; " + kind
)
or
exists(string type, string path, string kind |
Extensions::sinkModel(type, path, kind, madId) and
model = "Sink: " + type + "; " + path + "; " + kind
)
or
exists(string type, string path, string input, string output, string kind |
Extensions::summaryModel(type, path, input, output, kind, madId) and
model = "Summary: " + type + "; " + path + "; " + input + "; " + output + "; " + kind
)
}
/**
* Holds if rows involving `type` might be relevant for the analysis of this database.
*/
@@ -367,6 +404,8 @@ predicate isRelevantType(string type) {
(
Specific::isTypeUsed(type)
or
any(TypeModel model).isTypeUsed(type)
or
exists(TestAllModels t)
)
or

View File

@@ -29,7 +29,11 @@ import semmle.python.dataflow.new.DataFlow::DataFlow as DataFlow
/**
* Holds if models describing `type` may be relevant for the analysis of this database.
*/
predicate isTypeUsed(string type) { API::moduleImportExists(type) }
bindingset[type]
predicate isTypeUsed(string type) {
// If `type` is a path, then it is the first component that should be imported.
API::moduleImportExists(type.splitAt(".", 0))
}
/**
* Holds if `type` can be obtained from an instance of `otherType` due to
@@ -41,8 +45,59 @@ predicate hasImplicitTypeModel(string type, string otherType) { none() }
bindingset[type, path]
API::Node getExtraNodeFromPath(string type, AccessPath path, int n) { none() }
/**
* Holds if `type` = `typePath`+`suffix` and `suffix` is either empty or "!".
*/
bindingset[type]
private predicate parseType(string type, string typePath, string suffix) {
exists(string regexp |
regexp = "([^!]+)(!|)" and
typePath = type.regexpCapture(regexp, 1) and
suffix = type.regexpCapture(regexp, 2)
)
}
private predicate parseRelevantType(string type, string typePath, string suffix) {
isRelevantType(type) and
parseType(type, typePath, suffix)
}
pragma[nomagic]
private string getTypePathComponent(string typePath, int n) {
parseRelevantType(_, typePath, _) and
result = typePath.splitAt(".", n)
}
private int getNumTypePathComponents(string typePath) {
result = strictcount(int n | exists(getTypePathComponent(typePath, n)))
}
private API::Node getNodeFromTypePath(string typePath, int n) {
n = 1 and
result = API::moduleImport(getTypePathComponent(typePath, 0))
or
result = getNodeFromTypePath(typePath, n - 1).getMember(getTypePathComponent(typePath, n - 1))
}
private API::Node getNodeFromTypePath(string typePath) {
result = getNodeFromTypePath(typePath, getNumTypePathComponents(typePath))
}
/** Gets a Python-specific interpretation of the given `type`. */
API::Node getExtraNodeFromType(string type) { result = API::moduleImport(type) }
API::Node getExtraNodeFromType(string type) {
result = API::moduleImport(type)
or
exists(string typePath, string suffix, API::Node node |
parseRelevantType(type, typePath, suffix) and
node = getNodeFromTypePath(typePath)
|
suffix = "!" and
result = node
or
suffix = "" and
result = node.getAnInstance()
)
}
/**
* Gets a Python-specific API graph successor of `node` reachable by resolving `token`.
@@ -53,7 +108,7 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
result = node.getMember(token.getAnArgument())
or
token.getName() = "Instance" and
result = node.getReturn() // In Python `Instance` is just an alias for `ReturnValue`
result = node.getAnInstance()
or
token.getName() = "Awaited" and
result = node.getAwaited()

View File

@@ -0,0 +1,113 @@
/**
* Provides default sources, sinks, and sanitizers for detecting
* "HTTP Header injection" vulnerabilities, as well as extension
* points for adding your own.
*/
import python
private import semmle.python.Concepts
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.dataflow.new.RemoteFlowSources
/**
* Provides default sources, sinks, and sanitizers for detecting
* "HTTP Header injection" vulnerabilities, as well as extension
* points for adding your own.
*/
module HttpHeaderInjection {
/**
* A data flow source for "HTTP Header injection" vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for "HTTP Header injection" vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A data flow sanitizer for "HTTP Header 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 HTTP header write, considered as a flow sink.
*/
class HeaderWriteAsSink extends Sink {
HeaderWriteAsSink() {
exists(Http::Server::ResponseHeaderWrite headerWrite |
headerWrite.nameAllowsNewline() and
this = headerWrite.getNameArg()
or
headerWrite.valueAllowsNewline() and
this = headerWrite.getValueArg()
)
}
}
/** 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.
*/
class ReplaceLineBreaksSanitizer extends Sanitizer, DataFlow::CallCfgNode {
ReplaceLineBreaksSanitizer() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "replace" and
this.getArg(0).asExpr().(StringLiteral).getText() = "\n"
}
}
}

View File

@@ -0,0 +1,22 @@
/**
* Provides a taint tracking configuration for reasoning about HTTP header injection.
*/
import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.TaintTracking
private import HttpHeaderInjectionCustomizations
/**
* A taint-tracking configuration for detecting HTTP Header injection vulnerabilities.
*/
private module HeaderInjectionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { node instanceof HttpHeaderInjection::Source }
predicate isSink(DataFlow::Node node) { node instanceof HttpHeaderInjection::Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof HttpHeaderInjection::Sanitizer }
}
/** Global taint-tracking for detecting "HTTP Header injection" vulnerabilities. */
module HeaderInjectionFlow = TaintTracking::Global<HeaderInjectionConfig>;

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.ApiGraphs
/**
* Provides default sources, sinks and sanitizers for detecting
@@ -137,4 +138,34 @@ module ServerSideRequestForgery {
)
}
}
/** A validation that a string does not contain certain characters, considered as a sanitizer. */
private class StringRestrictionSanitizerGuard extends Sanitizer {
StringRestrictionSanitizerGuard() {
this = DataFlow::BarrierGuard<stringRestriction/3>::getABarrierNode()
}
}
private predicate stringRestriction(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
exists(DataFlow::MethodCallNode call, DataFlow::Node strNode |
call.asCfgNode() = g and strNode.asCfgNode() = node
|
branch = true and
call.calls(strNode,
["isalnum", "isalpha", "isdecimal", "isdigit", "isidentifier", "isnumeric", "isspace"])
or
branch = true and
call = API::moduleImport("re").getMember(["match", "fullmatch"]).getACall() and
strNode = [call.getArg(1), call.getArgByName("string")]
or
branch = true and
call =
API::moduleImport("re")
.getMember("compile")
.getReturn()
.getMember(["match", "fullmatch"])
.getACall() and
strNode = [call.getArg(0), call.getArgByName("string")]
)
}
}

View File

@@ -74,7 +74,7 @@ predicate fullyControlledRequest(Http::Client::Request request) {
}
/**
* DEPRECATED: Use `FullServerSideRequestForgeryFlow` module instead.
* DEPRECATED: Use `PartialServerSideRequestForgeryFlow` module instead.
*
* A taint-tracking configuration for detecting "Server-side request forgery" vulnerabilities.
*

View File

@@ -14,13 +14,14 @@
* - id: a user name or other account information;
* - password: a password or authorization key;
* - certificate: a certificate.
* - private: private data such as credit card numbers
*
* While classifications are represented as strings, this should not be relied upon.
* Instead, use the predicates in `SensitiveDataClassification::` to work with
* classifications.
*/
class SensitiveDataClassification extends string {
SensitiveDataClassification() { this in ["secret", "id", "password", "certificate"] }
SensitiveDataClassification() { this in ["secret", "id", "password", "certificate", "private"] }
}
/**
@@ -38,6 +39,9 @@ module SensitiveDataClassification {
/** Gets the classification for certificates. */
SensitiveDataClassification certificate() { result = "certificate" }
/** Gets the classification for private data. */
SensitiveDataClassification private() { result = "private" }
}
/**
@@ -77,6 +81,40 @@ module HeuristicNames {
*/
string maybeCertificate() { result = "(?is).*(cert)(?!.*(format|name|ification)).*" }
/**
* Gets a regular expression that identifies strings that may indicate the presence of
* private data.
*/
string maybePrivate() {
result =
"(?is).*(" +
// Inspired by the list on https://cwe.mitre.org/data/definitions/359.html
// Government identifiers, such as Social Security Numbers
"social.?security|employer.?identification|national.?insurance|resident.?id|" +
"passport.?(num|no)|([_-]|\\b)ssn([_-]|\\b)|" +
// Contact information, such as home addresses
"post.?code|zip.?code|home.?addr|" +
// and telephone numbers
"(mob(ile)?|home).?(num|no|tel|phone)|(tel|fax|phone).?(num|no)|telephone|" +
"emergency.?contact|" +
// Geographic location - where the user is (or was)
"latitude|longitude|nationality|" +
// Financial data - such as credit card numbers, salary, bank accounts, and debts
"(credit|debit|bank|visa).?(card|num|no|acc(ou)?nt)|acc(ou)?nt.?(no|num|credit)|" +
"salary|billing|credit.?(rating|score)|([_-]|\\b)ccn([_-]|\\b)|" +
// Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc.
// "e(mail|_mail)|" + // this seems too noisy
// Health - medical conditions, insurance status, prescription records
"birth.?da(te|y)|da(te|y).?(of.?)?birth|" +
"medical|(health|care).?plan|healthkit|appointment|prescription|" +
"blood.?(type|alcohol|glucose|pressure)|heart.?(rate|rhythm)|body.?(mass|fat)|" +
"menstrua|pregnan|insulin|inhaler|" +
// Relationships - work and family
"employ(er|ee)|spouse|maiden.?name" +
// ---
").*"
}
/**
* Gets a regular expression that identifies strings that may indicate the presence
* of sensitive data, with `classification` describing the kind of sensitive data involved.
@@ -90,6 +128,9 @@ module HeuristicNames {
or
result = maybeCertificate() and
classification = SensitiveDataClassification::certificate()
or
result = maybePrivate() and
classification = SensitiveDataClassification::private()
}
/**

View File

@@ -1,4 +1,4 @@
description: Removed unused column from the `folders` and `files` relations
compatibility: full
files.rel: reorder files.rel (int id, string name, string simple, string ext, int fromSource) id name
folders.rel: reorder folders.rel (int id, string name, string simple) id name
files.rel: reorder files.rel (@file id, string name, string simple, string ext, int fromSource) id name
folders.rel: reorder folders.rel (@folder id, string name, string simple) id name

View File

@@ -1,3 +1,25 @@
## 1.0.1
### Minor Analysis Improvements
* Added models for `opml` library.
## 1.0.0
### Breaking Changes
* CodeQL package management is now generally available, and all GitHub-produced CodeQL packages have had their version numbers increased to 1.0.0.
### Minor Analysis Improvements
* Added models of `gradio` PyPI package.
## 0.9.16
### New Queries
* The `py/header-injection` query, originally contributed to the experimental query pack by @jorgectf, has been promoted to the main query pack and renamed to `py/http-response-splitting`. This query finds instances of http header injection / response splitting vulnerabilities.
## 0.9.15
No user-facing changes.

View File

@@ -12,6 +12,29 @@
import python
import Variables.Definition
import semmle.python.ApiGraphs
private predicate is_pytest_fixture(Import imp, Variable name) {
exists(Alias a, API::Node pytest_fixture, API::Node decorator |
pytest_fixture = API::moduleImport("pytest").getMember("fixture") and
// The additional `.getReturn()` is to account for the difference between
// ```
// @pytest.fixture
// def foo():
// ...
// ```
// and
// ```
// @pytest.fixture(some, args, here)
// def foo():
// ...
// ```
decorator in [pytest_fixture, pytest_fixture.getReturn()] and
a = imp.getAName() and
a.getAsname().(Name).getVariable() = name and
a.getValue() = decorator.getReturn().getAValueReachableFromSource().asExpr()
)
}
predicate global_name_used(Module m, string name) {
exists(Name u, GlobalVariable v |
@@ -117,6 +140,7 @@ predicate unused_import(Import imp, Variable name) {
not all_not_understood(imp.getEnclosingModule()) and
not imported_module_used_in_doctest(imp) and
not imported_alias_used_in_typehint(imp, name) and
not is_pytest_fixture(imp, name) and
// Only consider import statements that actually point-to something (possibly an unknown module).
// If this is not the case, it's likely that the import statement never gets executed.
imp.getAName().getValue().pointsTo(_)

View File

@@ -0,0 +1,41 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Directly writing user input (for example, an HTTP request parameter) to an HTTP header
can lead to an HTTP response-splitting vulnerability.</p>
<p>If user-controlled input is used in an HTTP header that allows line break characters, an attacker can
inject additional headers or control the response body, leading to vulnerabilities such as XSS or cache poisoning.
</p>
</overview>
<recommendation>
<p>Ensure that user input containing line break characters is not written to an HTTP header.</p>
</recommendation>
<example>
<p>In the following example, the case marked BAD writes user input to the header name.
In the GOOD case, input is first escaped to not contain any line break characters.</p>
<sample src="examples/header_injection.py" />
</example>
<references>
<li>
SecLists.org: <a href="https://seclists.org/bugtraq/2005/Apr/187">HTTP response splitting</a>.
</li>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/HTTP_Response_Splitting">HTTP Response Splitting</a>.
</li>
<li>
Wikipedia: <a href="http://en.wikipedia.org/wiki/HTTP_response_splitting">HTTP response splitting</a>.
</li>
<li>
CAPEC: <a href="https://capec.mitre.org/data/definitions/105.html">CAPEC-105: HTTP Request Splitting</a>
</li>
</references>
</qhelp>

View File

@@ -1,19 +1,19 @@
/**
* @name HTTP Header Injection
* @description User input should not be used in HTTP headers, otherwise a malicious user
* may be able to inject a value that could manipulate the response.
* @name HTTP Response Splitting
* @description Writing user input directly to an HTTP header
* makes code vulnerable to attack by header splitting.
* @kind path-problem
* @problem.severity error
* @id py/header-injection
* @security-severity 6.1
* @precision high
* @id py/http-response-splitting
* @tags security
* experimental
* external/cwe/cwe-113
* external/cwe/cwe-079
*/
// determine precision above
import python
import experimental.semmle.python.security.injection.HTTPHeaders
import semmle.python.security.dataflow.HttpHeaderInjectionQuery
import HeaderInjectionFlow::PathGraph
from HeaderInjectionFlow::PathNode source, HeaderInjectionFlow::PathNode sink

View File

@@ -0,0 +1,17 @@
@app.route("/example_bad")
def example_bad():
rfs_header = request.args["rfs_header"]
response = Response()
custom_header = "X-MyHeader-" + rfs_header
# BAD: User input is used as part of the header name.
response.headers[custom_header] = "HeaderValue"
return response
@app.route("/example_good")
def example_bad():
rfs_header = request.args["rfs_header"]
response = Response()
custom_header = "X-MyHeader-" + rfs_header.replace("\n", "").replace("\r","").replace(":","")
# GOOD: Line break characters are removed from the input.
response.headers[custom_header] = "HeaderValue"
return response

View File

@@ -45,12 +45,21 @@ attribute is empty.
</p>
<p>
Note, however, that many browsers accept backslash characters (<code>\</code>) as equivalent
to forward slash characters (<code>/</code>) in URLs, but the <code>urlparse</code> function
does not. To account for this, you can first replace all backslashes with forward slashes,
as shown in the following example:
Note, however, that some cases are not handled as we desire out-of-the-box by <code>urlparse</code>, so we need to adjust two things, as shown in the example below:
</p>
<ul>
<li>
Many browsers accept backslash characters (<code>\</code>) as equivalent
to forward slash characters (<code>/</code>) in URLs, but the <code>urlparse</code> function
does not.
</li>
<li>
Mistyped URLs such as <code>https:/example.com</code> or <code>https:///example.com</code> are parsed as having an empty <code>netloc</code> attribute, while browsers will still redirect to the correct site.
</li>
</ul>
<sample src="examples/redirect_good2.py"/>
<p>

View File

@@ -7,7 +7,7 @@ app = Flask(__name__)
def hello():
target = request.args.get('target', '')
target = target.replace('\\', '')
if not urlparse(target).netloc:
if not urlparse(target).netloc and not urlparse(target).scheme:
# relative path, safe to redirect
return redirect(target, code=302)
# ignore the target and redirect to the home page

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Additional sanitizers have been added to the `py/full-ssrf` and `py/partial-ssrf` queries for methods that verify a string contains only a certain set of characters, such as `.isalnum()` as well as regular expression tests.

View File

@@ -0,0 +1,5 @@
## 0.9.16
### New Queries
* The `py/header-injection` query, originally contributed to the experimental query pack by @jorgectf, has been promoted to the main query pack and renamed to `py/http-response-splitting`. This query finds instances of http header injection / response splitting vulnerabilities.

View File

@@ -0,0 +1,9 @@
## 1.0.0
### Breaking Changes
* CodeQL package management is now generally available, and all GitHub-produced CodeQL packages have had their version numbers increased to 1.0.0.
### Minor Analysis Improvements
* Added models of `gradio` PyPI package.

View File

@@ -0,0 +1,5 @@
## 1.0.1
### Minor Analysis Improvements
* Added models for `opml` library.

View File

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

View File

@@ -1,26 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>If an HTTP Header is built using string concatenation or string formatting, and the
components of the concatenation include user input, a user
is likely to be able to manipulate the response.</p>
</overview>
<recommendation>
<p>User input should not be included in an HTTP Header.</p>
</recommendation>
<example>
<p>In the following example, the code appends a user-provided value into a header.</p>
<sample src="header_injection.py" />
</example>
<references>
<li>OWASP: <a href="https://owasp.org/www-community/attacks/HTTP_Response_Splitting">HTTP Response Splitting</a>.</li>
<li>Python Security: <a href="https://python-security.readthedocs.io/vuln/http-header-injection.html">HTTP header injection</a>.</li>
<li>SonarSource: <a href="https://rules.sonarsource.com/python/RSPEC-5167">RSPEC-5167</a>.</li>
</references>
</qhelp>

View File

@@ -1,9 +0,0 @@
from flask import Response, request, Flask, make_response
@app.route("/flask_Response")
def flask_Response():
rfs_header = request.args["rfs_header"]
response = Response()
response.headers['HeaderName'] = rfs_header
return response

View File

@@ -249,45 +249,6 @@ class SqlEscape extends DataFlow::Node instanceof SqlEscape::Range {
DataFlow::Node getAnInput() { result = super.getAnInput() }
}
/** Provides classes for modeling HTTP Header APIs. */
module HeaderDeclaration {
/**
* A data-flow node that collects functions setting HTTP Headers.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HeaderDeclaration` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the argument containing the header name.
*/
abstract DataFlow::Node getNameArg();
/**
* Gets the argument containing the header value.
*/
abstract DataFlow::Node getValueArg();
}
}
/**
* A data-flow node that collects functions setting HTTP Headers.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HeaderDeclaration::Range` instead.
*/
class HeaderDeclaration extends DataFlow::Node instanceof HeaderDeclaration::Range {
/**
* Gets the argument containing the header name.
*/
DataFlow::Node getNameArg() { result = super.getNameArg() }
/**
* Gets the argument containing the header value.
*/
DataFlow::Node getValueArg() { result = super.getValueArg() }
}
/** Provides classes for modeling Csv writer APIs. */
module CsvWriter {
/**

View File

@@ -6,6 +6,7 @@ import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import experimental.semmle.python.Concepts
import semmle.python.Concepts
/**
* Gets a header setting a cookie.
@@ -26,13 +27,13 @@ import experimental.semmle.python.Concepts
* * `isSameSite()` predicate would fail.
* * `getName()` and `getValue()` results would be `"name=value; Secure;"`.
*/
class CookieHeader extends Cookie::Range instanceof HeaderDeclaration {
class CookieHeader extends Cookie::Range instanceof Http::Server::ResponseHeaderWrite {
CookieHeader() {
exists(StringLiteral str |
str.getText() = "Set-Cookie" and
DataFlow::exprNode(str)
.(DataFlow::LocalSourceNode)
.flowsTo(this.(HeaderDeclaration).getNameArg())
.flowsTo(this.(Http::Server::ResponseHeaderWrite).getNameArg())
)
}
@@ -41,7 +42,7 @@ class CookieHeader extends Cookie::Range instanceof HeaderDeclaration {
str.getText().regexpMatch(".*; *Secure;.*") and
DataFlow::exprNode(str)
.(DataFlow::LocalSourceNode)
.flowsTo(this.(HeaderDeclaration).getValueArg())
.flowsTo(this.(Http::Server::ResponseHeaderWrite).getValueArg())
)
}
@@ -50,7 +51,7 @@ class CookieHeader extends Cookie::Range instanceof HeaderDeclaration {
str.getText().regexpMatch(".*; *HttpOnly;.*") and
DataFlow::exprNode(str)
.(DataFlow::LocalSourceNode)
.flowsTo(this.(HeaderDeclaration).getValueArg())
.flowsTo(this.(Http::Server::ResponseHeaderWrite).getValueArg())
)
}
@@ -59,13 +60,17 @@ class CookieHeader extends Cookie::Range instanceof HeaderDeclaration {
str.getText().regexpMatch(".*; *SameSite=(Strict|Lax);.*") and
DataFlow::exprNode(str)
.(DataFlow::LocalSourceNode)
.flowsTo(this.(HeaderDeclaration).getValueArg())
.flowsTo(this.(Http::Server::ResponseHeaderWrite).getValueArg())
)
}
override DataFlow::Node getNameArg() { result = this.(HeaderDeclaration).getValueArg() }
override DataFlow::Node getNameArg() {
result = this.(Http::Server::ResponseHeaderWrite).getValueArg()
}
override DataFlow::Node getValueArg() { result = this.(HeaderDeclaration).getValueArg() }
override DataFlow::Node getValueArg() {
result = this.(Http::Server::ResponseHeaderWrite).getValueArg()
}
override DataFlow::Node getHeaderArg() { none() }
}

View File

@@ -6,7 +6,6 @@ private import experimental.semmle.python.frameworks.AsyncSsh
private import experimental.semmle.python.frameworks.Stdlib
private import experimental.semmle.python.frameworks.Flask
private import experimental.semmle.python.frameworks.Django
private import experimental.semmle.python.frameworks.Werkzeug
private import experimental.semmle.python.frameworks.LDAP
private import experimental.semmle.python.frameworks.Netmiko
private import experimental.semmle.python.frameworks.Paramiko

View File

@@ -88,31 +88,6 @@ private module ExperimentalPrivateDjango {
result = baseClassRef().getReturn().getAMember()
}
class DjangoResponseSetItemCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
DjangoResponseSetItemCall() {
this = baseClassRef().getReturn().getMember("__setitem__").getACall()
}
override DataFlow::Node getNameArg() { result = this.getArg(0) }
override DataFlow::Node getValueArg() { result = this.getArg(1) }
}
class DjangoResponseDefinition extends DataFlow::Node, HeaderDeclaration::Range {
DataFlow::Node headerInput;
DjangoResponseDefinition() {
headerInput = headerInstance().asSink() and
headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
}
override DataFlow::Node getNameArg() {
result.asExpr() = this.asExpr().(Subscript).getIndex()
}
override DataFlow::Node getValueArg() { result = headerInput }
}
/**
* Gets a call to `set_cookie()`.
*

View File

@@ -11,76 +11,6 @@ private import semmle.python.ApiGraphs
private import semmle.python.frameworks.Flask
module ExperimentalFlask {
/**
* A reference to either `flask.make_response` function, or the `make_response` method on
* an instance of `flask.Flask`. This creates an instance of the `flask_response`
* class (class-attribute on a flask application), which by default is
* `flask.Response`.
*
* See
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.make_response
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response
*/
private API::Node flaskMakeResponse() {
result =
[API::moduleImport("flask"), Flask::FlaskApp::instance()]
.getMember(["make_response", "jsonify", "make_default_options_response"])
}
/** Gets a reference to a header instance. */
private DataFlow::LocalSourceNode headerInstance() {
result =
[Flask::Response::classRef(), flaskMakeResponse()]
.getReturn()
.getAMember()
.getAValueReachableFromSource()
}
/** Gets a reference to a header instance call/subscript */
private DataFlow::Node headerInstanceCall() {
headerInstance() in [result.(DataFlow::AttrRead), result.(DataFlow::AttrRead).getObject()] or
headerInstance().asExpr() = result.asExpr().(Subscript).getObject()
}
class FlaskHeaderDefinition extends DataFlow::Node, HeaderDeclaration::Range {
DataFlow::Node headerInput;
FlaskHeaderDefinition() {
this.asCfgNode().(DefinitionNode) = headerInstanceCall().asCfgNode() and
headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
}
override DataFlow::Node getNameArg() { result.asExpr() = this.asExpr().(Subscript).getIndex() }
override DataFlow::Node getValueArg() { result = headerInput }
}
private class FlaskMakeResponseExtend extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
KeyValuePair item;
FlaskMakeResponseExtend() {
this.getFunction() = headerInstanceCall() and
item = this.getArg(_).asExpr().(Dict).getAnItem()
}
override DataFlow::Node getNameArg() { result.asExpr() = item.getKey() }
override DataFlow::Node getValueArg() { result.asExpr() = item.getValue() }
}
private class FlaskResponse extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
KeyValuePair item;
FlaskResponse() {
this = Flask::Response::classRef().getACall() and
item = this.getArg(_).asExpr().(Dict).getAnItem()
}
override DataFlow::Node getNameArg() { result.asExpr() = item.getKey() }
override DataFlow::Node getValueArg() { result.asExpr() = item.getValue() }
}
/**
* Gets a call to `set_cookie()`.
*

View File

@@ -1,33 +0,0 @@
/**
* Provides classes modeling security-relevant aspects of the `Werkzeug` PyPI package.
* See
* - https://pypi.org/project/Werkzeug/
* - https://werkzeug.palletsprojects.com/en/1.0.x/#werkzeug
*/
private import python
private import semmle.python.frameworks.Flask
private import semmle.python.dataflow.new.DataFlow
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
private module Werkzeug {
module Datastructures {
module Headers {
class WerkzeugHeaderAddCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
WerkzeugHeaderAddCall() {
this.getFunction().(DataFlow::AttrRead).getObject().getALocalSource() =
API::moduleImport("werkzeug")
.getMember("datastructures")
.getMember("Headers")
.getACall() and
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "add"
}
override DataFlow::Node getNameArg() { result = this.getArg(0) }
override DataFlow::Node getValueArg() { result = this.getArg(1) }
}
}
}
}

View File

@@ -1,21 +0,0 @@
import python
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.RemoteFlowSources
/**
* A taint-tracking configuration for detecting HTTP Header injections.
*/
private module HeaderInjectionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) {
exists(HeaderDeclaration headerDeclaration |
sink in [headerDeclaration.getNameArg(), headerDeclaration.getValueArg()]
)
}
}
/** Global taint-tracking for detecting "HTTP Header injection" vulnerabilities. */
module HeaderInjectionFlow = TaintTracking::Global<HeaderInjectionConfig>;

View File

@@ -1,5 +1,5 @@
name: codeql/python-queries
version: 0.9.16-dev
version: 1.0.2-dev
groups:
- python
- queries

View File

@@ -319,6 +319,66 @@ module HttpServerHttpResponseTest implements TestSig {
}
}
module HttpResponseHeaderWriteTest implements TestSig {
string getARelevantTag() {
result =
[
"headerWriteNameUnsanitized", "headerWriteNameSanitized", "headerWriteValueUnsanitized",
"headerWriteValueSanitized", "headerWriteBulk"
]
}
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
(
exists(Http::Server::ResponseHeaderWrite write, DataFlow::Node node |
location = node.getLocation() and
element = node.toString()
|
node = write.getNameArg() and
(
if write.nameAllowsNewline()
then tag = "headerWriteNameUnsanitized"
else tag = "headerWriteNameSanitized"
) and
value = prettyNodeForInlineTest(node)
or
node = write.getValueArg() and
(
if write.valueAllowsNewline()
then tag = "headerWriteValueUnsanitized"
else tag = "headerWriteValueSanitized"
) and
value = prettyNodeForInlineTest(node)
)
or
exists(Http::Server::ResponseHeaderBulkWrite write, DataFlow::Node node |
node = write.getBulkArg() and
location = node.getLocation() and
element = node.toString() and
(
tag = "headerWriteBulk" and
value = prettyNodeForInlineTest(node)
or
(
if write.nameAllowsNewline()
then tag = "headerWriteNameUnsanitized"
else tag = "headerWriteNameSanitized"
) and
value = ""
or
(
if write.valueAllowsNewline()
then tag = "headerWriteValueUnsanitized"
else tag = "headerWriteValueSanitized"
) and
value = ""
)
)
)
}
}
module HttpServerHttpRedirectResponseTest implements TestSig {
string getARelevantTag() { result in ["HttpRedirectResponse", "redirectLocation"] }
@@ -559,7 +619,8 @@ import MakeTest<MergeTests5<MergeTests5<SystemCommandExecutionTest, DecodingTest
MergeTests5<SqlConstructionTest, SqlExecutionTest, XPathConstructionTest, XPathExecutionTest,
EscapingTest>,
MergeTests5<HttpServerRouteSetupTest, HttpServerRequestHandlerTest, HttpServerHttpResponseTest,
HttpServerHttpRedirectResponseTest, HttpServerCookieWriteTest>,
HttpServerHttpRedirectResponseTest,
MergeTests<HttpServerCookieWriteTest, HttpResponseHeaderWriteTest>>,
MergeTests5<FileSystemAccessTest, FileSystemWriteAccessTest, PathNormalizationTest,
SafeAccessCheckTest, PublicKeyGenerationTest>,
MergeTests5<CryptographicOperationTest, HttpClientRequestTest, CsrfProtectionSettingTest,

View File

@@ -33,14 +33,14 @@ edges
| TarSlipImprov.py:141:34:141:36 | ControlFlowNode for tar | TarSlipImprov.py:142:9:142:13 | ControlFlowNode for entry | provenance | |
| TarSlipImprov.py:142:9:142:13 | ControlFlowNode for entry | TarSlipImprov.py:143:36:143:40 | ControlFlowNode for entry | provenance | |
| TarSlipImprov.py:151:14:151:50 | ControlFlowNode for closing() | TarSlipImprov.py:151:55:151:56 | ControlFlowNode for tf | provenance | |
| TarSlipImprov.py:151:22:151:49 | ControlFlowNode for Attribute() | TarSlipImprov.py:151:14:151:50 | ControlFlowNode for closing() | provenance | |
| TarSlipImprov.py:151:22:151:49 | ControlFlowNode for Attribute() | TarSlipImprov.py:151:14:151:50 | ControlFlowNode for closing() | provenance | Config |
| TarSlipImprov.py:151:55:151:56 | ControlFlowNode for tf | TarSlipImprov.py:152:19:152:20 | ControlFlowNode for tf | provenance | |
| TarSlipImprov.py:152:19:152:20 | ControlFlowNode for tf | TarSlipImprov.py:157:18:157:40 | ControlFlowNode for py2_tarxz() | provenance | |
| TarSlipImprov.py:157:9:157:14 | ControlFlowNode for tar_cm | TarSlipImprov.py:162:20:162:23 | ControlFlowNode for tarc | provenance | |
| TarSlipImprov.py:157:18:157:40 | ControlFlowNode for py2_tarxz() | TarSlipImprov.py:157:9:157:14 | ControlFlowNode for tar_cm | provenance | |
| TarSlipImprov.py:159:9:159:14 | ControlFlowNode for tar_cm | TarSlipImprov.py:162:20:162:23 | ControlFlowNode for tarc | provenance | |
| TarSlipImprov.py:159:18:159:52 | ControlFlowNode for closing() | TarSlipImprov.py:159:9:159:14 | ControlFlowNode for tar_cm | provenance | |
| TarSlipImprov.py:159:26:159:51 | ControlFlowNode for Attribute() | TarSlipImprov.py:159:18:159:52 | ControlFlowNode for closing() | provenance | |
| TarSlipImprov.py:159:26:159:51 | ControlFlowNode for Attribute() | TarSlipImprov.py:159:18:159:52 | ControlFlowNode for closing() | provenance | Config |
| TarSlipImprov.py:162:20:162:23 | ControlFlowNode for tarc | TarSlipImprov.py:169:9:169:12 | ControlFlowNode for tarc | provenance | |
| TarSlipImprov.py:176:6:176:31 | ControlFlowNode for Attribute() | TarSlipImprov.py:176:36:176:38 | ControlFlowNode for tar | provenance | |
| TarSlipImprov.py:176:36:176:38 | ControlFlowNode for tar | TarSlipImprov.py:177:9:177:13 | ControlFlowNode for entry | provenance | |

View File

@@ -3,18 +3,18 @@ edges
| UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for request | UnsafeUnpack.py:11:18:11:24 | ControlFlowNode for request | provenance | |
| UnsafeUnpack.py:11:7:11:14 | ControlFlowNode for filename | UnsafeUnpack.py:13:24:13:58 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| UnsafeUnpack.py:11:18:11:24 | ControlFlowNode for request | UnsafeUnpack.py:11:18:11:29 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| UnsafeUnpack.py:11:18:11:29 | ControlFlowNode for Attribute | UnsafeUnpack.py:11:18:11:49 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:11:18:11:29 | ControlFlowNode for Attribute | UnsafeUnpack.py:11:18:11:49 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:11:18:11:29 | ControlFlowNode for Attribute | UnsafeUnpack.py:11:18:11:49 | ControlFlowNode for Attribute() | provenance | dict.get |
| UnsafeUnpack.py:11:18:11:49 | ControlFlowNode for Attribute() | UnsafeUnpack.py:11:7:11:14 | ControlFlowNode for filename | provenance | |
| UnsafeUnpack.py:13:13:13:20 | ControlFlowNode for response | UnsafeUnpack.py:17:27:17:34 | ControlFlowNode for response | provenance | |
| UnsafeUnpack.py:13:24:13:58 | ControlFlowNode for Attribute() | UnsafeUnpack.py:13:13:13:20 | ControlFlowNode for response | provenance | |
| UnsafeUnpack.py:16:23:16:29 | ControlFlowNode for tarpath | UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | provenance | |
| UnsafeUnpack.py:17:19:17:19 | ControlFlowNode for f | UnsafeUnpack.py:16:23:16:29 | ControlFlowNode for tarpath | provenance | |
| UnsafeUnpack.py:17:27:17:34 | ControlFlowNode for response | UnsafeUnpack.py:17:27:17:38 | ControlFlowNode for Attribute | provenance | |
| UnsafeUnpack.py:17:19:17:19 | ControlFlowNode for f | UnsafeUnpack.py:16:23:16:29 | ControlFlowNode for tarpath | provenance | Config |
| UnsafeUnpack.py:17:27:17:34 | ControlFlowNode for response | UnsafeUnpack.py:17:27:17:38 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| UnsafeUnpack.py:17:27:17:38 | ControlFlowNode for Attribute | UnsafeUnpack.py:17:27:17:45 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:17:27:17:34 | ControlFlowNode for response | UnsafeUnpack.py:17:27:17:38 | ControlFlowNode for Attribute | provenance | Config |
| UnsafeUnpack.py:17:27:17:38 | ControlFlowNode for Attribute | UnsafeUnpack.py:17:27:17:45 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| UnsafeUnpack.py:17:27:17:45 | ControlFlowNode for Attribute() | UnsafeUnpack.py:17:19:17:19 | ControlFlowNode for f | provenance | |
| UnsafeUnpack.py:17:27:17:38 | ControlFlowNode for Attribute | UnsafeUnpack.py:17:27:17:45 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:17:27:17:45 | ControlFlowNode for Attribute() | UnsafeUnpack.py:17:19:17:19 | ControlFlowNode for f | provenance | Config |
| UnsafeUnpack.py:33:50:33:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path | provenance | |
| UnsafeUnpack.py:47:20:47:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file | provenance | |
| UnsafeUnpack.py:51:1:51:15 | ControlFlowNode for compressed_file | UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file | provenance | |
@@ -27,54 +27,54 @@ edges
| UnsafeUnpack.py:81:1:81:8 | ControlFlowNode for response | UnsafeUnpack.py:85:15:85:22 | ControlFlowNode for response | provenance | |
| UnsafeUnpack.py:81:12:81:50 | ControlFlowNode for Attribute() | UnsafeUnpack.py:81:1:81:8 | ControlFlowNode for response | provenance | |
| UnsafeUnpack.py:84:11:84:17 | ControlFlowNode for tarpath | UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath | provenance | |
| UnsafeUnpack.py:85:7:85:7 | ControlFlowNode for f | UnsafeUnpack.py:84:11:84:17 | ControlFlowNode for tarpath | provenance | |
| UnsafeUnpack.py:85:15:85:22 | ControlFlowNode for response | UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute | provenance | |
| UnsafeUnpack.py:85:7:85:7 | ControlFlowNode for f | UnsafeUnpack.py:84:11:84:17 | ControlFlowNode for tarpath | provenance | Config |
| UnsafeUnpack.py:85:15:85:22 | ControlFlowNode for response | UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:85:15:85:33 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:85:15:85:22 | ControlFlowNode for response | UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute | provenance | Config |
| UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:85:15:85:33 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| UnsafeUnpack.py:85:15:85:33 | ControlFlowNode for Attribute() | UnsafeUnpack.py:85:7:85:7 | ControlFlowNode for f | provenance | |
| UnsafeUnpack.py:85:15:85:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:85:15:85:33 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:85:15:85:33 | ControlFlowNode for Attribute() | UnsafeUnpack.py:85:7:85:7 | ControlFlowNode for f | provenance | Config |
| UnsafeUnpack.py:102:23:102:30 | ControlFlowNode for savepath | UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | provenance | |
| UnsafeUnpack.py:103:23:103:27 | ControlFlowNode for chunk | UnsafeUnpack.py:104:37:104:41 | ControlFlowNode for chunk | provenance | |
| UnsafeUnpack.py:103:32:103:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:103:32:103:54 | ControlFlowNode for Subscript | provenance | |
| UnsafeUnpack.py:103:32:103:54 | ControlFlowNode for Subscript | UnsafeUnpack.py:103:32:103:63 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:103:32:103:54 | ControlFlowNode for Subscript | UnsafeUnpack.py:103:32:103:63 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:103:32:103:63 | ControlFlowNode for Attribute() | UnsafeUnpack.py:103:23:103:27 | ControlFlowNode for chunk | provenance | |
| UnsafeUnpack.py:104:25:104:29 | ControlFlowNode for wfile | UnsafeUnpack.py:102:23:102:30 | ControlFlowNode for savepath | provenance | |
| UnsafeUnpack.py:104:37:104:41 | ControlFlowNode for chunk | UnsafeUnpack.py:104:25:104:29 | ControlFlowNode for wfile | provenance | |
| UnsafeUnpack.py:104:25:104:29 | ControlFlowNode for wfile | UnsafeUnpack.py:102:23:102:30 | ControlFlowNode for savepath | provenance | Config |
| UnsafeUnpack.py:104:37:104:41 | ControlFlowNode for chunk | UnsafeUnpack.py:104:25:104:29 | ControlFlowNode for wfile | provenance | AdditionalTaintStep |
| UnsafeUnpack.py:104:37:104:41 | ControlFlowNode for chunk | UnsafeUnpack.py:104:25:104:29 | ControlFlowNode for wfile | provenance | Config |
| UnsafeUnpack.py:108:13:108:18 | ControlFlowNode for myfile | UnsafeUnpack.py:111:27:111:32 | ControlFlowNode for myfile | provenance | |
| UnsafeUnpack.py:108:22:108:34 | ControlFlowNode for Attribute | UnsafeUnpack.py:108:22:108:48 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:108:22:108:34 | ControlFlowNode for Attribute | UnsafeUnpack.py:108:22:108:48 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:108:22:108:34 | ControlFlowNode for Attribute | UnsafeUnpack.py:108:22:108:48 | ControlFlowNode for Attribute() | provenance | dict.get |
| UnsafeUnpack.py:108:22:108:48 | ControlFlowNode for Attribute() | UnsafeUnpack.py:108:13:108:18 | ControlFlowNode for myfile | provenance | |
| UnsafeUnpack.py:110:18:110:26 | ControlFlowNode for file_path | UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | provenance | |
| UnsafeUnpack.py:111:19:111:19 | ControlFlowNode for f | UnsafeUnpack.py:110:18:110:26 | ControlFlowNode for file_path | provenance | |
| UnsafeUnpack.py:111:27:111:32 | ControlFlowNode for myfile | UnsafeUnpack.py:111:27:111:39 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:111:27:111:39 | ControlFlowNode for Attribute() | UnsafeUnpack.py:111:19:111:19 | ControlFlowNode for f | provenance | |
| UnsafeUnpack.py:111:19:111:19 | ControlFlowNode for f | UnsafeUnpack.py:110:18:110:26 | ControlFlowNode for file_path | provenance | Config |
| UnsafeUnpack.py:111:27:111:32 | ControlFlowNode for myfile | UnsafeUnpack.py:111:27:111:39 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:111:27:111:39 | ControlFlowNode for Attribute() | UnsafeUnpack.py:111:19:111:19 | ControlFlowNode for f | provenance | Config |
| UnsafeUnpack.py:116:17:116:21 | ControlFlowNode for ufile | UnsafeUnpack.py:118:38:118:42 | ControlFlowNode for ufile | provenance | |
| UnsafeUnpack.py:116:27:116:39 | ControlFlowNode for Attribute | UnsafeUnpack.py:116:27:116:49 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:116:27:116:39 | ControlFlowNode for Attribute | UnsafeUnpack.py:116:27:116:49 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:116:27:116:49 | ControlFlowNode for Attribute() | UnsafeUnpack.py:116:17:116:21 | ControlFlowNode for ufile | provenance | |
| UnsafeUnpack.py:118:19:118:26 | ControlFlowNode for filename | UnsafeUnpack.py:119:48:119:55 | ControlFlowNode for filename | provenance | |
| UnsafeUnpack.py:118:30:118:55 | ControlFlowNode for Attribute() | UnsafeUnpack.py:118:19:118:26 | ControlFlowNode for filename | provenance | |
| UnsafeUnpack.py:118:38:118:42 | ControlFlowNode for ufile | UnsafeUnpack.py:118:38:118:47 | ControlFlowNode for Attribute | provenance | |
| UnsafeUnpack.py:118:38:118:47 | ControlFlowNode for Attribute | UnsafeUnpack.py:118:30:118:55 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:118:38:118:42 | ControlFlowNode for ufile | UnsafeUnpack.py:118:38:118:47 | ControlFlowNode for Attribute | provenance | Config |
| UnsafeUnpack.py:118:38:118:47 | ControlFlowNode for Attribute | UnsafeUnpack.py:118:30:118:55 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:119:19:119:36 | ControlFlowNode for uploaded_file_path | UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | provenance | |
| UnsafeUnpack.py:119:40:119:56 | ControlFlowNode for Attribute() | UnsafeUnpack.py:119:19:119:36 | ControlFlowNode for uploaded_file_path | provenance | |
| UnsafeUnpack.py:119:48:119:55 | ControlFlowNode for filename | UnsafeUnpack.py:119:40:119:56 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:119:48:119:55 | ControlFlowNode for filename | UnsafeUnpack.py:119:40:119:56 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:140:1:140:19 | ControlFlowNode for unsafe_filename_tar | UnsafeUnpack.py:141:22:141:40 | ControlFlowNode for unsafe_filename_tar | provenance | |
| UnsafeUnpack.py:140:23:140:35 | ControlFlowNode for Attribute | UnsafeUnpack.py:140:1:140:19 | ControlFlowNode for unsafe_filename_tar | provenance | |
| UnsafeUnpack.py:141:6:141:51 | ControlFlowNode for Attribute() | UnsafeUnpack.py:141:56:141:58 | ControlFlowNode for tar | provenance | |
| UnsafeUnpack.py:141:22:141:40 | ControlFlowNode for unsafe_filename_tar | UnsafeUnpack.py:141:6:141:51 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:141:22:141:40 | ControlFlowNode for unsafe_filename_tar | UnsafeUnpack.py:141:6:141:51 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:141:56:141:58 | ControlFlowNode for tar | UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar | provenance | |
| UnsafeUnpack.py:157:23:157:30 | ControlFlowNode for savepath | UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | provenance | |
| UnsafeUnpack.py:158:23:158:27 | ControlFlowNode for chunk | UnsafeUnpack.py:159:37:159:41 | ControlFlowNode for chunk | provenance | |
| UnsafeUnpack.py:158:32:158:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:158:32:158:54 | ControlFlowNode for Subscript | provenance | |
| UnsafeUnpack.py:158:32:158:54 | ControlFlowNode for Subscript | UnsafeUnpack.py:158:32:158:63 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:158:32:158:54 | ControlFlowNode for Subscript | UnsafeUnpack.py:158:32:158:63 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:158:32:158:63 | ControlFlowNode for Attribute() | UnsafeUnpack.py:158:23:158:27 | ControlFlowNode for chunk | provenance | |
| UnsafeUnpack.py:159:25:159:29 | ControlFlowNode for wfile | UnsafeUnpack.py:157:23:157:30 | ControlFlowNode for savepath | provenance | |
| UnsafeUnpack.py:159:37:159:41 | ControlFlowNode for chunk | UnsafeUnpack.py:159:25:159:29 | ControlFlowNode for wfile | provenance | |
| UnsafeUnpack.py:159:25:159:29 | ControlFlowNode for wfile | UnsafeUnpack.py:157:23:157:30 | ControlFlowNode for savepath | provenance | Config |
| UnsafeUnpack.py:159:37:159:41 | ControlFlowNode for chunk | UnsafeUnpack.py:159:25:159:29 | ControlFlowNode for wfile | provenance | AdditionalTaintStep |
| UnsafeUnpack.py:159:37:159:41 | ControlFlowNode for chunk | UnsafeUnpack.py:159:25:159:29 | ControlFlowNode for wfile | provenance | Config |
| UnsafeUnpack.py:161:19:161:21 | ControlFlowNode for tar | UnsafeUnpack.py:163:33:163:35 | ControlFlowNode for tar | provenance | |
| UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | UnsafeUnpack.py:161:19:161:21 | ControlFlowNode for tar | provenance | |
| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:163:23:163:28 | ControlFlowNode for member | UnsafeUnpack.py:166:37:166:42 | ControlFlowNode for member | provenance | |
| UnsafeUnpack.py:163:33:163:35 | ControlFlowNode for tar | UnsafeUnpack.py:163:23:163:28 | ControlFlowNode for member | provenance | |
| UnsafeUnpack.py:166:23:166:28 | [post] ControlFlowNode for result | UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | provenance | |
@@ -82,15 +82,15 @@ edges
| UnsafeUnpack.py:171:1:171:8 | ControlFlowNode for response | UnsafeUnpack.py:174:15:174:22 | ControlFlowNode for response | provenance | |
| UnsafeUnpack.py:171:12:171:50 | ControlFlowNode for Attribute() | UnsafeUnpack.py:171:1:171:8 | ControlFlowNode for response | provenance | |
| UnsafeUnpack.py:173:11:173:17 | ControlFlowNode for tarpath | UnsafeUnpack.py:176:17:176:23 | ControlFlowNode for tarpath | provenance | |
| UnsafeUnpack.py:174:7:174:7 | ControlFlowNode for f | UnsafeUnpack.py:173:11:173:17 | ControlFlowNode for tarpath | provenance | |
| UnsafeUnpack.py:174:15:174:22 | ControlFlowNode for response | UnsafeUnpack.py:174:15:174:26 | ControlFlowNode for Attribute | provenance | |
| UnsafeUnpack.py:174:7:174:7 | ControlFlowNode for f | UnsafeUnpack.py:173:11:173:17 | ControlFlowNode for tarpath | provenance | Config |
| UnsafeUnpack.py:174:15:174:22 | ControlFlowNode for response | UnsafeUnpack.py:174:15:174:26 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| UnsafeUnpack.py:174:15:174:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:174:15:174:33 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:174:15:174:22 | ControlFlowNode for response | UnsafeUnpack.py:174:15:174:26 | ControlFlowNode for Attribute | provenance | Config |
| UnsafeUnpack.py:174:15:174:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:174:15:174:33 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| UnsafeUnpack.py:174:15:174:33 | ControlFlowNode for Attribute() | UnsafeUnpack.py:174:7:174:7 | ControlFlowNode for f | provenance | |
| UnsafeUnpack.py:176:17:176:23 | ControlFlowNode for tarpath | UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | provenance | |
| UnsafeUnpack.py:174:15:174:26 | ControlFlowNode for Attribute | UnsafeUnpack.py:174:15:174:33 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:174:15:174:33 | ControlFlowNode for Attribute() | UnsafeUnpack.py:174:7:174:7 | ControlFlowNode for f | provenance | Config |
| UnsafeUnpack.py:176:17:176:23 | ControlFlowNode for tarpath | UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | provenance | Config |
| UnsafeUnpack.py:194:53:194:55 | ControlFlowNode for tmp | UnsafeUnpack.py:201:29:201:31 | ControlFlowNode for tmp | provenance | |
| UnsafeUnpack.py:201:29:201:31 | ControlFlowNode for tmp | UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | provenance | |
| UnsafeUnpack.py:201:29:201:31 | ControlFlowNode for tmp | UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | provenance | Config |
nodes
| UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
| UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |

View File

@@ -0,0 +1,18 @@
edges
| paramiko.py:15:21:15:23 | ControlFlowNode for cmd | paramiko.py:16:62:16:64 | ControlFlowNode for cmd | provenance | |
| paramiko.py:20:21:20:23 | ControlFlowNode for cmd | paramiko.py:21:70:21:72 | ControlFlowNode for cmd | provenance | |
| paramiko.py:25:21:25:23 | ControlFlowNode for cmd | paramiko.py:26:136:26:138 | ControlFlowNode for cmd | provenance | |
| paramiko.py:26:136:26:138 | ControlFlowNode for cmd | paramiko.py:26:114:26:139 | ControlFlowNode for Attribute() | provenance | Config |
nodes
| paramiko.py:15:21:15:23 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| paramiko.py:16:62:16:64 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| paramiko.py:20:21:20:23 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| paramiko.py:21:70:21:72 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| paramiko.py:25:21:25:23 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| paramiko.py:26:114:26:139 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| paramiko.py:26:136:26:138 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
subpaths
#select
| paramiko.py:16:62:16:64 | ControlFlowNode for cmd | paramiko.py:15:21:15:23 | ControlFlowNode for cmd | paramiko.py:16:62:16:64 | ControlFlowNode for cmd | This code execution depends on a $@. | paramiko.py:15:21:15:23 | ControlFlowNode for cmd | a user-provided value |
| paramiko.py:21:70:21:72 | ControlFlowNode for cmd | paramiko.py:20:21:20:23 | ControlFlowNode for cmd | paramiko.py:21:70:21:72 | ControlFlowNode for cmd | This code execution depends on a $@. | paramiko.py:20:21:20:23 | ControlFlowNode for cmd | a user-provided value |
| paramiko.py:26:114:26:139 | ControlFlowNode for Attribute() | paramiko.py:25:21:25:23 | ControlFlowNode for cmd | paramiko.py:26:114:26:139 | ControlFlowNode for Attribute() | This code execution depends on a $@. | paramiko.py:25:21:25:23 | ControlFlowNode for cmd | a user-provided value |

View File

@@ -13,7 +13,7 @@ edges
| sendgrid_mail.py:1:19:1:25 | ControlFlowNode for request | sendgrid_mail.py:37:41:37:47 | ControlFlowNode for request | provenance | |
| sendgrid_mail.py:14:22:14:28 | ControlFlowNode for request | sendgrid_mail.py:14:22:14:49 | ControlFlowNode for Subscript | provenance | AdditionalTaintStep |
| sendgrid_mail.py:26:34:26:40 | ControlFlowNode for request | sendgrid_mail.py:26:34:26:61 | ControlFlowNode for Subscript | provenance | AdditionalTaintStep |
| sendgrid_mail.py:26:34:26:61 | ControlFlowNode for Subscript | sendgrid_mail.py:26:22:26:62 | ControlFlowNode for HtmlContent() | provenance | |
| sendgrid_mail.py:26:34:26:61 | ControlFlowNode for Subscript | sendgrid_mail.py:26:22:26:62 | ControlFlowNode for HtmlContent() | provenance | Config |
| sendgrid_mail.py:37:41:37:47 | ControlFlowNode for request | sendgrid_mail.py:37:41:37:68 | ControlFlowNode for Subscript | provenance | AdditionalTaintStep |
| sendgrid_via_mail_send_post_request_body_bad.py:3:19:3:25 | ControlFlowNode for ImportMember | sendgrid_via_mail_send_post_request_body_bad.py:3:19:3:25 | ControlFlowNode for request | provenance | |
| sendgrid_via_mail_send_post_request_body_bad.py:3:19:3:25 | ControlFlowNode for request | sendgrid_via_mail_send_post_request_body_bad.py:16:51:16:57 | ControlFlowNode for request | provenance | |

View File

@@ -7,8 +7,8 @@ edges
| xslt.py:10:17:10:43 | ControlFlowNode for Attribute() | xslt.py:10:5:10:13 | ControlFlowNode for xsltQuery | provenance | |
| xslt.py:11:5:11:13 | ControlFlowNode for xslt_root | xslt.py:14:29:14:37 | ControlFlowNode for xslt_root | provenance | |
| xslt.py:11:17:11:36 | ControlFlowNode for Attribute() | xslt.py:11:5:11:13 | ControlFlowNode for xslt_root | provenance | |
| xslt.py:11:27:11:35 | ControlFlowNode for xsltQuery | xslt.py:11:17:11:36 | ControlFlowNode for Attribute() | provenance | |
| xslt.py:11:27:11:35 | ControlFlowNode for xsltQuery | xslt.py:11:17:11:36 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| xslt.py:11:27:11:35 | ControlFlowNode for xsltQuery | xslt.py:11:17:11:36 | ControlFlowNode for Attribute() | provenance | Config |
| xslt.py:11:27:11:35 | ControlFlowNode for xsltQuery | xslt.py:11:17:11:36 | ControlFlowNode for Attribute() | provenance | Decoding-XML |
| xsltInjection.py:3:26:3:32 | ControlFlowNode for ImportMember | xsltInjection.py:3:26:3:32 | ControlFlowNode for request | provenance | |
| xsltInjection.py:3:26:3:32 | ControlFlowNode for request | xsltInjection.py:10:17:10:23 | ControlFlowNode for request | provenance | |
| xsltInjection.py:3:26:3:32 | ControlFlowNode for request | xsltInjection.py:17:17:17:23 | ControlFlowNode for request | provenance | |
@@ -21,32 +21,32 @@ edges
| xsltInjection.py:10:17:10:43 | ControlFlowNode for Attribute() | xsltInjection.py:10:5:10:13 | ControlFlowNode for xsltQuery | provenance | |
| xsltInjection.py:11:5:11:13 | ControlFlowNode for xslt_root | xsltInjection.py:12:28:12:36 | ControlFlowNode for xslt_root | provenance | |
| xsltInjection.py:11:17:11:36 | ControlFlowNode for Attribute() | xsltInjection.py:11:5:11:13 | ControlFlowNode for xslt_root | provenance | |
| xsltInjection.py:11:27:11:35 | ControlFlowNode for xsltQuery | xsltInjection.py:11:17:11:36 | ControlFlowNode for Attribute() | provenance | |
| xsltInjection.py:11:27:11:35 | ControlFlowNode for xsltQuery | xsltInjection.py:11:17:11:36 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| xsltInjection.py:11:27:11:35 | ControlFlowNode for xsltQuery | xsltInjection.py:11:17:11:36 | ControlFlowNode for Attribute() | provenance | Config |
| xsltInjection.py:11:27:11:35 | ControlFlowNode for xsltQuery | xsltInjection.py:11:17:11:36 | ControlFlowNode for Attribute() | provenance | Decoding-XML |
| xsltInjection.py:17:5:17:13 | ControlFlowNode for xsltQuery | xsltInjection.py:18:27:18:35 | ControlFlowNode for xsltQuery | provenance | |
| xsltInjection.py:17:17:17:23 | ControlFlowNode for request | xsltInjection.py:17:17:17:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| xsltInjection.py:17:17:17:28 | ControlFlowNode for Attribute | xsltInjection.py:17:17:17:43 | ControlFlowNode for Attribute() | provenance | dict.get |
| xsltInjection.py:17:17:17:43 | ControlFlowNode for Attribute() | xsltInjection.py:17:5:17:13 | ControlFlowNode for xsltQuery | provenance | |
| xsltInjection.py:18:5:18:13 | ControlFlowNode for xslt_root | xsltInjection.py:21:29:21:37 | ControlFlowNode for xslt_root | provenance | |
| xsltInjection.py:18:17:18:36 | ControlFlowNode for Attribute() | xsltInjection.py:18:5:18:13 | ControlFlowNode for xslt_root | provenance | |
| xsltInjection.py:18:27:18:35 | ControlFlowNode for xsltQuery | xsltInjection.py:18:17:18:36 | ControlFlowNode for Attribute() | provenance | |
| xsltInjection.py:18:27:18:35 | ControlFlowNode for xsltQuery | xsltInjection.py:18:17:18:36 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| xsltInjection.py:18:27:18:35 | ControlFlowNode for xsltQuery | xsltInjection.py:18:17:18:36 | ControlFlowNode for Attribute() | provenance | Config |
| xsltInjection.py:18:27:18:35 | ControlFlowNode for xsltQuery | xsltInjection.py:18:17:18:36 | ControlFlowNode for Attribute() | provenance | Decoding-XML |
| xsltInjection.py:26:5:26:13 | ControlFlowNode for xsltQuery | xsltInjection.py:27:27:27:35 | ControlFlowNode for xsltQuery | provenance | |
| xsltInjection.py:26:17:26:23 | ControlFlowNode for request | xsltInjection.py:26:17:26:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| xsltInjection.py:26:17:26:28 | ControlFlowNode for Attribute | xsltInjection.py:26:17:26:43 | ControlFlowNode for Attribute() | provenance | dict.get |
| xsltInjection.py:26:17:26:43 | ControlFlowNode for Attribute() | xsltInjection.py:26:5:26:13 | ControlFlowNode for xsltQuery | provenance | |
| xsltInjection.py:27:5:27:13 | ControlFlowNode for xslt_root | xsltInjection.py:31:24:31:32 | ControlFlowNode for xslt_root | provenance | |
| xsltInjection.py:27:17:27:36 | ControlFlowNode for Attribute() | xsltInjection.py:27:5:27:13 | ControlFlowNode for xslt_root | provenance | |
| xsltInjection.py:27:27:27:35 | ControlFlowNode for xsltQuery | xsltInjection.py:27:17:27:36 | ControlFlowNode for Attribute() | provenance | |
| xsltInjection.py:27:27:27:35 | ControlFlowNode for xsltQuery | xsltInjection.py:27:17:27:36 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| xsltInjection.py:27:27:27:35 | ControlFlowNode for xsltQuery | xsltInjection.py:27:17:27:36 | ControlFlowNode for Attribute() | provenance | Config |
| xsltInjection.py:27:27:27:35 | ControlFlowNode for xsltQuery | xsltInjection.py:27:17:27:36 | ControlFlowNode for Attribute() | provenance | Decoding-XML |
| xsltInjection.py:35:5:35:13 | ControlFlowNode for xsltQuery | xsltInjection.py:36:34:36:42 | ControlFlowNode for xsltQuery | provenance | |
| xsltInjection.py:35:17:35:23 | ControlFlowNode for request | xsltInjection.py:35:17:35:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| xsltInjection.py:35:17:35:28 | ControlFlowNode for Attribute | xsltInjection.py:35:17:35:43 | ControlFlowNode for Attribute() | provenance | dict.get |
| xsltInjection.py:35:17:35:43 | ControlFlowNode for Attribute() | xsltInjection.py:35:5:35:13 | ControlFlowNode for xsltQuery | provenance | |
| xsltInjection.py:36:5:36:13 | ControlFlowNode for xslt_root | xsltInjection.py:40:24:40:32 | ControlFlowNode for xslt_root | provenance | |
| xsltInjection.py:36:17:36:43 | ControlFlowNode for Attribute() | xsltInjection.py:36:5:36:13 | ControlFlowNode for xslt_root | provenance | |
| xsltInjection.py:36:34:36:42 | ControlFlowNode for xsltQuery | xsltInjection.py:36:17:36:43 | ControlFlowNode for Attribute() | provenance | |
| xsltInjection.py:36:34:36:42 | ControlFlowNode for xsltQuery | xsltInjection.py:36:17:36:43 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| xsltInjection.py:36:34:36:42 | ControlFlowNode for xsltQuery | xsltInjection.py:36:17:36:43 | ControlFlowNode for Attribute() | provenance | Config |
| xsltInjection.py:36:34:36:42 | ControlFlowNode for xsltQuery | xsltInjection.py:36:17:36:43 | ControlFlowNode for Attribute() | provenance | Decoding-XML |
| xsltInjection.py:44:5:44:13 | ControlFlowNode for xsltQuery | xsltInjection.py:45:5:45:15 | ControlFlowNode for xsltStrings | provenance | |
| xsltInjection.py:44:17:44:23 | ControlFlowNode for request | xsltInjection.py:44:17:44:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| xsltInjection.py:44:17:44:28 | ControlFlowNode for Attribute | xsltInjection.py:44:17:44:43 | ControlFlowNode for Attribute() | provenance | dict.get |
@@ -54,8 +54,8 @@ edges
| xsltInjection.py:45:5:45:15 | ControlFlowNode for xsltStrings | xsltInjection.py:46:38:46:48 | ControlFlowNode for xsltStrings | provenance | |
| xsltInjection.py:46:5:46:13 | ControlFlowNode for xslt_root | xsltInjection.py:50:24:50:32 | ControlFlowNode for xslt_root | provenance | |
| xsltInjection.py:46:17:46:49 | ControlFlowNode for Attribute() | xsltInjection.py:46:5:46:13 | ControlFlowNode for xslt_root | provenance | |
| xsltInjection.py:46:38:46:48 | ControlFlowNode for xsltStrings | xsltInjection.py:46:17:46:49 | ControlFlowNode for Attribute() | provenance | |
| xsltInjection.py:46:38:46:48 | ControlFlowNode for xsltStrings | xsltInjection.py:46:17:46:49 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| xsltInjection.py:46:38:46:48 | ControlFlowNode for xsltStrings | xsltInjection.py:46:17:46:49 | ControlFlowNode for Attribute() | provenance | Config |
| xsltInjection.py:46:38:46:48 | ControlFlowNode for xsltStrings | xsltInjection.py:46:17:46:49 | ControlFlowNode for Attribute() | provenance | Decoding-XML |
nodes
| xslt.py:3:26:3:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
| xslt.py:3:26:3:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |

View File

@@ -1,47 +0,0 @@
edges
| django_bad.py:5:5:5:14 | ControlFlowNode for rfs_header | django_bad.py:7:40:7:49 | ControlFlowNode for rfs_header | provenance | |
| django_bad.py:5:18:5:58 | ControlFlowNode for Attribute() | django_bad.py:5:5:5:14 | ControlFlowNode for rfs_header | provenance | |
| django_bad.py:12:5:12:14 | ControlFlowNode for rfs_header | django_bad.py:14:30:14:39 | ControlFlowNode for rfs_header | provenance | |
| django_bad.py:12:18:12:58 | ControlFlowNode for Attribute() | django_bad.py:12:5:12:14 | ControlFlowNode for rfs_header | provenance | |
| flask_bad.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_bad.py:1:29:1:35 | ControlFlowNode for request | provenance | |
| flask_bad.py:1:29:1:35 | ControlFlowNode for request | flask_bad.py:9:18:9:24 | ControlFlowNode for request | provenance | |
| flask_bad.py:1:29:1:35 | ControlFlowNode for request | flask_bad.py:19:18:19:24 | ControlFlowNode for request | provenance | |
| flask_bad.py:1:29:1:35 | ControlFlowNode for request | flask_bad.py:27:18:27:24 | ControlFlowNode for request | provenance | |
| flask_bad.py:1:29:1:35 | ControlFlowNode for request | flask_bad.py:35:18:35:24 | ControlFlowNode for request | provenance | |
| flask_bad.py:9:5:9:14 | ControlFlowNode for rfs_header | flask_bad.py:12:31:12:40 | ControlFlowNode for rfs_header | provenance | |
| flask_bad.py:9:18:9:24 | ControlFlowNode for request | flask_bad.py:9:5:9:14 | ControlFlowNode for rfs_header | provenance | AdditionalTaintStep |
| flask_bad.py:19:5:19:14 | ControlFlowNode for rfs_header | flask_bad.py:21:38:21:47 | ControlFlowNode for rfs_header | provenance | |
| flask_bad.py:19:18:19:24 | ControlFlowNode for request | flask_bad.py:19:5:19:14 | ControlFlowNode for rfs_header | provenance | AdditionalTaintStep |
| flask_bad.py:27:5:27:14 | ControlFlowNode for rfs_header | flask_bad.py:29:34:29:43 | ControlFlowNode for rfs_header | provenance | |
| flask_bad.py:27:18:27:24 | ControlFlowNode for request | flask_bad.py:27:5:27:14 | ControlFlowNode for rfs_header | provenance | AdditionalTaintStep |
| flask_bad.py:35:5:35:14 | ControlFlowNode for rfs_header | flask_bad.py:38:24:38:33 | ControlFlowNode for rfs_header | provenance | |
| flask_bad.py:35:18:35:24 | ControlFlowNode for request | flask_bad.py:35:5:35:14 | ControlFlowNode for rfs_header | provenance | AdditionalTaintStep |
nodes
| django_bad.py:5:5:5:14 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| django_bad.py:5:18:5:58 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| django_bad.py:7:40:7:49 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| django_bad.py:12:5:12:14 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| django_bad.py:12:18:12:58 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| django_bad.py:14:30:14:39 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_bad.py:1:29:1:35 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
| flask_bad.py:1:29:1:35 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_bad.py:9:5:9:14 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_bad.py:9:18:9:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_bad.py:12:31:12:40 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_bad.py:19:5:19:14 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_bad.py:19:18:19:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_bad.py:21:38:21:47 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_bad.py:27:5:27:14 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_bad.py:27:18:27:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_bad.py:29:34:29:43 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_bad.py:35:5:35:14 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_bad.py:35:18:35:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_bad.py:38:24:38:33 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
subpaths
#select
| django_bad.py:7:40:7:49 | ControlFlowNode for rfs_header | django_bad.py:5:18:5:58 | ControlFlowNode for Attribute() | django_bad.py:7:40:7:49 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | django_bad.py:5:18:5:58 | ControlFlowNode for Attribute() | user-provided value |
| django_bad.py:14:30:14:39 | ControlFlowNode for rfs_header | django_bad.py:12:18:12:58 | ControlFlowNode for Attribute() | django_bad.py:14:30:14:39 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | django_bad.py:12:18:12:58 | ControlFlowNode for Attribute() | user-provided value |
| flask_bad.py:12:31:12:40 | ControlFlowNode for rfs_header | flask_bad.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_bad.py:12:31:12:40 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_bad.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value |
| flask_bad.py:21:38:21:47 | ControlFlowNode for rfs_header | flask_bad.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_bad.py:21:38:21:47 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_bad.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value |
| flask_bad.py:29:34:29:43 | ControlFlowNode for rfs_header | flask_bad.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_bad.py:29:34:29:43 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_bad.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value |
| flask_bad.py:38:24:38:33 | ControlFlowNode for rfs_header | flask_bad.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_bad.py:38:24:38:33 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_bad.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value |

View File

@@ -1 +0,0 @@
experimental/Security/CWE-113/HeaderInjection.ql

View File

@@ -1,15 +0,0 @@
import django.http
def django_setitem():
rfs_header = django.http.request.GET.get("rfs_header")
response = django.http.HttpResponse()
response.__setitem__('HeaderName', rfs_header)
return response
def django_response():
rfs_header = django.http.request.GET.get("rfs_header")
response = django.http.HttpResponse()
response['HeaderName'] = rfs_header
return response

View File

@@ -1,47 +0,0 @@
from flask import Response, request, Flask, make_response
from werkzeug.datastructures import Headers
app = Flask(__name__)
@app.route('/werkzeug_headers')
def werkzeug_headers():
rfs_header = request.args["rfs_header"]
response = Response()
headers = Headers()
headers.add("HeaderName", rfs_header)
response.headers = headers
return response
@app.route("/flask_Response")
def flask_Response():
rfs_header = request.args["rfs_header"]
response = Response()
response.headers['HeaderName'] = rfs_header
return response
@app.route("/flask_make_response")
def flask_make_response():
rfs_header = request.args["rfs_header"]
resp = make_response("hello")
resp.headers['HeaderName'] = rfs_header
return resp
@app.route("/flask_make_response_extend")
def flask_make_response_extend():
rfs_header = request.args["rfs_header"]
resp = make_response("hello")
resp.headers.extend(
{'HeaderName': rfs_header})
return resp
@app.route("/Response_arg")
def Response_arg():
return Response(headers={'HeaderName': request.args["rfs_header"]})
# if __name__ == "__main__":
# app.run(debug=True)

View File

@@ -6,12 +6,12 @@ edges
| samples.py:9:18:9:47 | ControlFlowNode for escape() | samples.py:9:5:9:14 | ControlFlowNode for user_input | provenance | |
| samples.py:9:25:9:31 | ControlFlowNode for request | samples.py:9:25:9:36 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| samples.py:9:25:9:36 | ControlFlowNode for Attribute | samples.py:9:25:9:46 | ControlFlowNode for Attribute() | provenance | dict.get |
| samples.py:9:25:9:46 | ControlFlowNode for Attribute() | samples.py:9:18:9:47 | ControlFlowNode for escape() | provenance | |
| samples.py:9:25:9:46 | ControlFlowNode for Attribute() | samples.py:9:18:9:47 | ControlFlowNode for escape() | provenance | Config |
| samples.py:16:5:16:14 | ControlFlowNode for user_input | samples.py:20:62:20:71 | ControlFlowNode for user_input | provenance | |
| samples.py:16:18:16:47 | ControlFlowNode for escape() | samples.py:16:5:16:14 | ControlFlowNode for user_input | provenance | |
| samples.py:16:25:16:31 | ControlFlowNode for request | samples.py:16:25:16:36 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| samples.py:16:25:16:36 | ControlFlowNode for Attribute | samples.py:16:25:16:46 | ControlFlowNode for Attribute() | provenance | dict.get |
| samples.py:16:25:16:46 | ControlFlowNode for Attribute() | samples.py:16:18:16:47 | ControlFlowNode for escape() | provenance | |
| samples.py:16:25:16:46 | ControlFlowNode for Attribute() | samples.py:16:18:16:47 | ControlFlowNode for escape() | provenance | Config |
nodes
| samples.py:2:26:2:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
| samples.py:2:26:2:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |

View File

@@ -4,38 +4,38 @@ edges
| test.py:3:1:3:3 | ControlFlowNode for BSC | test.py:66:19:66:21 | ControlFlowNode for BSC | provenance | |
| test.py:3:7:3:51 | ControlFlowNode for Attribute() | test.py:3:1:3:3 | ControlFlowNode for BSC | provenance | |
| test.py:7:5:7:15 | ControlFlowNode for blob_client | test.py:8:5:8:15 | ControlFlowNode for blob_client | provenance | |
| test.py:7:19:7:21 | ControlFlowNode for BSC | test.py:7:19:7:42 | ControlFlowNode for Attribute() | provenance | |
| test.py:7:19:7:21 | ControlFlowNode for BSC | test.py:7:19:7:42 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:7:19:7:42 | ControlFlowNode for Attribute() | test.py:7:5:7:15 | ControlFlowNode for blob_client | provenance | |
| test.py:8:5:8:15 | ControlFlowNode for blob_client | test.py:9:5:9:15 | ControlFlowNode for blob_client | provenance | |
| test.py:9:5:9:15 | ControlFlowNode for blob_client | test.py:9:5:9:15 | [post] ControlFlowNode for blob_client | provenance | |
| test.py:9:5:9:15 | ControlFlowNode for blob_client | test.py:9:5:9:15 | [post] ControlFlowNode for blob_client | provenance | Config |
| test.py:9:5:9:15 | [post] ControlFlowNode for blob_client | test.py:11:9:11:19 | ControlFlowNode for blob_client | provenance | |
| test.py:15:5:15:23 | ControlFlowNode for blob_service_client | test.py:16:5:16:23 | ControlFlowNode for blob_service_client | provenance | |
| test.py:15:27:15:71 | ControlFlowNode for Attribute() | test.py:15:5:15:23 | ControlFlowNode for blob_service_client | provenance | |
| test.py:16:5:16:23 | ControlFlowNode for blob_service_client | test.py:17:5:17:23 | ControlFlowNode for blob_service_client | provenance | |
| test.py:17:5:17:23 | ControlFlowNode for blob_service_client | test.py:17:5:17:23 | [post] ControlFlowNode for blob_service_client | provenance | |
| test.py:17:5:17:23 | ControlFlowNode for blob_service_client | test.py:17:5:17:23 | [post] ControlFlowNode for blob_service_client | provenance | Config |
| test.py:17:5:17:23 | [post] ControlFlowNode for blob_service_client | test.py:19:19:19:37 | ControlFlowNode for blob_service_client | provenance | |
| test.py:19:5:19:15 | ControlFlowNode for blob_client | test.py:21:9:21:19 | ControlFlowNode for blob_client | provenance | |
| test.py:19:19:19:37 | ControlFlowNode for blob_service_client | test.py:19:19:19:58 | ControlFlowNode for Attribute() | provenance | |
| test.py:19:19:19:37 | ControlFlowNode for blob_service_client | test.py:19:19:19:58 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:19:19:19:58 | ControlFlowNode for Attribute() | test.py:19:5:19:15 | ControlFlowNode for blob_client | provenance | |
| test.py:25:5:25:20 | ControlFlowNode for container_client | test.py:26:5:26:20 | ControlFlowNode for container_client | provenance | |
| test.py:25:24:25:66 | ControlFlowNode for Attribute() | test.py:25:5:25:20 | ControlFlowNode for container_client | provenance | |
| test.py:26:5:26:20 | ControlFlowNode for container_client | test.py:27:5:27:20 | ControlFlowNode for container_client | provenance | |
| test.py:27:5:27:20 | ControlFlowNode for container_client | test.py:27:5:27:20 | [post] ControlFlowNode for container_client | provenance | |
| test.py:27:5:27:20 | ControlFlowNode for container_client | test.py:27:5:27:20 | [post] ControlFlowNode for container_client | provenance | Config |
| test.py:27:5:27:20 | [post] ControlFlowNode for container_client | test.py:29:19:29:34 | ControlFlowNode for container_client | provenance | |
| test.py:29:5:29:15 | ControlFlowNode for blob_client | test.py:31:9:31:19 | ControlFlowNode for blob_client | provenance | |
| test.py:29:19:29:34 | ControlFlowNode for container_client | test.py:29:19:29:55 | ControlFlowNode for Attribute() | provenance | |
| test.py:29:19:29:34 | ControlFlowNode for container_client | test.py:29:19:29:55 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:29:19:29:55 | ControlFlowNode for Attribute() | test.py:29:5:29:15 | ControlFlowNode for blob_client | provenance | |
| test.py:35:5:35:15 | ControlFlowNode for blob_client | test.py:36:5:36:15 | ControlFlowNode for blob_client | provenance | |
| test.py:35:19:35:21 | ControlFlowNode for BSC | test.py:35:19:35:42 | ControlFlowNode for Attribute() | provenance | |
| test.py:35:19:35:21 | ControlFlowNode for BSC | test.py:35:19:35:42 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:35:19:35:42 | ControlFlowNode for Attribute() | test.py:35:5:35:15 | ControlFlowNode for blob_client | provenance | |
| test.py:36:5:36:15 | ControlFlowNode for blob_client | test.py:37:5:37:15 | ControlFlowNode for blob_client | provenance | |
| test.py:37:5:37:15 | ControlFlowNode for blob_client | test.py:37:5:37:15 | [post] ControlFlowNode for blob_client | provenance | |
| test.py:37:5:37:15 | ControlFlowNode for blob_client | test.py:37:5:37:15 | [post] ControlFlowNode for blob_client | provenance | Config |
| test.py:37:5:37:15 | [post] ControlFlowNode for blob_client | test.py:43:9:43:19 | ControlFlowNode for blob_client | provenance | |
| test.py:66:5:66:15 | ControlFlowNode for blob_client | test.py:67:5:67:15 | ControlFlowNode for blob_client | provenance | |
| test.py:66:19:66:21 | ControlFlowNode for BSC | test.py:66:19:66:42 | ControlFlowNode for Attribute() | provenance | |
| test.py:66:19:66:21 | ControlFlowNode for BSC | test.py:66:19:66:42 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:66:19:66:42 | ControlFlowNode for Attribute() | test.py:66:5:66:15 | ControlFlowNode for blob_client | provenance | |
| test.py:67:5:67:15 | ControlFlowNode for blob_client | test.py:68:5:68:15 | ControlFlowNode for blob_client | provenance | |
| test.py:68:5:68:15 | ControlFlowNode for blob_client | test.py:68:5:68:15 | [post] ControlFlowNode for blob_client | provenance | |
| test.py:68:5:68:15 | ControlFlowNode for blob_client | test.py:68:5:68:15 | [post] ControlFlowNode for blob_client | provenance | Config |
| test.py:68:5:68:15 | [post] ControlFlowNode for blob_client | test.py:69:12:69:22 | ControlFlowNode for blob_client | provenance | |
| test.py:69:12:69:22 | ControlFlowNode for blob_client | test.py:73:10:73:33 | ControlFlowNode for get_unsafe_blob_client() | provenance | |
| test.py:73:5:73:6 | ControlFlowNode for bc | test.py:75:9:75:10 | ControlFlowNode for bc | provenance | |

View File

@@ -1,24 +1,24 @@
edges
| test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:11:21:11:29 | ControlFlowNode for file_path | provenance | |
| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:52 | ControlFlowNode for Attribute() | provenance | |
| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:52 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:12:21:12:29 | ControlFlowNode for file_path | provenance | |
| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:48 | ControlFlowNode for Attribute() | provenance | |
| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:48 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:14:26:14:34 | ControlFlowNode for file_path | provenance | |
| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:15:14:15:29 | ControlFlowNode for Attribute() | provenance | |
| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:15:14:15:29 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:18:26:18:34 | ControlFlowNode for file_path | provenance | |
| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:19:14:19:39 | ControlFlowNode for Attribute() | provenance | |
| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:19:14:19:39 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:22:21:22:29 | ControlFlowNode for file_path | provenance | |
| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:60 | ControlFlowNode for Attribute() | provenance | |
| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:60 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:24:18:24:26 | ControlFlowNode for file_path | provenance | |
| test.py:24:18:24:26 | ControlFlowNode for file_path | test.py:24:5:24:52 | ControlFlowNode for Attribute() | provenance | |
| test.py:24:18:24:26 | ControlFlowNode for file_path | test.py:24:5:24:52 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:24:18:24:26 | ControlFlowNode for file_path | test.py:25:26:25:34 | ControlFlowNode for file_path | provenance | |
| test.py:25:26:25:34 | ControlFlowNode for file_path | test.py:25:5:25:55 | ControlFlowNode for Attribute() | provenance | |
| test.py:25:26:25:34 | ControlFlowNode for file_path | test.py:25:5:25:55 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:25:26:25:34 | ControlFlowNode for file_path | test.py:26:28:26:36 | ControlFlowNode for file_path | provenance | |
| test.py:26:28:26:36 | ControlFlowNode for file_path | test.py:26:5:26:57 | ControlFlowNode for Attribute() | provenance | |
| test.py:26:28:26:36 | ControlFlowNode for file_path | test.py:26:5:26:57 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:26:28:26:36 | ControlFlowNode for file_path | test.py:27:28:27:36 | ControlFlowNode for file_path | provenance | |
| test.py:27:28:27:36 | ControlFlowNode for file_path | test.py:27:5:27:50 | ControlFlowNode for Attribute() | provenance | |
| test.py:27:28:27:36 | ControlFlowNode for file_path | test.py:27:5:27:50 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:27:28:27:36 | ControlFlowNode for file_path | test.py:28:26:28:34 | ControlFlowNode for file_path | provenance | |
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:28:5:28:60 | ControlFlowNode for Attribute() | provenance | |
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:28:5:28:60 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:35:27:35:35 | ControlFlowNode for file_path | provenance | |
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:39:15:39:23 | ControlFlowNode for file_path | provenance | |
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:40:19:40:27 | ControlFlowNode for file_path | provenance | |

View File

@@ -1,6 +1,4 @@
edges
| django_bad.py:27:33:27:67 | ControlFlowNode for Attribute() | django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | provenance | |
| django_bad.py:27:71:27:106 | ControlFlowNode for Attribute() | django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | provenance | |
| flask_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_bad.py:1:26:1:32 | ControlFlowNode for request | provenance | |
| flask_bad.py:1:26:1:32 | ControlFlowNode for request | flask_bad.py:24:21:24:27 | ControlFlowNode for request | provenance | |
| flask_bad.py:1:26:1:32 | ControlFlowNode for request | flask_bad.py:24:49:24:55 | ControlFlowNode for request | provenance | |
@@ -14,9 +12,6 @@ edges
nodes
| django_bad.py:19:21:19:55 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | semmle.label | ControlFlowNode for Fstring |
| django_bad.py:27:33:27:67 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| django_bad.py:27:71:27:106 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| flask_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
| flask_bad.py:1:26:1:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_bad.py:24:21:24:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
@@ -34,12 +29,6 @@ subpaths
| django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | Cookie is constructed from a $@,and its httponly flag is not properly set. | django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | user-supplied input |
| django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | Cookie is constructed from a $@,and its samesite flag is not properly set. | django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | user-supplied input |
| django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | Cookie is constructed from a $@,and its secure flag is not properly set. | django_bad.py:20:21:20:56 | ControlFlowNode for Attribute() | user-supplied input |
| django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | django_bad.py:27:33:27:67 | ControlFlowNode for Attribute() | django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | Cookie is constructed from a $@,and its httponly flag is not properly set. | django_bad.py:27:33:27:67 | ControlFlowNode for Attribute() | user-supplied input |
| django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | django_bad.py:27:33:27:67 | ControlFlowNode for Attribute() | django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | Cookie is constructed from a $@,and its samesite flag is not properly set. | django_bad.py:27:33:27:67 | ControlFlowNode for Attribute() | user-supplied input |
| django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | django_bad.py:27:33:27:67 | ControlFlowNode for Attribute() | django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | Cookie is constructed from a $@,and its secure flag is not properly set. | django_bad.py:27:33:27:67 | ControlFlowNode for Attribute() | user-supplied input |
| django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | django_bad.py:27:71:27:106 | ControlFlowNode for Attribute() | django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | Cookie is constructed from a $@,and its httponly flag is not properly set. | django_bad.py:27:71:27:106 | ControlFlowNode for Attribute() | user-supplied input |
| django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | django_bad.py:27:71:27:106 | ControlFlowNode for Attribute() | django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | Cookie is constructed from a $@,and its samesite flag is not properly set. | django_bad.py:27:71:27:106 | ControlFlowNode for Attribute() | user-supplied input |
| django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | django_bad.py:27:71:27:106 | ControlFlowNode for Attribute() | django_bad.py:27:30:27:124 | ControlFlowNode for Fstring | Cookie is constructed from a $@,and its secure flag is not properly set. | django_bad.py:27:71:27:106 | ControlFlowNode for Attribute() | user-supplied input |
| flask_bad.py:24:21:24:40 | ControlFlowNode for Subscript | flask_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_bad.py:24:21:24:40 | ControlFlowNode for Subscript | Cookie is constructed from a $@,and its httponly flag is not properly set. | flask_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | user-supplied input |
| flask_bad.py:24:21:24:40 | ControlFlowNode for Subscript | flask_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_bad.py:24:21:24:40 | ControlFlowNode for Subscript | Cookie is constructed from a $@,and its samesite flag is not properly set. | flask_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | user-supplied input |
| flask_bad.py:24:21:24:40 | ControlFlowNode for Subscript | flask_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_bad.py:24:21:24:40 | ControlFlowNode for Subscript | Cookie is constructed from a $@,and its secure flag is not properly set. | flask_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | user-supplied input |

View File

@@ -1,15 +1,9 @@
| django_bad.py:6:5:7:52 | ControlFlowNode for Attribute() | Cookie is added without the 'httponly' flag properly set. |
| django_bad.py:6:5:7:52 | ControlFlowNode for Attribute() | Cookie is added without the 'samesite' flag properly set. |
| django_bad.py:6:5:7:52 | ControlFlowNode for Attribute() | Cookie is added without the 'secure' flag properly set. |
| django_bad.py:13:5:13:26 | ControlFlowNode for Subscript | Cookie is added without the 'httponly' flag properly set. |
| django_bad.py:13:5:13:26 | ControlFlowNode for Subscript | Cookie is added without the 'samesite' flag properly set. |
| django_bad.py:13:5:13:26 | ControlFlowNode for Subscript | Cookie is added without the 'secure' flag properly set. |
| django_bad.py:19:5:21:66 | ControlFlowNode for Attribute() | Cookie is added without the 'httponly' flag properly set. |
| django_bad.py:19:5:21:66 | ControlFlowNode for Attribute() | Cookie is added without the 'samesite' flag properly set. |
| django_bad.py:19:5:21:66 | ControlFlowNode for Attribute() | Cookie is added without the 'secure' flag properly set. |
| django_bad.py:27:5:27:26 | ControlFlowNode for Subscript | Cookie is added without the 'httponly' flag properly set. |
| django_bad.py:27:5:27:26 | ControlFlowNode for Subscript | Cookie is added without the 'samesite' flag properly set. |
| django_bad.py:27:5:27:26 | ControlFlowNode for Subscript | Cookie is added without the 'secure' flag properly set. |
| django_good.py:19:5:19:44 | ControlFlowNode for Attribute() | Cookie is added without the 'httponly' flag properly set. |
| django_good.py:19:5:19:44 | ControlFlowNode for Attribute() | Cookie is added without the 'samesite' flag properly set. |
| django_good.py:19:5:19:44 | ControlFlowNode for Attribute() | Cookie is added without the 'secure' flag properly set. |

View File

@@ -7,7 +7,7 @@ def django_response(request):
httponly=False, samesite='None')
return resp
# This test no longer produces an output due to django header setting methods not being modeled in the main query pack
def django_response():
response = django.http.HttpResponse()
response['Set-Cookie'] = "name=value; SameSite=None;"
@@ -21,7 +21,7 @@ def django_response(request):
secure=False, httponly=False, samesite='None')
return resp
# This test no longer produces an output due to django header setting methods not being modeled in the main query pack
def django_response():
response = django.http.HttpResponse()
response['Set-Cookie'] = f"{django.http.request.GET.get('name')}={django.http.request.GET.get('value')}; SameSite=None;"

View File

@@ -18,7 +18,10 @@ extensions:
- ["foo", "Member[MS_spread]", "Argument[0]", "ReturnValue.TupleElement[0]", "value"]
- ["foo", "Member[MS_spread]", "Argument[1]", "ReturnValue.TupleElement[1]", "value"]
- ["foo", "Member[MS_spread_all]", "Argument[0]", "ReturnValue.TupleElement[0,1]", "value"]
- ["foo", "Member[MS_Class].Instance.Member[instance_method]", "Argument[self]", "ReturnValue.TupleElement[0]", "value"]
- ["foo", "Member[MS_Class].Instance.Member[instance_method]", "Argument[0]", "ReturnValue.TupleElement[1]", "value"]
- ["foo", "Member[MS_Class].Instance.Member[explicit_self]", "Argument[self:]", "ReturnValue", "value"]
- ["foo.MS_Class!", "Call", "Argument[0, x:]", "ReturnValue.Attribute[config]", "value"]
- ["foo.MS_Class_transitive!", "Subclass.Call", "Argument[0, x:]", "ReturnValue.Attribute[config]", "value"]
- ["foo.MS_Class_transitive", "Member[instance_method]", "Argument[0]", "ReturnValue", "value"]
- ["foo.MS_Class", "Member[instance_method]", "Argument[self]", "ReturnValue.TupleElement[0]", "value"]
- ["foo.MS_Class", "Member[instance_method]", "Argument[0]", "ReturnValue.TupleElement[1]", "value"]
- ["foo.MS_Class", "Member[explicit_self]", "Argument[self:]", "ReturnValue", "value"]
- ["json", "Member[MS_loads]", "Argument[0]", "ReturnValue", "taint"]

View File

@@ -18,7 +18,10 @@ extensions:
- ["foo", "Member[MS_spread]", "Argument[0]", "ReturnValue.TupleElement[0]", "value"]
- ["foo", "Member[MS_spread]", "Argument[1]", "ReturnValue.TupleElement[1]", "value"]
- ["foo", "Member[MS_spread_all]", "Argument[0]", "ReturnValue.TupleElement[0,1]", "value"]
- ["foo", "Member[MS_Class].Instance.Member[instance_method]", "Argument[self]", "ReturnValue.TupleElement[0]", "value"]
- ["foo", "Member[MS_Class].Instance.Member[instance_method]", "Argument[0]", "ReturnValue.TupleElement[1]", "value"]
- ["foo", "Member[MS_Class].Instance.Member[explicit_self]", "Argument[self:]", "ReturnValue", "value"]
- ["foo.MS_Class!", "Call", "Argument[0, x:]", "ReturnValue.Attribute[config]", "value"]
- ["foo.MS_Class_transitive!", "Subclass.Call", "Argument[0, x:]", "ReturnValue.Attribute[config]", "value"]
- ["foo.MS_Class_transitive", "Member[instance_method]", "Argument[0]", "ReturnValue", "value"]
- ["foo.MS_Class", "Member[instance_method]", "Argument[self]", "ReturnValue.TupleElement[0]", "value"]
- ["foo.MS_Class", "Member[instance_method]", "Argument[0]", "ReturnValue.TupleElement[1]", "value"]
- ["foo.MS_Class", "Member[explicit_self]", "Argument[self:]", "ReturnValue", "value"]
- ["json", "Member[MS_loads]", "Argument[0]", "ReturnValue", "taint"]

View File

@@ -122,7 +122,42 @@ a, b = MS_spread_all(SOURCE)
SINK(a) # $ flow="SOURCE, l:-1 -> a"
SINK(b) # $ flow="SOURCE, l:-2 -> b"
from foo import MS_Class
from foo import MS_Class, MS_Class_transitive
# Class summaries
class_via_positional = MS_Class(SOURCE)
SINK(class_via_positional.config) # $ flow="SOURCE, l:-1 -> class_via_positional.config"
class_via_kw = MS_Class(x = SOURCE)
SINK(class_via_kw.config) # $ flow="SOURCE, l:-1 -> class_via_kw.config"
class C(MS_Class_transitive):
pass
subclass_via_positional = C(SOURCE)
SINK(subclass_via_positional.config) # $ flow="SOURCE, l:-1 -> subclass_via_positional.config"
subclass_via_kw = C(x = SOURCE)
SINK(subclass_via_kw.config) # $ flow="SOURCE, l:-1 -> subclass_via_kw.config"
SINK(subclass_via_kw.instance_method(SOURCE)) # $ flow="SOURCE -> subclass_via_kw.instance_method(..)"
class D(MS_Class_transitive):
def __init__(x, y):
# special handling of y
super().__init__(x)
SINK(D(SOURCE, NONSOURCE).config) # $ flow="SOURCE -> D(..).config"
SINK(D(x = SOURCE, y = NONSOURCE).config) # $ flow="SOURCE -> D(..).config"
class E(MS_Class_transitive):
# Notice the swapped order of the arguments
def __init__(y, x):
# special handling of y
super().__init__(x)
SINK(E(NONSOURCE, SOURCE).config) # $ MISSING: flow="SOURCE -> E(..).config"
SINK(E(x = SOURCE, y = NONSOURCE).config) # $ flow="SOURCE -> E(..).config"
c = MS_Class()
a, b = c.instance_method(SOURCE)

View File

@@ -42,7 +42,7 @@ edges
| summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist | summaries.py:68:6:68:26 | ControlFlowNode for Subscript | provenance | |
| summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist [List element] | summaries.py:68:6:68:23 | ControlFlowNode for tainted_resultlist [List element] | provenance | |
| summaries.py:67:22:67:39 | ControlFlowNode for json_loads() [List element] | summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist [List element] | provenance | |
| summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist | provenance | AdditionalTaintStep |
| summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | summaries.py:67:1:67:18 | ControlFlowNode for tainted_resultlist | provenance | Decoding-JSON |
| summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | summaries.py:67:22:67:39 | ControlFlowNode for json_loads() [List element] | provenance | json.loads |
| summaries.py:68:6:68:23 | ControlFlowNode for tainted_resultlist [List element] | summaries.py:68:6:68:26 | ControlFlowNode for Subscript | provenance | |
nodes

View File

@@ -78,6 +78,7 @@ isSource
| test.py:39:11:39:20 | ControlFlowNode for Await | test-source |
| test.py:41:8:41:27 | ControlFlowNode for Attribute() | test-source |
| test.py:46:7:46:16 | ControlFlowNode for SubClass() | test-source |
| test.py:51:8:51:18 | ControlFlowNode for Sub2Class() | test-source |
| test.py:53:7:53:16 | ControlFlowNode for Attribute() | test-source |
| test.py:60:13:60:16 | ControlFlowNode for self | test-source |
| test.py:60:24:60:28 | ControlFlowNode for named | test-source |

View File

@@ -48,7 +48,7 @@ sub = SubClass()
class Sub2Class (CommonTokens.Class):
pass
sub2 = Sub2Class() # TODO: Currently not recognized as an instance of CommonTokens.Class
sub2 = Sub2Class()
val = inst.foo()

View File

@@ -1,6 +1,7 @@
import json
from flask import Flask, make_response, jsonify, Response, request, redirect
from werkzeug.datastructures import Headers
app = Flask(__name__)
@@ -117,7 +118,7 @@ def response_modification1(): # $requestHandler
@app.route("/content-type/response-modification2") # $routeSetup="/content-type/response-modification2"
def response_modification2(): # $requestHandler
resp = make_response("<h1>hello</h1>") # $HttpResponse mimetype=text/html responseBody="<h1>hello</h1>"
resp.headers["content-type"] = "text/plain" # $ MISSING: HttpResponse mimetype=text/plain
resp.headers["content-type"] = "text/plain" # $ headerWriteNameUnsanitized="content-type" headerWriteValueSanitized="text/plain" MISSING: HttpResponse mimetype=text/plain
return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp
@@ -147,7 +148,7 @@ def Response3(): # $requestHandler
@app.route("/content-type/Response4") # $routeSetup="/content-type/Response4"
def Response4(): # $requestHandler
# note: capitalization of Content-Type does not matter
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/plain"}) # $HttpResponse responseBody="<h1>hello</h1>" SPURIOUS: mimetype=text/html MISSING: mimetype=text/plain
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/plain"}) # $ headerWriteBulk=Dict headerWriteNameUnsanitized headerWriteValueSanitized HttpResponse responseBody="<h1>hello</h1>" SPURIOUS: mimetype=text/html MISSING: mimetype=text/plain
return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp
@@ -155,7 +156,7 @@ def Response4(): # $requestHandler
def Response5(): # $requestHandler
# content_type argument takes priority (and result is text/plain)
# note: capitalization of Content-Type does not matter
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/html"}, content_type="text/plain; charset=utf-8") # $HttpResponse mimetype=text/plain responseBody="<h1>hello</h1>"
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/html"}, content_type="text/plain; charset=utf-8") # $ headerWriteBulk=Dict headerWriteNameUnsanitized headerWriteValueSanitized HttpResponse mimetype=text/plain responseBody="<h1>hello</h1>"
return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp
@@ -163,7 +164,7 @@ def Response5(): # $requestHandler
def Response6(): # $requestHandler
# mimetype argument takes priority over header (and result is text/plain)
# note: capitalization of Content-Type does not matter
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/html"}, mimetype="text/plain") # $HttpResponse mimetype=text/plain responseBody="<h1>hello</h1>"
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/html"}, mimetype="text/plain") # $ headerWriteBulk=Dict headerWriteNameUnsanitized headerWriteValueSanitized HttpResponse mimetype=text/plain responseBody="<h1>hello</h1>"
return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp
@@ -207,12 +208,45 @@ def setting_cookie(): # $requestHandler
resp = make_response() # $ HttpResponse mimetype=text/html
resp.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
resp.set_cookie(key="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value"
resp.headers.add("Set-Cookie", "key2=value2") # $ MISSING: CookieWrite CookieRawHeader="key2=value2"
resp.headers.add("Set-Cookie", "key2=value2") # $ headerWriteNameUnsanitized="Set-Cookie" headerWriteValueSanitized="key2=value2" MISSING: CookieWrite CookieRawHeader="key2=value2"
resp.delete_cookie("key3") # $ CookieWrite CookieName="key3"
resp.delete_cookie(key="key3") # $ CookieWrite CookieName="key3"
return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp
################################################################################
# Headers
################################################################################
@app.route("/headers") # $routeSetup="/headers"
def headers(): # $requestHandler
resp1 = Response() # $ HttpResponse mimetype=text/html
resp1.headers["X-MyHeader"] = "a" # $ headerWriteNameUnsanitized="X-MyHeader" headerWriteValueSanitized="a"
resp2 = make_response() # $ HttpResponse mimetype=text/html
resp2.headers["X-MyHeader"] = "aa" # $ headerWriteNameUnsanitized="X-MyHeader" headerWriteValueSanitized="aa"
resp2.headers.extend({"X-MyHeader2": "b"}) # $ headerWriteBulk=Dict headerWriteNameUnsanitized headerWriteValueSanitized
resp3 = make_response("hello", 200, {"X-MyHeader3": "c"}) # $ HttpResponse mimetype=text/html responseBody="hello" headerWriteBulk=Dict headerWriteNameUnsanitized headerWriteValueSanitized
resp4 = make_response("hello", {"X-MyHeader4": "d"}) # $ HttpResponse mimetype=text/html responseBody="hello" headerWriteBulk=Dict headerWriteNameUnsanitized headerWriteValueSanitized
resp5 = Response(headers={"X-MyHeader5":"e"}) # $ HttpResponse mimetype=text/html headerWriteBulk=Dict headerWriteNameUnsanitized headerWriteValueSanitized
return resp5 # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp5
@app.route("/werkzeug-headers") # $routeSetup="/werkzeug-headers"
def werkzeug_headers(): # $requestHandler
response = Response() # $ HttpResponse mimetype=text/html
headers = Headers()
headers.add("X-MyHeader1", "a") # $ headerWriteNameUnsanitized="X-MyHeader1" headerWriteValueSanitized="a"
headers.add_header("X-MyHeader2", "b") # $ headerWriteNameUnsanitized="X-MyHeader2" headerWriteValueSanitized="b"
headers.set("X-MyHeader3", "c") # $ headerWriteNameUnsanitized="X-MyHeader3" headerWriteValueSanitized="c"
headers.setdefault("X-MyHeader4", "d") # $ headerWriteNameUnsanitized="X-MyHeader4" headerWriteValueSanitized="d"
headers.__setitem__("X-MyHeader5", "e") # $ headerWriteNameUnsanitized="X-MyHeader5" headerWriteValueSanitized="e"
headers["X-MyHeader6"] = "f" # $ headerWriteNameUnsanitized="X-MyHeader6" headerWriteValueSanitized="f"
h1 = {"X-MyHeader7": "g"}
headers.extend(h1) # $ headerWriteBulk=h1 headerWriteNameUnsanitized headerWriteValueSanitized
h2 = [("X-MyHeader8", "h")]
headers.extend(h2) # $ headerWriteBulk=h2 headerWriteNameUnsanitized headerWriteValueSanitized
response.headers = headers
return response # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=response
################################################################################
if __name__ == "__main__":

View File

@@ -0,0 +1,5 @@
import flask
class MySessionInterface(flask.sessions.SessionInterface):
def open_session(self, app, request):
ensure_tainted(request) # $tainted

View File

@@ -0,0 +1,2 @@
testFailures
failures

View File

@@ -0,0 +1,33 @@
import gradio as gr
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Output Box")
# static block - not used as a source
static_block = gr.HTML("""
<div style='height: 100px; width: 800px; background-color: pink;'></div>
""")
greet_btn = gr.Button("Hello")
# decorator
@greet_btn.click(inputs=name, outputs=output)
def greet(name): # $ source=name
return "Hello " + name + "!"
# `click` event handler with keyword arguments
def greet1(name): # $ source=name
return "Hello " + name + "!"
greet1_btn = gr.Button("Hello")
greet1_btn.click(fn=greet1, inputs=name, outputs=output, api_name="greet")
# `click` event handler with positional arguments
def greet2(name): # $ source=name
return "Hello " + name + "!"
greet2_btn = gr.Button("Hello")
greet2_btn.click(fn=greet2, inputs=name, outputs=output, api_name="greet")
demo.launch()

View File

@@ -0,0 +1,20 @@
import python
import semmle.python.dataflow.new.RemoteFlowSources
import TestUtilities.InlineExpectationsTest
private import semmle.python.dataflow.new.internal.PrintNode
module SourceTest implements TestSig {
string getARelevantTag() { result = "source" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(RemoteFlowSource rfs |
location = rfs.getLocation() and
element = rfs.toString() and
value = prettyNode(rfs) and
tag = "source"
)
}
}
import MakeTest<SourceTest>

View File

@@ -0,0 +1,26 @@
edges
| taint_step_test.py:5:5:5:8 | ControlFlowNode for path | taint_step_test.py:19:43:19:46 | ControlFlowNode for path | provenance | |
| taint_step_test.py:5:12:5:35 | ControlFlowNode for Attribute() | taint_step_test.py:5:5:5:8 | ControlFlowNode for path | provenance | |
| taint_step_test.py:6:5:6:8 | ControlFlowNode for file | taint_step_test.py:19:48:19:51 | ControlFlowNode for file | provenance | |
| taint_step_test.py:6:12:6:35 | ControlFlowNode for Attribute() | taint_step_test.py:6:5:6:8 | ControlFlowNode for file | provenance | |
| taint_step_test.py:11:18:11:21 | ControlFlowNode for path | taint_step_test.py:12:9:12:16 | ControlFlowNode for filepath | provenance | |
| taint_step_test.py:11:18:11:21 | ControlFlowNode for path | taint_step_test.py:12:9:12:16 | ControlFlowNode for filepath | provenance | AdditionalTaintStep |
| taint_step_test.py:11:24:11:27 | ControlFlowNode for file | taint_step_test.py:12:9:12:16 | ControlFlowNode for filepath | provenance | AdditionalTaintStep |
| taint_step_test.py:12:9:12:16 | ControlFlowNode for filepath | taint_step_test.py:13:19:13:26 | ControlFlowNode for filepath | provenance | |
| taint_step_test.py:19:43:19:46 | ControlFlowNode for path | taint_step_test.py:11:18:11:21 | ControlFlowNode for path | provenance | AdditionalTaintStep |
| taint_step_test.py:19:48:19:51 | ControlFlowNode for file | taint_step_test.py:11:24:11:27 | ControlFlowNode for file | provenance | AdditionalTaintStep |
nodes
| taint_step_test.py:5:5:5:8 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
| taint_step_test.py:5:12:5:35 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| taint_step_test.py:6:5:6:8 | ControlFlowNode for file | semmle.label | ControlFlowNode for file |
| taint_step_test.py:6:12:6:35 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| taint_step_test.py:11:18:11:21 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
| taint_step_test.py:11:24:11:27 | ControlFlowNode for file | semmle.label | ControlFlowNode for file |
| taint_step_test.py:12:9:12:16 | ControlFlowNode for filepath | semmle.label | ControlFlowNode for filepath |
| taint_step_test.py:13:19:13:26 | ControlFlowNode for filepath | semmle.label | ControlFlowNode for filepath |
| taint_step_test.py:19:43:19:46 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
| taint_step_test.py:19:48:19:51 | ControlFlowNode for file | semmle.label | ControlFlowNode for file |
subpaths
#select
| taint_step_test.py:13:19:13:26 | ControlFlowNode for filepath | taint_step_test.py:5:12:5:35 | ControlFlowNode for Attribute() | taint_step_test.py:13:19:13:26 | ControlFlowNode for filepath | This path depends on a $@. | taint_step_test.py:5:12:5:35 | ControlFlowNode for Attribute() | user-provided value |
| taint_step_test.py:13:19:13:26 | ControlFlowNode for filepath | taint_step_test.py:6:12:6:35 | ControlFlowNode for Attribute() | taint_step_test.py:13:19:13:26 | ControlFlowNode for filepath | This path depends on a $@. | taint_step_test.py:6:12:6:35 | ControlFlowNode for Attribute() | user-provided value |

View File

@@ -0,0 +1,22 @@
import gradio as gr
import os
with gr.Blocks() as demo:
path = gr.Textbox(label="Path") # $ source=gr.Textbox(..)
file = gr.Textbox(label="File") # $ source=gr.Textbox(..)
output = gr.Textbox(label="Output Box")
# path injection sink
def fileread(path, file):
filepath = os.path.join(path, file)
with open(filepath, "r") as f:
return f.read()
# `click` event handler with `inputs` containing a list
greet1_btn = gr.Button("Path for the file to display")
greet1_btn.click(fn=fileread, inputs=[path,file], outputs=output, api_name="fileread")
demo.launch()

View File

@@ -0,0 +1 @@
Security/CWE-022/PathInjection.ql

View File

@@ -0,0 +1,2 @@
testFailures
failures

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View File

@@ -0,0 +1,4 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
testFailures
failures

View File

@@ -0,0 +1,2 @@
import experimental.meta.InlineTaintTest
import MakeInlineTaintTest<TestTaintTrackingConfig>

View File

@@ -0,0 +1,153 @@
from pyramid.view import view_config
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.httpexceptions import HTTPMultipleChoices, HTTPMovedPermanently, HTTPFound, HTTPSeeOther, HTTPUseProxy, HTTPTemporaryRedirect, HTTPPermanentRedirect
from wsgiref.simple_server import make_server
def ignore(*args, **kwargs): pass
ensure_tainted = ensure_not_tainted = ignore
@view_config(route_name="test1") # $ routeSetup
def test1(request): # $ requestHandler
ensure_tainted(
request, # $ tainted
request.accept, # $ tainted
request.accept_charset, # $ tainted
request.accept_encoding, # $ tainted
request.accept_language, # $ tainted
request.authorization, # $ tainted
request.cache_control, # $ tainted
request.client_addr, # $ tainted
request.content_type, # $ tainted
request.domain, # $ tainted
request.host, # $ tainted
request.host_port, # $ tainted
request.host_url, # $ tainted
request.if_match, # $ tainted
request.if_none_match, # $ tainted
request.if_range, # $ tainted
request.pragma, # $ tainted
request.range, # $ tainted
request.referer, # $ tainted
request.referrer, # $ tainted
request.user_agent, # $ tainted
request.as_bytes, # $ tainted
request.body, # $ tainted
request.body_file.read(), # $ tainted
request.body_file_raw.read(), # $ tainted
request.body_file_seekable.read(),# $ tainted
request.json, # $ tainted
request.json_body, # $ tainted
request.json['a']['b'][0]['c'], # $ tainted
request.text, # $ tainted
request.matchdict, # $ tainted
request.path, # $ tainted
request.path_info, # $ tainted
request.path_info_peek(), # $ tainted
request.path_info_pop(), # $ tainted
request.path_qs, # $ tainted
request.path_url, # $ tainted
request.query_string, # $ tainted
request.url, # $ tainted
request.urlargs, # $ tainted
request.urlvars, # $ tainted
request.GET['a'], # $ tainted
request.POST['b'], # $ tainted
request.cookies['c'], # $ tainted
request.params['d'], # $ tainted
request.headers['X-My-Header'], # $ tainted
request.GET.values(), # $ tainted
request.copy(), # $ tainted
request.copy_get(), # $ tainted
request.copy().GET['a'], # $ tainted
request.copy_get().body # $ tainted
)
return Response("Ok") # $ HttpResponse responseBody="Ok" mimetype=text/html
def test2(request): # $ requestHandler
ensure_tainted(request) # $ tainted
resp = Response("Ok", content_type="text/plain") # $ HttpResponse responseBody="Ok" mimetype=text/plain
resp.body = "Ok2" # $ HttpResponse responseBody="Ok2" SPURIOUS: mimetype=text/html
return resp
@view_config(route_name="test3", renderer="string") # $ routeSetup
def test3(ctx, req): # $ requestHandler
ensure_tainted(req) # $ tainted
resp = req.response # $ HttpResponse mimetype=text/html
resp.set_cookie("hi", "there") # $ CookieWrite CookieName="hi" CookieValue="there"
resp.set_cookie(value="there", name="hi") # $ CookieWrite CookieName="hi" CookieValue="there"
return "Ok" # $ HttpResponse responseBody="Ok" mimetype=text/html
@view_config(route_name="test4", renderer="string") # $ routeSetup
def test4(request): # $ requestHandler
a = HTTPMultipleChoices("redirect") # $HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation="redirect"
b = HTTPMovedPermanently(location="redirect") # $HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation="redirect"
c = HTTPFound(location="redirect") # $HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation="redirect"
d = HTTPSeeOther(location="redirect") # $HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation="redirect"
e = HTTPUseProxy(location="redirect") # $HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation="redirect"
f = HTTPTemporaryRedirect(location="redirect") # $HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation="redirect"
g = HTTPPermanentRedirect(location="redirect") # $HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation="redirect"
raise a
# Unsupported cases
class Test5:
def __init__(self, request): # $ MISSING: requestHandler
ensure_tainted(request) # $ MISSING: tainted
self.req = request
@view_config(route_name="test5", renderer="string") # $ routeSetup
def test5(self): # $ requestHandler
ensure_not_tainted(self) # $ SPURIOUS: tainted
ensure_tainted(self.req) # $ MISSING: tainted
return "Ok" # $ HttpResponse mimetype=text/html responseBody="Ok"
@view_config(route_name="test6", attr="test6method", renderer="string") # $ routeSetup
class Test6:
def __init__(self, request): # $ MISSING: requestHandler
ensure_tainted(request) # $ MISSING: tainted
self.req = request
def test6method(self): # $ MISSING: requestHandler
ensure_not_tainted(self)
ensure_tainted(self.req) # $ MISSING: tainted
return "Ok" # $ MISSING: HttpResponse mimetype=text/html responseBody="Ok"
@view_config(route_name="test6", renderer="string") # $ routeSetup
class Test6:
def __init__(self, context, request): # $ MISSING: requestHandler
ensure_tainted(request) # $ MISSING: tainted
self.req = request
def __call__(self): # $ MISSING: requestHandler
ensure_not_tainted(self)
ensure_tainted(self.req) # $ MISSING: tainted
return "Ok" # $ MISSING: HttpResponse mimetype=text/html responseBody="Ok"
class Test7:
def __call__(self,context,request): # $ MISSING: requestHandler
ensure_tainted(request) # $ MISSING: tainted
return "Ok" # $ MISSING: HttpResponse mimetype=text/html responseBody="Ok"
if __name__ == "__main__":
with Configurator() as config:
for i in range(1,8):
config.add_route(f"test{i}", f"/test{i}")
config.add_view(test2, route_name="test2") # $ routeSetup
config.add_view(Test7(), route_name="test7", renderer="string") # $ routeSetup
config.scan()
server = make_server('127.0.0.1', 8080, config.make_wsgi_app())
print("serving")
server.serve_forever()

View File

@@ -2,6 +2,7 @@
# see https://docs.python.org/3/library/wsgiref.html#wsgiref.simple_server.WSGIServer
import sys
import wsgiref.simple_server
import wsgiref.headers
def ignore(*arg, **kwargs): pass
ensure_tainted = ensure_not_tainted = ignore
@@ -17,7 +18,7 @@ def func(environ, start_response): # $ requestHandler
environ, # $ tainted
environ["PATH_INFO"], # $ tainted
)
write = start_response("200 OK", [("Content-Type", "text/plain")])
write = start_response("200 OK", [("Content-Type", "text/plain")]) # $ headerWriteBulk=List headerWriteNameUnsanitized headerWriteValueUnsanitized
write(b"hello") # $ HttpResponse responseBody=b"hello"
write(data=b" ") # $ HttpResponse responseBody=b" "
@@ -32,9 +33,17 @@ class MyServer(wsgiref.simple_server.WSGIServer):
self.set_app(self.my_method)
def my_method(self, _env, start_response): # $ requestHandler
start_response("200 OK", [])
start_response("200 OK", []) # $ headerWriteBulk=List headerWriteNameUnsanitized headerWriteValueUnsanitized
return [b"my_method"] # $ HttpResponse responseBody=List
def func2(environ, start_response): # $ requestHandler
headers = wsgiref.headers.Headers([("Content-Type", "text/plain")]) # $ headerWriteBulk=List headerWriteNameUnsanitized headerWriteValueUnsanitized
headers.add_header("X-MyHeader", "a") # $ headerWriteNameUnsanitized="X-MyHeader" headerWriteValueUnsanitized="a"
headers.setdefault("X-MyHeader2", "b") # $ headerWriteNameUnsanitized="X-MyHeader2" headerWriteValueUnsanitized="b"
headers.__setitem__("X-MyHeader3", "c") # $ headerWriteNameUnsanitized="X-MyHeader3" headerWriteValueUnsanitized="c"
headers["X-MyHeader4"] = "d" # $ headerWriteNameUnsanitized="X-MyHeader4" headerWriteValueUnsanitized="d"
start_response(status, headers) # $ headerWriteBulk=headers headerWriteNameUnsanitized headerWriteValueUnsanitized
return [b"Hello"] # $ HttpResponse responseBody=List
case = sys.argv[1]
if case == "1":
@@ -45,9 +54,11 @@ elif case == "2":
elif case == "3":
server = MyServer()
def func3(_env, start_response): # $ requestHandler
start_response("200 OK", [])
start_response("200 OK", []) # $ headerWriteBulk=List headerWriteNameUnsanitized headerWriteValueUnsanitized
return [b"foo"] # $ HttpResponse responseBody=List
server.set_app(func3)
elif case == "4":
server = wsgiref.simple_server.make_server(ADDRESS[0], ADDRESS[1], func2)
else:
sys.exit("wrong case")

View File

@@ -5,3 +5,4 @@
| imports_test.py:10:1:10:22 | Import | Import of 'top_level_cycle' is not used. |
| imports_test.py:27:1:27:25 | Import | Import of 'func2' is not used. |
| imports_test.py:34:1:34:14 | Import | Import of 'module2' is not used. |
| imports_test.py:116:1:116:41 | Import | Import of 'not_a_fixture' is not used. |

View File

@@ -111,3 +111,8 @@ import subexpression_return_type
def baz() -> Optional['subexpression_return_type']:
pass
from pytest_fixtures import not_a_fixture # BAD
from pytest_fixtures import fixture, wrapped_fixture # GOOD (pytest fixtures are used implicitly by pytest)
from pytest_fixtures import session_fixture, wrapped_autouse_fixture # GOOD (pytest fixtures are used implicitly by pytest)

View File

@@ -0,0 +1,34 @@
import pytest
@pytest.fixture
def fixture():
pass
def fixture_wrapper():
@pytest.fixture
def delegate():
pass
return delegate
@fixture_wrapper
def wrapped_fixture():
pass
@pytest.fixture(scope='session')
def session_fixture():
pass
def not_a_fixture():
pass
def another_fixture_wrapper():
@pytest.fixture(autouse=True)
def delegate():
pass
return delegate
@another_fixture_wrapper
def wrapped_autouse_fixture():
pass

View File

@@ -5,12 +5,12 @@ edges
| test.py:5:26:5:32 | ControlFlowNode for request | test.py:34:12:34:18 | ControlFlowNode for request | provenance | |
| test.py:5:26:5:32 | ControlFlowNode for request | test.py:42:12:42:18 | ControlFlowNode for request | provenance | |
| test.py:5:26:5:32 | ControlFlowNode for request | test.py:54:12:54:18 | ControlFlowNode for request | provenance | |
| test.py:13:5:13:12 | ControlFlowNode for data_raw | test.py:14:5:14:8 | ControlFlowNode for data | provenance | AdditionalTaintStep |
| test.py:13:5:13:12 | ControlFlowNode for data_raw | test.py:14:5:14:8 | ControlFlowNode for data | provenance | Decoding-Base64 |
| test.py:13:16:13:22 | ControlFlowNode for request | test.py:13:16:13:27 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| test.py:13:16:13:27 | ControlFlowNode for Attribute | test.py:13:16:13:39 | ControlFlowNode for Attribute() | provenance | dict.get |
| test.py:13:16:13:39 | ControlFlowNode for Attribute() | test.py:13:5:13:12 | ControlFlowNode for data_raw | provenance | |
| test.py:14:5:14:8 | ControlFlowNode for data | test.py:15:36:15:39 | ControlFlowNode for data | provenance | |
| test.py:23:5:23:12 | ControlFlowNode for data_raw | test.py:24:5:24:8 | ControlFlowNode for data | provenance | AdditionalTaintStep |
| test.py:23:5:23:12 | ControlFlowNode for data_raw | test.py:24:5:24:8 | ControlFlowNode for data | provenance | Decoding-Base64 |
| test.py:23:16:23:22 | ControlFlowNode for request | test.py:23:16:23:27 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| test.py:23:16:23:27 | ControlFlowNode for Attribute | test.py:23:16:23:39 | ControlFlowNode for Attribute() | provenance | dict.get |
| test.py:23:16:23:39 | ControlFlowNode for Attribute() | test.py:23:5:23:12 | ControlFlowNode for data_raw | provenance | |

View File

@@ -27,28 +27,28 @@ edges
| path_injection.py:19:16:19:47 | ControlFlowNode for Attribute() | path_injection.py:19:5:19:12 | ControlFlowNode for filename | provenance | |
| path_injection.py:20:5:20:9 | ControlFlowNode for npath | path_injection.py:21:14:21:18 | ControlFlowNode for npath | provenance | |
| path_injection.py:20:13:20:64 | ControlFlowNode for Attribute() | path_injection.py:20:5:20:9 | ControlFlowNode for npath | provenance | |
| path_injection.py:20:30:20:63 | ControlFlowNode for Attribute() | path_injection.py:20:13:20:64 | ControlFlowNode for Attribute() | provenance | |
| path_injection.py:20:30:20:63 | ControlFlowNode for Attribute() | path_injection.py:20:13:20:64 | ControlFlowNode for Attribute() | provenance | Config |
| path_injection.py:27:5:27:12 | ControlFlowNode for filename | path_injection.py:28:30:28:63 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| path_injection.py:27:16:27:22 | ControlFlowNode for request | path_injection.py:27:16:27:27 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| path_injection.py:27:16:27:27 | ControlFlowNode for Attribute | path_injection.py:27:16:27:47 | ControlFlowNode for Attribute() | provenance | dict.get |
| path_injection.py:27:16:27:47 | ControlFlowNode for Attribute() | path_injection.py:27:5:27:12 | ControlFlowNode for filename | provenance | |
| path_injection.py:28:5:28:9 | ControlFlowNode for npath | path_injection.py:31:14:31:18 | ControlFlowNode for npath | provenance | |
| path_injection.py:28:13:28:64 | ControlFlowNode for Attribute() | path_injection.py:28:5:28:9 | ControlFlowNode for npath | provenance | |
| path_injection.py:28:30:28:63 | ControlFlowNode for Attribute() | path_injection.py:28:13:28:64 | ControlFlowNode for Attribute() | provenance | |
| path_injection.py:28:30:28:63 | ControlFlowNode for Attribute() | path_injection.py:28:13:28:64 | ControlFlowNode for Attribute() | provenance | Config |
| path_injection.py:46:5:46:12 | ControlFlowNode for filename | path_injection.py:47:30:47:63 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| path_injection.py:46:16:46:22 | ControlFlowNode for request | path_injection.py:46:16:46:27 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| path_injection.py:46:16:46:27 | ControlFlowNode for Attribute | path_injection.py:46:16:46:47 | ControlFlowNode for Attribute() | provenance | dict.get |
| path_injection.py:46:16:46:47 | ControlFlowNode for Attribute() | path_injection.py:46:5:46:12 | ControlFlowNode for filename | provenance | |
| path_injection.py:47:5:47:9 | ControlFlowNode for npath | path_injection.py:48:14:48:18 | ControlFlowNode for npath | provenance | |
| path_injection.py:47:13:47:64 | ControlFlowNode for Attribute() | path_injection.py:47:5:47:9 | ControlFlowNode for npath | provenance | |
| path_injection.py:47:30:47:63 | ControlFlowNode for Attribute() | path_injection.py:47:13:47:64 | ControlFlowNode for Attribute() | provenance | |
| path_injection.py:47:30:47:63 | ControlFlowNode for Attribute() | path_injection.py:47:13:47:64 | ControlFlowNode for Attribute() | provenance | Config |
| path_injection.py:63:5:63:12 | ControlFlowNode for filename | path_injection.py:64:29:64:62 | ControlFlowNode for Attribute() | provenance | AdditionalTaintStep |
| path_injection.py:63:16:63:22 | ControlFlowNode for request | path_injection.py:63:16:63:27 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| path_injection.py:63:16:63:27 | ControlFlowNode for Attribute | path_injection.py:63:16:63:47 | ControlFlowNode for Attribute() | provenance | dict.get |
| path_injection.py:63:16:63:47 | ControlFlowNode for Attribute() | path_injection.py:63:5:63:12 | ControlFlowNode for filename | provenance | |
| path_injection.py:64:5:64:9 | ControlFlowNode for npath | path_injection.py:65:14:65:18 | ControlFlowNode for npath | provenance | |
| path_injection.py:64:13:64:63 | ControlFlowNode for Attribute() | path_injection.py:64:5:64:9 | ControlFlowNode for npath | provenance | |
| path_injection.py:64:29:64:62 | ControlFlowNode for Attribute() | path_injection.py:64:13:64:63 | ControlFlowNode for Attribute() | provenance | |
| path_injection.py:64:29:64:62 | ControlFlowNode for Attribute() | path_injection.py:64:13:64:63 | ControlFlowNode for Attribute() | provenance | Config |
| path_injection.py:84:5:84:12 | ControlFlowNode for filename | path_injection.py:85:5:85:24 | ControlFlowNode for possibly_unsafe_path | provenance | AdditionalTaintStep |
| path_injection.py:84:16:84:22 | ControlFlowNode for request | path_injection.py:84:16:84:27 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
| path_injection.py:84:16:84:27 | ControlFlowNode for Attribute | path_injection.py:84:16:84:47 | ControlFlowNode for Attribute() | provenance | dict.get |
@@ -104,7 +104,7 @@ edges
| test.py:9:12:9:39 | ControlFlowNode for Attribute() | test.py:31:9:31:16 | ControlFlowNode for source() | provenance | |
| test.py:9:12:9:39 | ControlFlowNode for Attribute() | test.py:46:9:46:16 | ControlFlowNode for source() | provenance | |
| test.py:12:15:12:15 | ControlFlowNode for x | test.py:13:29:13:29 | ControlFlowNode for x | provenance | |
| test.py:13:29:13:29 | ControlFlowNode for x | test.py:13:12:13:30 | ControlFlowNode for Attribute() | provenance | |
| test.py:13:29:13:29 | ControlFlowNode for x | test.py:13:12:13:30 | ControlFlowNode for Attribute() | provenance | Config |
| test.py:18:5:18:5 | ControlFlowNode for x | test.py:19:10:19:10 | ControlFlowNode for x | provenance | |
| test.py:18:9:18:16 | ControlFlowNode for source() | test.py:18:5:18:5 | ControlFlowNode for x | provenance | |
| test.py:24:5:24:5 | ControlFlowNode for x | test.py:25:19:25:19 | ControlFlowNode for x | provenance | |
@@ -112,7 +112,7 @@ edges
| test.py:25:5:25:5 | ControlFlowNode for y | test.py:26:10:26:10 | ControlFlowNode for y | provenance | |
| test.py:25:9:25:20 | ControlFlowNode for normalize() | test.py:25:5:25:5 | ControlFlowNode for y | provenance | |
| test.py:25:19:25:19 | ControlFlowNode for x | test.py:12:15:12:15 | ControlFlowNode for x | provenance | |
| test.py:25:19:25:19 | ControlFlowNode for x | test.py:25:9:25:20 | ControlFlowNode for normalize() | provenance | |
| test.py:25:19:25:19 | ControlFlowNode for x | test.py:25:9:25:20 | ControlFlowNode for normalize() | provenance | Config |
| test.py:31:5:31:5 | ControlFlowNode for x | test.py:33:14:33:14 | ControlFlowNode for x | provenance | |
| test.py:31:9:31:16 | ControlFlowNode for source() | test.py:31:5:31:5 | ControlFlowNode for x | provenance | |
| test.py:46:5:46:5 | ControlFlowNode for x | test.py:48:23:48:23 | ControlFlowNode for x | provenance | |
@@ -120,7 +120,7 @@ edges
| test.py:48:9:48:9 | ControlFlowNode for y | test.py:49:14:49:14 | ControlFlowNode for y | provenance | |
| test.py:48:13:48:24 | ControlFlowNode for normalize() | test.py:48:9:48:9 | ControlFlowNode for y | provenance | |
| test.py:48:23:48:23 | ControlFlowNode for x | test.py:12:15:12:15 | ControlFlowNode for x | provenance | |
| test.py:48:23:48:23 | ControlFlowNode for x | test.py:48:13:48:24 | ControlFlowNode for normalize() | provenance | |
| test.py:48:23:48:23 | ControlFlowNode for x | test.py:48:13:48:24 | ControlFlowNode for normalize() | provenance | Config |
nodes
| flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
| flask_path_injection.py:1:26:1:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |

View File

@@ -0,0 +1,43 @@
edges
| flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:1:29:1:35 | ControlFlowNode for request | provenance | |
| flask_tests.py:1:29:1:35 | ControlFlowNode for request | flask_tests.py:9:18:9:24 | ControlFlowNode for request | provenance | |
| flask_tests.py:1:29:1:35 | ControlFlowNode for request | flask_tests.py:19:18:19:24 | ControlFlowNode for request | provenance | |
| flask_tests.py:1:29:1:35 | ControlFlowNode for request | flask_tests.py:20:36:20:42 | ControlFlowNode for request | provenance | |
| flask_tests.py:1:29:1:35 | ControlFlowNode for request | flask_tests.py:31:18:31:24 | ControlFlowNode for request | provenance | |
| flask_tests.py:9:5:9:14 | ControlFlowNode for rfs_header | flask_tests.py:13:17:13:26 | ControlFlowNode for rfs_header | provenance | |
| flask_tests.py:9:18:9:24 | ControlFlowNode for request | flask_tests.py:9:5:9:14 | ControlFlowNode for rfs_header | provenance | AdditionalTaintStep |
| flask_tests.py:19:18:19:24 | ControlFlowNode for request | flask_tests.py:20:36:20:61 | ControlFlowNode for Subscript | provenance | AdditionalTaintStep |
| flask_tests.py:20:36:20:42 | ControlFlowNode for request | flask_tests.py:20:36:20:61 | ControlFlowNode for Subscript | provenance | AdditionalTaintStep |
| flask_tests.py:31:5:31:14 | ControlFlowNode for rfs_header | flask_tests.py:33:11:33:20 | ControlFlowNode for rfs_header | provenance | |
| flask_tests.py:31:5:31:14 | ControlFlowNode for rfs_header | flask_tests.py:35:12:35:21 | ControlFlowNode for rfs_header | provenance | |
| flask_tests.py:31:18:31:24 | ControlFlowNode for request | flask_tests.py:31:5:31:14 | ControlFlowNode for rfs_header | provenance | AdditionalTaintStep |
| wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | wsgiref_tests.py:6:5:6:10 | ControlFlowNode for h_name | provenance | |
| wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | wsgiref_tests.py:7:5:7:9 | ControlFlowNode for h_val | provenance | |
| wsgiref_tests.py:6:5:6:10 | ControlFlowNode for h_name | wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | provenance | |
| wsgiref_tests.py:7:5:7:9 | ControlFlowNode for h_val | wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | provenance | |
nodes
| flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
| flask_tests.py:1:29:1:35 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_tests.py:9:5:9:14 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_tests.py:9:18:9:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_tests.py:13:17:13:26 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_tests.py:19:18:19:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_tests.py:20:36:20:42 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_tests.py:20:36:20:61 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| flask_tests.py:31:5:31:14 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_tests.py:31:18:31:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_tests.py:33:11:33:20 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| flask_tests.py:35:12:35:21 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
| wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | semmle.label | ControlFlowNode for environ |
| wsgiref_tests.py:6:5:6:10 | ControlFlowNode for h_name | semmle.label | ControlFlowNode for h_name |
| wsgiref_tests.py:7:5:7:9 | ControlFlowNode for h_val | semmle.label | ControlFlowNode for h_val |
| wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | semmle.label | ControlFlowNode for h_name |
| wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | semmle.label | ControlFlowNode for h_val |
subpaths
#select
| flask_tests.py:13:17:13:26 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:13:17:13:26 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value |
| flask_tests.py:20:36:20:61 | ControlFlowNode for Subscript | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:20:36:20:61 | ControlFlowNode for Subscript | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value |
| flask_tests.py:33:11:33:20 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:33:11:33:20 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value |
| flask_tests.py:35:12:35:21 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:35:12:35:21 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value |
| wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | This HTTP header is constructed from a $@. | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | user-provided value |
| wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | This HTTP header is constructed from a $@. | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | user-provided value |

View File

@@ -0,0 +1 @@
Security/CWE-113/HeaderInjection.ql

View File

@@ -0,0 +1,40 @@
from flask import Response, request, Flask, make_response
from werkzeug.datastructures import Headers
app = Flask(__name__)
@app.route('/werkzeug_headers')
def werkzeug_headers():
rfs_header = request.args["rfs_header"]
response = Response()
headers = Headers()
headers.add("HeaderName", rfs_header) # GOOD: Newlines are rejected from header value.
headers.add(rfs_header, "HeaderValue") # BAD: User controls header name.
response.headers = headers
return response
@app.route("/flask_make_response_header_arg2")
def flask_make_response_header_arg2():
rfs_header = request.args["rfs_header"]
resp = make_response("hello", {request.args["rfs_header"]: "HeaderValue"}) # BAD
return resp
@app.route("/flask_escaped")
def flask_escaped():
rfs_header = request.args["rfs_header"]
resp = make_response("hello", {rfs_header.replace("\n", ""): "HeaderValue"}) # GOOD - Newlines are removed from the input.
return resp
@app.route("/flask_extend")
def flask_extend():
rfs_header = request.args["rfs_header"]
response = Response()
h1 = {rfs_header: "HeaderValue"}
response.headers.extend(h1) # BAD
h2 = [(rfs_header, "HeaderValue")]
response.headers.extend(h2) # BAD
return response
# if __name__ == "__main__":
# app.run(debug=True)

Some files were not shown because too many files have changed in this diff Show More