mirror of
https://github.com/github/codeql.git
synced 2026-04-25 08:45:14 +02:00
fix conflict
This commit is contained in:
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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__"],
|
||||
)
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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" ]
|
||||
|
||||
@@ -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
|
||||
|
||||
5
python/ql/lib/change-notes/released/0.12.1.md
Normal file
5
python/ql/lib/change-notes/released/0.12.1.md
Normal 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.
|
||||
14
python/ql/lib/change-notes/released/1.0.0.md
Normal file
14
python/ql/lib/change-notes/released/1.0.0.md
Normal 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`.
|
||||
3
python/ql/lib/change-notes/released/1.0.1.md
Normal file
3
python/ql/lib/change-notes/released/1.0.1.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.0.1
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.12.0
|
||||
lastReleaseVersion: 1.0.1
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
123
python/ql/lib/semmle/python/frameworks/Gradio.qll
Normal file
123
python/ql/lib/semmle/python/frameworks/Gradio.qll
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
64
python/ql/lib/semmle/python/frameworks/Opml.qll
Normal file
64
python/ql/lib/semmle/python/frameworks/Opml.qll
Normal 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 }
|
||||
}
|
||||
}
|
||||
299
python/ql/lib/semmle/python/frameworks/Pyramid.qll
Normal file
299
python/ql/lib/semmle/python/frameworks/Pyramid.qll
Normal 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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
@@ -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")]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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(_)
|
||||
|
||||
41
python/ql/src/Security/CWE-113/HeaderInjection.qhelp
Normal file
41
python/ql/src/Security/CWE-113/HeaderInjection.qhelp
Normal 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>
|
||||
@@ -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
|
||||
17
python/ql/src/Security/CWE-113/examples/header_injection.py
Normal file
17
python/ql/src/Security/CWE-113/examples/header_injection.py
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
4
python/ql/src/change-notes/2024-06-04-ssrf-sanitizers.md
Normal file
4
python/ql/src/change-notes/2024-06-04-ssrf-sanitizers.md
Normal 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.
|
||||
5
python/ql/src/change-notes/released/0.9.16.md
Normal file
5
python/ql/src/change-notes/released/0.9.16.md
Normal 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.
|
||||
9
python/ql/src/change-notes/released/1.0.0.md
Normal file
9
python/ql/src/change-notes/released/1.0.0.md
Normal 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.
|
||||
5
python/ql/src/change-notes/released/1.0.1.md
Normal file
5
python/ql/src/change-notes/released/1.0.1.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 1.0.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added models for `opml` library.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.9.15
|
||||
lastReleaseVersion: 1.0.1
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()`.
|
||||
*
|
||||
|
||||
@@ -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()`.
|
||||
*
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/python-queries
|
||||
version: 0.9.16-dev
|
||||
version: 1.0.2-dev
|
||||
groups:
|
||||
- python
|
||||
- queries
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 | |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
@@ -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 | |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
@@ -1 +0,0 @@
|
||||
experimental/Security/CWE-113/HeaderInjection.ql
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 | |
|
||||
|
||||
@@ -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 | |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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. |
|
||||
|
||||
@@ -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;"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import flask
|
||||
|
||||
class MySessionInterface(flask.sessions.SessionInterface):
|
||||
def open_session(self, app, request):
|
||||
ensure_tainted(request) # $tainted
|
||||
@@ -0,0 +1,2 @@
|
||||
testFailures
|
||||
failures
|
||||
@@ -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()
|
||||
@@ -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>
|
||||
@@ -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 |
|
||||
@@ -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()
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-022/PathInjection.ql
|
||||
@@ -0,0 +1,2 @@
|
||||
testFailures
|
||||
failures
|
||||
@@ -0,0 +1,2 @@
|
||||
import python
|
||||
import experimental.meta.ConceptsTest
|
||||
@@ -0,0 +1,4 @@
|
||||
argumentToEnsureNotTaintedNotMarkedAsSpurious
|
||||
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
|
||||
testFailures
|
||||
failures
|
||||
@@ -0,0 +1,2 @@
|
||||
import experimental.meta.InlineTaintTest
|
||||
import MakeInlineTaintTest<TestTaintTrackingConfig>
|
||||
153
python/ql/test/library-tests/frameworks/pyramid/pyramid_test.py
Normal file
153
python/ql/test/library-tests/frameworks/pyramid/pyramid_test.py
Normal 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()
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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. |
|
||||
|
||||
@@ -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)
|
||||
|
||||
34
python/ql/test/query-tests/Imports/unused/pytest_fixtures.py
Normal file
34
python/ql/test/query-tests/Imports/unused/pytest_fixtures.py
Normal 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
|
||||
@@ -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 | |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-113/HeaderInjection.ql
|
||||
@@ -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
Reference in New Issue
Block a user