mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
Merge pull request #6155 from RasmusWL/port-cleartext-queries
Python: Port cleartext queries
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* Added `HTTP::Server::CookieWrite` concept for statements that sets a cookie in an HTTP response, along with modeling of this in supported web frameworks (aiohttp/flask/django/tornado/twisted).
|
||||
@@ -14,25 +14,13 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
import semmle.python.dataflow.TaintTracking
|
||||
import semmle.python.security.SensitiveData
|
||||
import semmle.python.security.ClearText
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
import DataFlow::PathGraph
|
||||
import semmle.python.security.dataflow.CleartextLogging::CleartextLogging
|
||||
|
||||
class CleartextLoggingConfiguration extends TaintTracking::Configuration {
|
||||
CleartextLoggingConfiguration() { this = "ClearTextLogging" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src, TaintKind kind) {
|
||||
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
|
||||
sink.asCfgNode() instanceof ClearTextLogging::Sink and
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
}
|
||||
|
||||
from CleartextLoggingConfiguration config, TaintedPathSource source, TaintedPathSink sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getSink(), source, sink, "Sensitive data returned by $@ is logged here.",
|
||||
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, string classification
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
classification = source.getNode().(Source).getClassification()
|
||||
select sink.getNode(), source, sink, "$@ is logged here.", source.getNode(),
|
||||
"Sensitive data (" + classification + ")"
|
||||
|
||||
@@ -14,25 +14,13 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
import semmle.python.dataflow.TaintTracking
|
||||
import semmle.python.security.SensitiveData
|
||||
import semmle.python.security.ClearText
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
import DataFlow::PathGraph
|
||||
import semmle.python.security.dataflow.CleartextStorage::CleartextStorage
|
||||
|
||||
class CleartextStorageConfiguration extends TaintTracking::Configuration {
|
||||
CleartextStorageConfiguration() { this = "ClearTextStorage" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src, TaintKind kind) {
|
||||
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
|
||||
sink.asCfgNode() instanceof ClearTextStorage::Sink and
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
}
|
||||
|
||||
from CleartextStorageConfiguration config, TaintedPathSource source, TaintedPathSink sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getSink(), source, sink, "Sensitive data from $@ is stored here.", source.getSource(),
|
||||
source.getCfgNode().(SensitiveData::Source).repr()
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, string classification
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
classification = source.getNode().(Source).getClassification()
|
||||
select sink.getNode(), source, sink, "$@ is stored here.", source.getNode(),
|
||||
"Sensitive data (" + classification + ")"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @name Clear-text logging of sensitive information
|
||||
* @description OLD QUERY: Logging sensitive information without encryption or hashing can
|
||||
* expose it to an attacker.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @id py/old/clear-text-logging-sensitive-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
import semmle.python.dataflow.TaintTracking
|
||||
import semmle.python.security.SensitiveData
|
||||
import semmle.python.security.ClearText
|
||||
|
||||
class CleartextLoggingConfiguration extends TaintTracking::Configuration {
|
||||
CleartextLoggingConfiguration() { this = "ClearTextLogging" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src, TaintKind kind) {
|
||||
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
|
||||
sink.asCfgNode() instanceof ClearTextLogging::Sink and
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
}
|
||||
|
||||
from CleartextLoggingConfiguration config, TaintedPathSource source, TaintedPathSink sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getSink(), source, sink, "Sensitive data returned by $@ is logged here.",
|
||||
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @name Clear-text storage of sensitive information
|
||||
* @description OLD QUERY: Sensitive information stored without encryption or hashing can expose it to an
|
||||
* attacker.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @id py/old/clear-text-storage-sensitive-data
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
import semmle.python.dataflow.TaintTracking
|
||||
import semmle.python.security.SensitiveData
|
||||
import semmle.python.security.ClearText
|
||||
|
||||
class CleartextStorageConfiguration extends TaintTracking::Configuration {
|
||||
CleartextStorageConfiguration() { this = "ClearTextStorage" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src, TaintKind kind) {
|
||||
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
|
||||
sink.asCfgNode() instanceof ClearTextStorage::Sink and
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
}
|
||||
|
||||
from CleartextStorageConfiguration config, TaintedPathSource source, TaintedPathSink sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getSink(), source, sink, "Sensitive data from $@ is stored here.", source.getSource(),
|
||||
source.getCfgNode().(SensitiveData::Source).repr()
|
||||
@@ -72,6 +72,39 @@ module FileSystemAccess {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that writes data to the file system access.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `FileSystemWriteAccess::Range` instead.
|
||||
*/
|
||||
class FileSystemWriteAccess extends FileSystemAccess {
|
||||
override FileSystemWriteAccess::Range range;
|
||||
|
||||
/**
|
||||
* Gets a node that represents data to be written to the file system (possibly with
|
||||
* some transformation happening before it is written, like JSON encoding).
|
||||
*/
|
||||
DataFlow::Node getADataNode() { result = range.getADataNode() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new file system writes. */
|
||||
module FileSystemWriteAccess {
|
||||
/**
|
||||
* A data flow node that writes data to the file system access.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `FileSystemWriteAccess` instead.
|
||||
*/
|
||||
abstract class Range extends FileSystemAccess::Range {
|
||||
/**
|
||||
* Gets a node that represents data to be written to the file system (possibly with
|
||||
* some transformation happening before it is written, like JSON encoding).
|
||||
*/
|
||||
abstract DataFlow::Node getADataNode();
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides classes for modeling path-related APIs. */
|
||||
module Path {
|
||||
/**
|
||||
@@ -235,6 +268,35 @@ private class EncodingAdditionalTaintStep extends TaintTracking::AdditionalTaint
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that logs data.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `Logging::Range` instead.
|
||||
*/
|
||||
class Logging extends DataFlow::Node {
|
||||
Logging::Range range;
|
||||
|
||||
Logging() { this = range }
|
||||
|
||||
/** Gets an input that is logged. */
|
||||
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new logging mechanisms. */
|
||||
module Logging {
|
||||
/**
|
||||
* A data-flow node that logs data.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `Logging` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets an input that is logged. */
|
||||
abstract DataFlow::Node getAnInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that dynamically executes Python code.
|
||||
*
|
||||
@@ -594,6 +656,62 @@ module HTTP {
|
||||
abstract DataFlow::Node getRedirectLocation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that sets a cookie in an HTTP response.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `HTTP::CookieWrite::Range` instead.
|
||||
*/
|
||||
class CookieWrite extends DataFlow::Node {
|
||||
CookieWrite::Range range;
|
||||
|
||||
CookieWrite() { this = range }
|
||||
|
||||
/**
|
||||
* Gets the argument, if any, specifying the raw cookie header.
|
||||
*/
|
||||
DataFlow::Node getHeaderArg() { result = range.getHeaderArg() }
|
||||
|
||||
/**
|
||||
* Gets the argument, if any, specifying the cookie name.
|
||||
*/
|
||||
DataFlow::Node getNameArg() { result = range.getNameArg() }
|
||||
|
||||
/**
|
||||
* Gets the argument, if any, specifying the cookie value.
|
||||
*/
|
||||
DataFlow::Node getValueArg() { result = range.getValueArg() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new cookie writes on HTTP responses. */
|
||||
module CookieWrite {
|
||||
/**
|
||||
* A data-flow node that sets a cookie in an HTTP response.
|
||||
*
|
||||
* Note: we don't require that this redirect must be sent to a client (a kind of
|
||||
* "if a tree falls in a forest and nobody hears it" situation).
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `HttpResponse` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the argument, if any, specifying the raw cookie header.
|
||||
*/
|
||||
abstract DataFlow::Node getHeaderArg();
|
||||
|
||||
/**
|
||||
* Gets the argument, if any, specifying the cookie name.
|
||||
*/
|
||||
abstract DataFlow::Node getNameArg();
|
||||
|
||||
/**
|
||||
* Gets the argument, if any, specifying the cookie value.
|
||||
*/
|
||||
abstract DataFlow::Node getValueArg();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -295,6 +295,36 @@ module AiohttpWebModel {
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `aiohttp.web.Response` class
|
||||
*
|
||||
* See https://docs.aiohttp.org/en/stable/web_reference.html#response-classes
|
||||
*/
|
||||
module Response {
|
||||
/**
|
||||
* A source of instances of `aiohttp.web.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 `Response::instance()` predicate to get
|
||||
* references to instances of `aiohttp.web.Response`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** Gets a reference to an instance of `aiohttp.web.Response`. */
|
||||
private DataFlow::LocalSourceNode 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 `aiohttp.web.Response`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `aiohttp.StreamReader` class
|
||||
*
|
||||
@@ -488,35 +518,46 @@ module AiohttpWebModel {
|
||||
* - https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-exceptions
|
||||
*/
|
||||
class AiohttpWebResponseInstantiation extends HTTP::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
Response::InstanceSource, DataFlow::CallCfgNode {
|
||||
API::Node apiNode;
|
||||
|
||||
AiohttpWebResponseInstantiation() {
|
||||
this = API::moduleImport("aiohttp").getMember("web").getMember("Response").getACall()
|
||||
or
|
||||
exists(string httpExceptionClassName |
|
||||
httpExceptionClassName in [
|
||||
"HTTPException", "HTTPSuccessful", "HTTPOk", "HTTPCreated", "HTTPAccepted",
|
||||
"HTTPNonAuthoritativeInformation", "HTTPNoContent", "HTTPResetContent",
|
||||
"HTTPPartialContent", "HTTPRedirection", "HTTPMultipleChoices", "HTTPMovedPermanently",
|
||||
"HTTPFound", "HTTPSeeOther", "HTTPNotModified", "HTTPUseProxy", "HTTPTemporaryRedirect",
|
||||
"HTTPPermanentRedirect", "HTTPError", "HTTPClientError", "HTTPBadRequest",
|
||||
"HTTPUnauthorized", "HTTPPaymentRequired", "HTTPForbidden", "HTTPNotFound",
|
||||
"HTTPMethodNotAllowed", "HTTPNotAcceptable", "HTTPProxyAuthenticationRequired",
|
||||
"HTTPRequestTimeout", "HTTPConflict", "HTTPGone", "HTTPLengthRequired",
|
||||
"HTTPPreconditionFailed", "HTTPRequestEntityTooLarge", "HTTPRequestURITooLong",
|
||||
"HTTPUnsupportedMediaType", "HTTPRequestRangeNotSatisfiable", "HTTPExpectationFailed",
|
||||
"HTTPMisdirectedRequest", "HTTPUnprocessableEntity", "HTTPFailedDependency",
|
||||
"HTTPUpgradeRequired", "HTTPPreconditionRequired", "HTTPTooManyRequests",
|
||||
"HTTPRequestHeaderFieldsTooLarge", "HTTPUnavailableForLegalReasons", "HTTPServerError",
|
||||
"HTTPInternalServerError", "HTTPNotImplemented", "HTTPBadGateway",
|
||||
"HTTPServiceUnavailable", "HTTPGatewayTimeout", "HTTPVersionNotSupported",
|
||||
"HTTPVariantAlsoNegotiates", "HTTPInsufficientStorage", "HTTPNotExtended",
|
||||
"HTTPNetworkAuthenticationRequired"
|
||||
] and
|
||||
this =
|
||||
API::moduleImport("aiohttp").getMember("web").getMember(httpExceptionClassName).getACall()
|
||||
this = apiNode.getACall() and
|
||||
(
|
||||
apiNode = API::moduleImport("aiohttp").getMember("web").getMember("Response")
|
||||
or
|
||||
exists(string httpExceptionClassName |
|
||||
httpExceptionClassName in [
|
||||
"HTTPException", "HTTPSuccessful", "HTTPOk", "HTTPCreated", "HTTPAccepted",
|
||||
"HTTPNonAuthoritativeInformation", "HTTPNoContent", "HTTPResetContent",
|
||||
"HTTPPartialContent", "HTTPRedirection", "HTTPMultipleChoices",
|
||||
"HTTPMovedPermanently", "HTTPFound", "HTTPSeeOther", "HTTPNotModified",
|
||||
"HTTPUseProxy", "HTTPTemporaryRedirect", "HTTPPermanentRedirect", "HTTPError",
|
||||
"HTTPClientError", "HTTPBadRequest", "HTTPUnauthorized", "HTTPPaymentRequired",
|
||||
"HTTPForbidden", "HTTPNotFound", "HTTPMethodNotAllowed", "HTTPNotAcceptable",
|
||||
"HTTPProxyAuthenticationRequired", "HTTPRequestTimeout", "HTTPConflict", "HTTPGone",
|
||||
"HTTPLengthRequired", "HTTPPreconditionFailed", "HTTPRequestEntityTooLarge",
|
||||
"HTTPRequestURITooLong", "HTTPUnsupportedMediaType", "HTTPRequestRangeNotSatisfiable",
|
||||
"HTTPExpectationFailed", "HTTPMisdirectedRequest", "HTTPUnprocessableEntity",
|
||||
"HTTPFailedDependency", "HTTPUpgradeRequired", "HTTPPreconditionRequired",
|
||||
"HTTPTooManyRequests", "HTTPRequestHeaderFieldsTooLarge",
|
||||
"HTTPUnavailableForLegalReasons", "HTTPServerError", "HTTPInternalServerError",
|
||||
"HTTPNotImplemented", "HTTPBadGateway", "HTTPServiceUnavailable",
|
||||
"HTTPGatewayTimeout", "HTTPVersionNotSupported", "HTTPVariantAlsoNegotiates",
|
||||
"HTTPInsufficientStorage", "HTTPNotExtended", "HTTPNetworkAuthenticationRequired"
|
||||
] and
|
||||
apiNode = API::moduleImport("aiohttp").getMember("web").getMember(httpExceptionClassName)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Get the internal `API::Node` that this is call of.
|
||||
*/
|
||||
API::Node getApiNode() { result = apiNode }
|
||||
|
||||
override DataFlow::Node getBody() {
|
||||
result in [this.getArgByName("text"), this.getArgByName("body")]
|
||||
}
|
||||
@@ -534,6 +575,11 @@ module AiohttpWebModel {
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets an HTTP response instance. */
|
||||
private API::Node aiohttpResponseInstance() {
|
||||
result = any(AiohttpWebResponseInstantiation call).getApiNode().getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* An instantiation of aiohttp.web HTTP redirect exception.
|
||||
*
|
||||
@@ -559,4 +605,61 @@ module AiohttpWebModel {
|
||||
result in [this.getArg(0), this.getArgByName("location")]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `set_cookie` on a HTTP Response.
|
||||
*/
|
||||
class AiohttpResponseSetCookieCall extends HTTP::Server::CookieWrite::Range, DataFlow::CallCfgNode {
|
||||
AiohttpResponseSetCookieCall() {
|
||||
this = aiohttpResponseInstance().getMember("set_cookie").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("name")] }
|
||||
|
||||
override DataFlow::Node getValueArg() { result in [this.getArg(1), this.getArgByName("value")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `del_cookie` on a HTTP Response.
|
||||
*/
|
||||
class AiohttpResponseDelCookieCall extends HTTP::Server::CookieWrite::Range, DataFlow::CallCfgNode {
|
||||
AiohttpResponseDelCookieCall() {
|
||||
this = aiohttpResponseInstance().getMember("del_cookie").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("name")] }
|
||||
|
||||
override DataFlow::Node getValueArg() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of the `cookies` attribute on a HTTP response, such as
|
||||
* `response.cookies[name] = value`.
|
||||
*/
|
||||
class AiohttpResponseCookieSubscriptWrite extends HTTP::Server::CookieWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
AiohttpResponseCookieSubscriptWrite() {
|
||||
exists(SubscriptNode subscript |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
subscript.getObject() = aiohttpResponseInstance().getMember("cookies").getAUse().asCfgNode() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1350,9 +1350,9 @@ private module PrivateDjango {
|
||||
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponse.write
|
||||
*/
|
||||
class HttpResponseWriteCall extends HTTP::Server::HttpResponse::Range, DataFlow::CallCfgNode {
|
||||
HTTP::Server::HttpResponse::Range instance;
|
||||
django::http::response::HttpResponse::InstanceSource instance;
|
||||
|
||||
HttpResponseWriteCall() { node.getFunction() = write(instance).asCfgNode() }
|
||||
HttpResponseWriteCall() { this.getFunction() = write(instance) }
|
||||
|
||||
override DataFlow::Node getBody() {
|
||||
result in [this.getArg(0), this.getArgByName("content")]
|
||||
@@ -1364,6 +1364,77 @@ private module PrivateDjango {
|
||||
|
||||
override string getMimetypeDefault() { result = instance.getMimetypeDefault() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `set_cookie` on a HTTP Response.
|
||||
*/
|
||||
class DjangoResponseSetCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DjangoResponseSetCookieCall() {
|
||||
this.calls(django::http::response::HttpResponse::instance(), "set_cookie")
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() {
|
||||
result in [this.getArg(0), this.getArgByName("key")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getValueArg() {
|
||||
result in [this.getArg(1), this.getArgByName("value")]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `delete_cookie` on a HTTP Response.
|
||||
*/
|
||||
class DjangoResponseDeleteCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DjangoResponseDeleteCookieCall() {
|
||||
this.calls(django::http::response::HttpResponse::instance(), "delete_cookie")
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() {
|
||||
result in [this.getArg(0), this.getArgByName("key")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getValueArg() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of the `cookies` attribute on a HTTP response, such as
|
||||
* `response.cookies[name] = value`.
|
||||
*/
|
||||
class DjangoResponseCookieSubscriptWrite extends HTTP::Server::CookieWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
DjangoResponseCookieSubscriptWrite() {
|
||||
exists(SubscriptNode subscript, DataFlow::AttrRead cookieLookup |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
cookieLookup.getAttributeName() = "cookies" and
|
||||
cookieLookup.getObject() = django::http::response::HttpResponse::instance() and
|
||||
exists(DataFlow::Node subscriptObj |
|
||||
subscriptObj.asCfgNode() = subscript.getObject()
|
||||
|
|
||||
cookieLookup.flowsTo(subscriptObj)
|
||||
) and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -465,4 +465,39 @@ module Flask {
|
||||
result = "text/html"
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// flask.Response related
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* A call to `set_cookie` on a Flask HTTP Response.
|
||||
*
|
||||
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.Response.set_cookie
|
||||
*/
|
||||
class FlaskResponseSetCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
FlaskResponseSetCookieCall() { this.calls(Flask::Response::instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("key")] }
|
||||
|
||||
override DataFlow::Node getValueArg() { result in [this.getArg(1), this.getArgByName("value")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `delete_cookie` on a Flask HTTP Response.
|
||||
*
|
||||
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.Response.delete_cookie
|
||||
*/
|
||||
class FlaskResponseDeleteCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
FlaskResponseDeleteCookieCall() { this.calls(Flask::Response::instance(), "delete_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("key")] }
|
||||
|
||||
override DataFlow::Node getValueArg() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,23 +383,74 @@ private module Stdlib {
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a reference to the builtin `open` function */
|
||||
private API::Node getOpenFunctionRef() {
|
||||
result = API::builtin("open")
|
||||
or
|
||||
// io.open is a special case, since it is an alias for the builtin `open`
|
||||
result = API::moduleImport("io").getMember("open")
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the builtin `open` function.
|
||||
* See https://docs.python.org/3/library/functions.html#open
|
||||
*/
|
||||
private class OpenCall extends FileSystemAccess::Range, DataFlow::CallCfgNode {
|
||||
OpenCall() {
|
||||
this = API::builtin("open").getACall()
|
||||
or
|
||||
// io.open is a special case, since it is an alias for the builtin `open`
|
||||
this = API::moduleImport("io").getMember("open").getACall()
|
||||
}
|
||||
OpenCall() { this = getOpenFunctionRef().getACall() }
|
||||
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
result in [this.getArg(0), this.getArgByName("file")]
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a reference to an open file. */
|
||||
private DataFlow::LocalSourceNode openFile(DataFlow::TypeTracker t, FileSystemAccess openCall) {
|
||||
t.start() and
|
||||
result = openCall and
|
||||
(
|
||||
openCall instanceof OpenCall
|
||||
or
|
||||
openCall instanceof PathLibOpenCall
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = openFile(t2, openCall).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an open file. */
|
||||
private DataFlow::Node openFile(FileSystemAccess openCall) {
|
||||
openFile(DataFlow::TypeTracker::end(), openCall).flowsTo(result)
|
||||
}
|
||||
|
||||
/** Gets a reference to the `write` or `writelines` method on an open file. */
|
||||
private DataFlow::LocalSourceNode writeMethodOnOpenFile(
|
||||
DataFlow::TypeTracker t, FileSystemAccess openCall
|
||||
) {
|
||||
t.startInAttr(["write", "writelines"]) and
|
||||
result = openFile(openCall)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = writeMethodOnOpenFile(t2, openCall).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `write` or `writelines` method on an open file. */
|
||||
private DataFlow::Node writeMethodOnOpenFile(FileSystemAccess openCall) {
|
||||
writeMethodOnOpenFile(DataFlow::TypeTracker::end(), openCall).flowsTo(result)
|
||||
}
|
||||
|
||||
/** A call to the `write` or `writelines` method on an opened file, such as `open("foo", "w").write(...)`. */
|
||||
private class WriteCallOnOpenFile extends FileSystemWriteAccess::Range, DataFlow::CallCfgNode {
|
||||
FileSystemAccess openCall;
|
||||
|
||||
WriteCallOnOpenFile() { this.getFunction() = writeMethodOnOpenFile(openCall) }
|
||||
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
// best effort attempt to give the path argument, that was initially given to the
|
||||
// `open` call.
|
||||
result = openCall.getAPathArgument()
|
||||
}
|
||||
|
||||
override DataFlow::Node getADataNode() { result in [this.getArg(0), this.getArgByName("data")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* An exec statement (only Python 2).
|
||||
* See https://docs.python.org/2/reference/simple_stmts.html#the-exec-statement.
|
||||
@@ -1001,11 +1052,14 @@ private module Stdlib {
|
||||
/** Gets a reference to a `pathlib.Path` object. */
|
||||
DataFlow::LocalSourceNode pathlibPath() { result = pathlibPath(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** A file system access from a `pathlib.Path` method call. */
|
||||
private class PathlibFileAccess extends FileSystemAccess::Range, DataFlow::CallCfgNode {
|
||||
DataFlow::AttrRead fileAccess;
|
||||
string attrbuteName;
|
||||
|
||||
PathlibFileAccess() {
|
||||
fileAccess.getAttributeName() in [
|
||||
attrbuteName = fileAccess.getAttributeName() and
|
||||
attrbuteName in [
|
||||
"stat", "chmod", "exists", "expanduser", "glob", "group", "is_dir", "is_file", "is_mount",
|
||||
"is_symlink", "is_socket", "is_fifo", "is_block_device", "is_char_device", "iter_dir",
|
||||
"lchmod", "lstat", "mkdir", "open", "owner", "read_bytes", "read_text", "readlink",
|
||||
@@ -1019,6 +1073,18 @@ private module Stdlib {
|
||||
override DataFlow::Node getAPathArgument() { result = fileAccess.getObject() }
|
||||
}
|
||||
|
||||
/** A file system write from a `pathlib.Path` method call. */
|
||||
private class PathlibFileWrites extends PathlibFileAccess, FileSystemWriteAccess::Range {
|
||||
PathlibFileWrites() { attrbuteName in ["write_bytes", "write_text"] }
|
||||
|
||||
override DataFlow::Node getADataNode() { result in [this.getArg(0), this.getArgByName("data")] }
|
||||
}
|
||||
|
||||
/** A call to the `open` method on a `pathlib.Path` instance. */
|
||||
private class PathLibOpenCall extends PathlibFileAccess {
|
||||
PathLibOpenCall() { attrbuteName = "open" }
|
||||
}
|
||||
|
||||
/** An additional taint steps for objects of type `pathlib.Path` */
|
||||
private class PathlibPathTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
@@ -1076,118 +1142,175 @@ private module Stdlib {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// hashlib
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a call to `hashlib.new` with `algorithmName` as the first argument. */
|
||||
private DataFlow::CallCfgNode hashlibNewCall(string algorithmName) {
|
||||
exists(DataFlow::Node nameArg |
|
||||
result = API::moduleImport("hashlib").getMember("new").getACall() and
|
||||
nameArg in [result.getArg(0), result.getArgByName("name")] and
|
||||
exists(StrConst str |
|
||||
nameArg.getALocalSource() = DataFlow::exprNode(str) and
|
||||
algorithmName = str.getText()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a reference to the result of calling `hashlib.new` with `algorithmName` as the first argument. */
|
||||
private DataFlow::LocalSourceNode hashlibNewResult(DataFlow::TypeTracker t, string algorithmName) {
|
||||
t.start() and
|
||||
result = hashlibNewCall(algorithmName)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = hashlibNewResult(t2, algorithmName).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the result of calling `hashlib.new` with `algorithmName` as the first argument. */
|
||||
DataFlow::Node hashlibNewResult(string algorithmName) {
|
||||
hashlibNewResult(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation by supplying initial data when calling the `hashlib.new` function.
|
||||
*/
|
||||
class HashlibNewCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode {
|
||||
string hashName;
|
||||
|
||||
HashlibNewCall() {
|
||||
this = hashlibNewCall(hashName) and
|
||||
exists([this.getArg(1), this.getArgByName("data")])
|
||||
}
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(1), this.getArgByName("data")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation by using the `update` method on the result of calling the `hashlib.new` function.
|
||||
*/
|
||||
class HashlibNewUpdateCall extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
string hashName;
|
||||
|
||||
HashlibNewUpdateCall() {
|
||||
exists(DataFlow::AttrRead attr |
|
||||
attr.getObject() = hashlibNewResult(hashName) and
|
||||
this.getFunction() = attr and
|
||||
attr.getAttributeName() = "update"
|
||||
// ---------------------------------------------------------------------------
|
||||
// hashlib
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a call to `hashlib.new` with `algorithmName` as the first argument. */
|
||||
private DataFlow::CallCfgNode hashlibNewCall(string algorithmName) {
|
||||
exists(DataFlow::Node nameArg |
|
||||
result = API::moduleImport("hashlib").getMember("new").getACall() and
|
||||
nameArg in [result.getArg(0), result.getArgByName("name")] and
|
||||
exists(StrConst str |
|
||||
nameArg.getALocalSource() = DataFlow::exprNode(str) and
|
||||
algorithmName = str.getText()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation from the `hashlib` package using one of the predefined classes
|
||||
* (such as `hashlib.md5`). `hashlib.new` is not included, since it is handled by
|
||||
* `HashlibNewCall` and `HashlibNewUpdateCall`.
|
||||
*/
|
||||
abstract class HashlibGenericHashOperation extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
string hashName;
|
||||
API::Node hashClass;
|
||||
|
||||
bindingset[this]
|
||||
HashlibGenericHashOperation() {
|
||||
not hashName = "new" and
|
||||
hashClass = API::moduleImport("hashlib").getMember(hashName)
|
||||
}
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation from the `hashlib` package using one of the predefined classes
|
||||
* (such as `hashlib.md5`), by calling its' `update` mehtod.
|
||||
*/
|
||||
class HashlibHashClassUpdateCall extends HashlibGenericHashOperation {
|
||||
HashlibHashClassUpdateCall() { this = hashClass.getReturn().getMember("update").getACall() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation from the `hashlib` package using one of the predefined classes
|
||||
* (such as `hashlib.md5`), by passing data to when instantiating the class.
|
||||
*/
|
||||
class HashlibDataPassedToHashClass extends HashlibGenericHashOperation {
|
||||
HashlibDataPassedToHashClass() {
|
||||
// we only want to model calls to classes such as `hashlib.md5()` if initial data
|
||||
// is passed as an argument
|
||||
this = hashClass.getACall() and
|
||||
exists([this.getArg(0), this.getArgByName("string")])
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() {
|
||||
result = this.getArg(0)
|
||||
/** Gets a reference to the result of calling `hashlib.new` with `algorithmName` as the first argument. */
|
||||
private DataFlow::LocalSourceNode hashlibNewResult(DataFlow::TypeTracker t, string algorithmName) {
|
||||
t.start() and
|
||||
result = hashlibNewCall(algorithmName)
|
||||
or
|
||||
// in Python 3.9, you are allowed to use `hashlib.md5(string=<bytes-like>)`.
|
||||
result = this.getArgByName("string")
|
||||
exists(DataFlow::TypeTracker t2 | result = hashlibNewResult(t2, algorithmName).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the result of calling `hashlib.new` with `algorithmName` as the first argument. */
|
||||
DataFlow::Node hashlibNewResult(string algorithmName) {
|
||||
hashlibNewResult(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation by supplying initial data when calling the `hashlib.new` function.
|
||||
*/
|
||||
class HashlibNewCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode {
|
||||
string hashName;
|
||||
|
||||
HashlibNewCall() {
|
||||
this = hashlibNewCall(hashName) and
|
||||
exists([this.getArg(1), this.getArgByName("data")])
|
||||
}
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(1), this.getArgByName("data")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation by using the `update` method on the result of calling the `hashlib.new` function.
|
||||
*/
|
||||
class HashlibNewUpdateCall extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
string hashName;
|
||||
|
||||
HashlibNewUpdateCall() {
|
||||
exists(DataFlow::AttrRead attr |
|
||||
attr.getObject() = hashlibNewResult(hashName) and
|
||||
this.getFunction() = attr and
|
||||
attr.getAttributeName() = "update"
|
||||
)
|
||||
}
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation from the `hashlib` package using one of the predefined classes
|
||||
* (such as `hashlib.md5`). `hashlib.new` is not included, since it is handled by
|
||||
* `HashlibNewCall` and `HashlibNewUpdateCall`.
|
||||
*/
|
||||
abstract class HashlibGenericHashOperation extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
string hashName;
|
||||
API::Node hashClass;
|
||||
|
||||
bindingset[this]
|
||||
HashlibGenericHashOperation() {
|
||||
not hashName = "new" and
|
||||
hashClass = API::moduleImport("hashlib").getMember(hashName)
|
||||
}
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation from the `hashlib` package using one of the predefined classes
|
||||
* (such as `hashlib.md5`), by calling its' `update` mehtod.
|
||||
*/
|
||||
class HashlibHashClassUpdateCall extends HashlibGenericHashOperation {
|
||||
HashlibHashClassUpdateCall() { this = hashClass.getReturn().getMember("update").getACall() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing operation from the `hashlib` package using one of the predefined classes
|
||||
* (such as `hashlib.md5`), by passing data to when instantiating the class.
|
||||
*/
|
||||
class HashlibDataPassedToHashClass extends HashlibGenericHashOperation {
|
||||
HashlibDataPassedToHashClass() {
|
||||
// we only want to model calls to classes such as `hashlib.md5()` if initial data
|
||||
// is passed as an argument
|
||||
this = hashClass.getACall() and
|
||||
exists([this.getArg(0), this.getArgByName("string")])
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() {
|
||||
result = this.getArg(0)
|
||||
or
|
||||
// in Python 3.9, you are allowed to use `hashlib.md5(string=<bytes-like>)`.
|
||||
result = this.getArgByName("string")
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// logging
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* Provides models for the `logging.Logger` class and subclasses.
|
||||
*
|
||||
* See https://docs.python.org/3.9/library/logging.html#logging.Logger.
|
||||
*/
|
||||
module Logger {
|
||||
/** Gets a reference to the `logging.Logger` class or any subclass. */
|
||||
API::Node subclassRef() {
|
||||
result = API::moduleImport("logging").getMember("Logger").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `logging.Logger` or any subclass. */
|
||||
API::Node instance() {
|
||||
result = subclassRef().getReturn()
|
||||
or
|
||||
result = API::moduleImport("logging").getMember("root")
|
||||
or
|
||||
result = API::moduleImport("logging").getMember("getLogger").getReturn()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to one of the logging methods from `logging` or on a `logging.Logger`
|
||||
* subclass.
|
||||
*
|
||||
* See:
|
||||
* - https://docs.python.org/3.9/library/logging.html#logging.debug
|
||||
* - https://docs.python.org/3.9/library/logging.html#logging.Logger.debug
|
||||
*/
|
||||
class LoggerLogCall extends Logging::Range, DataFlow::CallCfgNode {
|
||||
/** The argument-index where the message is passed. */
|
||||
int msgIndex;
|
||||
|
||||
LoggerLogCall() {
|
||||
exists(string method |
|
||||
method in ["critical", "fatal", "error", "warning", "warn", "info", "debug", "exception"] and
|
||||
msgIndex = 0
|
||||
or
|
||||
method = "log" and
|
||||
msgIndex = 1
|
||||
|
|
||||
this = Logger::instance().getMember(method).getACall()
|
||||
or
|
||||
this = API::moduleImport("logging").getMember(method).getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() {
|
||||
result = this.getArgByName("msg")
|
||||
or
|
||||
result = this.getArg(any(int i | i >= msgIndex))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -422,7 +422,7 @@ private module Tornado {
|
||||
/**
|
||||
* A call to the `tornado.web.RequestHandler.redirect` method.
|
||||
*
|
||||
* See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.redirect
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.redirect
|
||||
*/
|
||||
private class TornadoRequestHandlerRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
@@ -444,7 +444,7 @@ private module Tornado {
|
||||
/**
|
||||
* A call to the `tornado.web.RequestHandler.write` method.
|
||||
*
|
||||
* See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.write
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write
|
||||
*/
|
||||
private class TornadoRequestHandlerWriteCall extends HTTP::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
@@ -458,4 +458,22 @@ private module Tornado {
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `tornado.web.RequestHandler.set_cookie` method.
|
||||
*
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.set_cookie
|
||||
*/
|
||||
class TornadoRequestHandlerSetCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
TornadoRequestHandlerSetCookieCall() {
|
||||
this.calls(tornado::web::RequestHandler::instance(), "set_cookie")
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("name")] }
|
||||
|
||||
override DataFlow::Node getValueArg() { result in [this.getArg(1), this.getArgByName("value")] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,4 +247,42 @@ private module Twisted {
|
||||
|
||||
override string getMimetypeDefault() { result = "text/html" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `addCookie` function on a twisted request.
|
||||
*
|
||||
* See https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http.Request.html#addCookie
|
||||
*/
|
||||
class TwistedRequestAddCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
TwistedRequestAddCookieCall() { this.calls(Twisted::Request::instance(), "addCookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("k")] }
|
||||
|
||||
override DataFlow::Node getValueArg() { result in [this.getArg(1), this.getArgByName("v")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `append` on the `cookies` attribute of a twisted request.
|
||||
*
|
||||
* See https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http.Request.html#cookies
|
||||
*/
|
||||
class TwistedRequestCookiesAppendCall extends HTTP::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
TwistedRequestCookiesAppendCall() {
|
||||
exists(DataFlow::AttrRead cookiesLookup |
|
||||
cookiesLookup.getObject() = Twisted::Request::instance() and
|
||||
cookiesLookup.getAttributeName() = "cookies" and
|
||||
this.calls(cookiesLookup, "append")
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() { result = this.getArg(0) }
|
||||
|
||||
override DataFlow::Node getNameArg() { none() }
|
||||
|
||||
override DataFlow::Node getValueArg() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for "Clear-text logging of sensitive information".
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `CleartextLogging::Configuration` is needed, otherwise
|
||||
* `CleartextLoggingCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
private import semmle.python.dataflow.new.SensitiveDataSources
|
||||
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "Clear-text logging of sensitive information".
|
||||
*/
|
||||
module CleartextLogging {
|
||||
import CleartextLoggingCustomizations::CleartextLogging
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Clear-text logging of sensitive information".
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CleartextLogging" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node)
|
||||
or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "Clear-text logging of sensitive information"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.dataflow.new.SensitiveDataSources
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "Clear-text logging of sensitive information"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module CleartextLogging {
|
||||
/**
|
||||
* A data flow source for "Clear-text logging of sensitive information" vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node {
|
||||
/** Gets the classification of the sensitive data. */
|
||||
abstract string getClassification();
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow sink for "Clear-text logging of sensitive information" vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for "Clear-text logging of sensitive information" vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of sensitive data, considered as a flow source.
|
||||
*/
|
||||
class SensitiveDataSourceAsSource extends Source, SensitiveDataSource {
|
||||
override SensitiveDataClassification getClassification() {
|
||||
result = SensitiveDataSource.super.getClassification()
|
||||
}
|
||||
}
|
||||
|
||||
/** A piece of data logged, considered as a flow sink. */
|
||||
class LoggingAsSink extends Sink {
|
||||
LoggingAsSink() { this = any(Logging write).getAnInput() }
|
||||
}
|
||||
|
||||
/** A piece of data printed, considered as a flow sink. */
|
||||
class PrintedDataAsSink extends Sink, DataFlow::CallCfgNode {
|
||||
PrintedDataAsSink() {
|
||||
this = API::builtin("print").getACall().getArg(_)
|
||||
or
|
||||
// special handling of writing to `sys.stdout` and `sys.stderr`, which is
|
||||
// essentially the same as printing
|
||||
this =
|
||||
API::moduleImport("sys")
|
||||
.getMember(["stdout", "stderr"])
|
||||
.getMember("write")
|
||||
.getACall()
|
||||
.getArg(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for "Clear-text storage of sensitive information".
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `CleartextStorage::Configuration` is needed, otherwise
|
||||
* `CleartextStorageCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
private import semmle.python.dataflow.new.SensitiveDataSources
|
||||
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "Clear-text storage of sensitive information".
|
||||
*/
|
||||
module CleartextStorage {
|
||||
import CleartextStorageCustomizations::CleartextStorage
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Clear-text storage of sensitive information".
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CleartextStorage" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node)
|
||||
or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "Clear-text storage of sensitive information"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.SensitiveDataSources
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "Clear-text storage of sensitive information"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module CleartextStorage {
|
||||
/**
|
||||
* A data flow source for "Clear-text storage of sensitive information" vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node {
|
||||
/** Gets the classification of the sensitive data. */
|
||||
abstract string getClassification();
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow sink for "Clear-text storage of sensitive information" vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for "Clear-text storage of sensitive information" vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of sensitive data, considered as a flow source.
|
||||
*/
|
||||
class SensitiveDataSourceAsSource extends Source, SensitiveDataSource {
|
||||
override SensitiveDataClassification getClassification() {
|
||||
result = SensitiveDataSource.super.getClassification()
|
||||
}
|
||||
}
|
||||
|
||||
/** The data written to a file, considered as a flow sink. */
|
||||
class FileWriteDataAsSink extends Sink {
|
||||
FileWriteDataAsSink() { this = any(FileSystemWriteAccess write).getADataNode() }
|
||||
}
|
||||
|
||||
/** The data written to a cookie on a HTTP response, considered as a flow sink. */
|
||||
class CookieWriteAsSink extends Sink {
|
||||
CookieWriteAsSink() {
|
||||
exists(HTTP::Server::CookieWrite write |
|
||||
this = write.getValueArg()
|
||||
or
|
||||
this = write.getHeaderArg()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,23 @@ class EncodingTest extends InlineExpectationsTest {
|
||||
}
|
||||
}
|
||||
|
||||
class LoggingTest extends InlineExpectationsTest {
|
||||
LoggingTest() { this = "LoggingTest" }
|
||||
|
||||
override string getARelevantTag() { result in ["loggingInput"] }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(location.getFile().getRelativePath()) and
|
||||
exists(Logging logging, DataFlow::Node data |
|
||||
location = data.getLocation() and
|
||||
element = data.toString() and
|
||||
value = prettyNodeForInlineTest(data) and
|
||||
data = logging.getAnInput() and
|
||||
tag = "loggingInput"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class CodeExecutionTest extends InlineExpectationsTest {
|
||||
CodeExecutionTest() { this = "CodeExecutionTest" }
|
||||
|
||||
@@ -284,6 +301,38 @@ class HttpServerHttpRedirectResponseTest extends InlineExpectationsTest {
|
||||
}
|
||||
}
|
||||
|
||||
class HttpServerCookieWriteTest extends InlineExpectationsTest {
|
||||
HttpServerCookieWriteTest() { this = "HttpServerCookieWriteTest" }
|
||||
|
||||
override string getARelevantTag() {
|
||||
result in ["CookieWrite", "CookieRawHeader", "CookieName", "CookieValue"]
|
||||
}
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(location.getFile().getRelativePath()) and
|
||||
exists(HTTP::Server::CookieWrite cookieWrite |
|
||||
location = cookieWrite.getLocation() and
|
||||
(
|
||||
element = cookieWrite.toString() and
|
||||
value = "" and
|
||||
tag = "CookieWrite"
|
||||
or
|
||||
element = cookieWrite.toString() and
|
||||
value = prettyNodeForInlineTest(cookieWrite.getHeaderArg()) and
|
||||
tag = "CookieRawHeader"
|
||||
or
|
||||
element = cookieWrite.toString() and
|
||||
value = prettyNodeForInlineTest(cookieWrite.getNameArg()) and
|
||||
tag = "CookieName"
|
||||
or
|
||||
element = cookieWrite.toString() and
|
||||
value = prettyNodeForInlineTest(cookieWrite.getValueArg()) and
|
||||
tag = "CookieValue"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class FileSystemAccessTest extends InlineExpectationsTest {
|
||||
FileSystemAccessTest() { this = "FileSystemAccessTest" }
|
||||
|
||||
@@ -301,6 +350,23 @@ class FileSystemAccessTest extends InlineExpectationsTest {
|
||||
}
|
||||
}
|
||||
|
||||
class FileSystemWriteAccessTest extends InlineExpectationsTest {
|
||||
FileSystemWriteAccessTest() { this = "FileSystemWriteAccessTest" }
|
||||
|
||||
override string getARelevantTag() { result = "fileWriteData" }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(location.getFile().getRelativePath()) and
|
||||
exists(FileSystemWriteAccess write, DataFlow::Node data |
|
||||
data = write.getADataNode() and
|
||||
location = data.getLocation() and
|
||||
element = data.toString() and
|
||||
value = prettyNodeForInlineTest(data) and
|
||||
tag = "fileWriteData"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class PathNormalizationTest extends InlineExpectationsTest {
|
||||
PathNormalizationTest() { this = "PathNormalizationTest" }
|
||||
|
||||
|
||||
@@ -65,6 +65,20 @@ async def redirect_302(request): # $ requestHandler
|
||||
else:
|
||||
raise web.HTTPFound(location="/logout") # $ HttpResponse HttpRedirectResponse mimetype=application/octet-stream redirectLocation="/logout"
|
||||
|
||||
################################################################################
|
||||
# Cookies
|
||||
################################################################################
|
||||
|
||||
@routes.get("/setting_cookie") # $ routeSetup="/setting_cookie"
|
||||
async def setting_cookie(request): # $ requestHandler
|
||||
resp = web.Response(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
|
||||
resp.cookies["key"] = "value" # $ CookieWrite CookieName="key" CookieValue="value"
|
||||
resp.headers["Set-Cookie"] = "key2=value2" # $ MISSING: CookieWrite CookieRawHeader="key2=value2"
|
||||
resp.set_cookie("key3", "value3") # $ CookieWrite CookieName="key3" CookieValue="value3"
|
||||
resp.set_cookie(name="key3", value="value3") # $ CookieWrite CookieName="key3" CookieValue="value3"
|
||||
resp.del_cookie("key4") # $ CookieWrite CookieName="key4"
|
||||
return resp
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = web.Application()
|
||||
|
||||
@@ -103,3 +103,17 @@ class CustomJsonResponse(JsonResponse):
|
||||
|
||||
def safe__custom_json_response(request):
|
||||
return CustomJsonResponse("ACME Responses", {"foo": request.GET.get("foo")}) # $HttpResponse mimetype=application/json MISSING: responseBody=Dict SPURIOUS: responseBody="ACME Responses"
|
||||
|
||||
################################################################################
|
||||
# Cookies
|
||||
################################################################################
|
||||
|
||||
def setting_cookie(request):
|
||||
resp = HttpResponse() # $ 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["Set-Cookie"] = "key2=value2" # $ MISSING: CookieWrite CookieRawHeader="key2=value2"
|
||||
resp.cookies["key3"] = "value3" # $ CookieWrite CookieName="key3" CookieValue="value3"
|
||||
resp.delete_cookie("key4") # $ CookieWrite CookieName="key4"
|
||||
resp.delete_cookie(key="key4") # $ CookieWrite CookieName="key4"
|
||||
return resp
|
||||
|
||||
@@ -184,6 +184,20 @@ def redirect_simple(): # $requestHandler
|
||||
return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp
|
||||
|
||||
|
||||
################################################################################
|
||||
# Cookies
|
||||
################################################################################
|
||||
|
||||
@app.route("/setting_cookie") # $routeSetup="/setting_cookie"
|
||||
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.delete_cookie("key3") # $ CookieWrite CookieName="key3"
|
||||
resp.delete_cookie(key="key3") # $ CookieWrite CookieName="key3"
|
||||
return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp
|
||||
|
||||
################################################################################
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
from pathlib import Path, PosixPath, WindowsPath
|
||||
|
||||
p = Path("filepath")
|
||||
posix = PosixPath("posix/filepath")
|
||||
windows = WindowsPath("windows/filepath")
|
||||
|
||||
p.chmod(0o777) # $ getAPathArgument=p
|
||||
posix.chmod(0o777) # $ getAPathArgument=posix
|
||||
windows.chmod(0o777) # $ getAPathArgument=windows
|
||||
|
||||
with p.open() as f: # $ getAPathArgument=p
|
||||
f.read()
|
||||
|
||||
p.write_bytes(b"hello") # $ getAPathArgument=p fileWriteData=b"hello"
|
||||
p.write_text("hello") # $ getAPathArgument=p fileWriteData="hello"
|
||||
p.open("wt").write("hello") # $ getAPathArgument=p fileWriteData="hello"
|
||||
|
||||
name = windows.parent.name
|
||||
o = open
|
||||
o(name) # $ getAPathArgument=name
|
||||
|
||||
wb = p.write_bytes
|
||||
wb(b"hello") # $ getAPathArgument=p fileWriteData=b"hello"
|
||||
@@ -1,39 +1,29 @@
|
||||
import builtins
|
||||
import io
|
||||
|
||||
open("filepath") # $getAPathArgument="filepath"
|
||||
open(file="filepath") # $getAPathArgument="filepath"
|
||||
open("filepath") # $ getAPathArgument="filepath"
|
||||
open(file="filepath") # $ getAPathArgument="filepath"
|
||||
|
||||
o = open
|
||||
|
||||
o("filepath") # $getAPathArgument="filepath"
|
||||
o(file="filepath") # $getAPathArgument="filepath"
|
||||
o("filepath") # $ getAPathArgument="filepath"
|
||||
o(file="filepath") # $ getAPathArgument="filepath"
|
||||
|
||||
|
||||
builtins.open("filepath") # $getAPathArgument="filepath"
|
||||
builtins.open(file="filepath") # $getAPathArgument="filepath"
|
||||
builtins.open("filepath") # $ getAPathArgument="filepath"
|
||||
builtins.open(file="filepath") # $ getAPathArgument="filepath"
|
||||
|
||||
|
||||
io.open("filepath") # $getAPathArgument="filepath"
|
||||
io.open(file="filepath") # $getAPathArgument="filepath"
|
||||
io.open("filepath") # $ getAPathArgument="filepath"
|
||||
io.open(file="filepath") # $ getAPathArgument="filepath"
|
||||
|
||||
from pathlib import Path, PosixPath, WindowsPath
|
||||
f = open("path") # $ getAPathArgument="path"
|
||||
f.write("foo") # $ getAPathArgument="path" fileWriteData="foo"
|
||||
lines = ["foo"]
|
||||
f.writelines(lines) # $ getAPathArgument="path" fileWriteData=lines
|
||||
|
||||
p = Path("filepath")
|
||||
posix = PosixPath("posix/filepath")
|
||||
windows = WindowsPath("windows/filepath")
|
||||
|
||||
p.chmod(0o777) # $getAPathArgument=p
|
||||
posix.chmod(0o777) # $getAPathArgument=posix
|
||||
windows.chmod(0o777) # $getAPathArgument=windows
|
||||
def through_function(open_file):
|
||||
open_file.write("foo") # $ fileWriteData="foo" getAPathArgument="path"
|
||||
|
||||
with p.open() as f: # $getAPathArgument=p
|
||||
f.read()
|
||||
|
||||
p.write_bytes(b"hello") # $getAPathArgument=p
|
||||
|
||||
name = windows.parent.name
|
||||
o(name) # $getAPathArgument=name
|
||||
|
||||
wb = p.write_bytes
|
||||
wb(b"hello") # $getAPathArgument=p
|
||||
through_function(f)
|
||||
|
||||
45
python/ql/test/library-tests/frameworks/stdlib/Logging.py
Normal file
45
python/ql/test/library-tests/frameworks/stdlib/Logging.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import logging
|
||||
|
||||
# this bit just included to make this file runable
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
password = "<pass>"
|
||||
msg = "foo %s"
|
||||
|
||||
LOGGER = logging.getLogger("LOGGER")
|
||||
|
||||
logging.info(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
logging.info(msg="hello") # $ loggingInput="hello"
|
||||
|
||||
logging.log(logging.INFO, msg, password) # $ loggingInput=msg loggingInput=password
|
||||
LOGGER.log(logging.INFO, msg, password) # $ loggingInput=msg loggingInput=password
|
||||
|
||||
logging.root.info(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
|
||||
# test of all levels
|
||||
|
||||
logging.critical(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
logging.fatal(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
logging.error(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
logging.warning(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
logging.warn(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
logging.info(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
logging.debug(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
logging.exception(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
|
||||
LOGGER.critical(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
LOGGER.fatal(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
LOGGER.error(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
LOGGER.warning(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
LOGGER.warn(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
LOGGER.info(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
LOGGER.debug(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
LOGGER.exception(msg, password) # $ loggingInput=msg loggingInput=password
|
||||
|
||||
# not sure how to make these print anything, but just to show that it works
|
||||
logging.Logger("foo").info("hello") # $ loggingInput="hello"
|
||||
|
||||
class MyLogger(logging.Logger):
|
||||
pass
|
||||
|
||||
MyLogger("bar").info("hello") # $ loggingInput="hello"
|
||||
@@ -58,6 +58,18 @@ class ExampleConnectionWrite(tornado.web.RequestHandler):
|
||||
stream.write(b"foo stream") # $ MISSING: HttpResponse responseBody=b"foo stream"
|
||||
stream.close()
|
||||
|
||||
################################################################################
|
||||
# Cookies
|
||||
################################################################################
|
||||
|
||||
class CookieWriting(tornado.web.RequestHandler):
|
||||
def get(self): # $ requestHandler
|
||||
self.write("foo") # $ HttpResponse mimetype=text/html responseBody="foo"
|
||||
self.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
|
||||
self.set_cookie(name="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value"
|
||||
self.set_header("Set-Cookie", "key2=value2") # $ MISSING: CookieWrite CookieRawHeader="key2=value2"
|
||||
|
||||
|
||||
def make_app():
|
||||
return tornado.web.Application(
|
||||
[
|
||||
@@ -66,6 +78,7 @@ def make_app():
|
||||
(r"/ExampleRedirect", ExampleRedirect), # $ routeSetup="/ExampleRedirect"
|
||||
(r"/ExampleConnectionWrite", ExampleConnectionWrite), # $ routeSetup="/ExampleConnectionWrite"
|
||||
(r"/ExampleConnectionWrite/(stream)", ExampleConnectionWrite), # $ routeSetup="/ExampleConnectionWrite/(stream)"
|
||||
(r"/CookieWriting", CookieWriting), # $ routeSetup="/CookieWriting"
|
||||
],
|
||||
debug=True,
|
||||
)
|
||||
@@ -74,6 +87,7 @@ def make_app():
|
||||
if __name__ == "__main__":
|
||||
import tornado.ioloop
|
||||
|
||||
print("running on http://localhost:8888/")
|
||||
app = make_app()
|
||||
app.listen(8888)
|
||||
tornado.ioloop.IOLoop.current().start()
|
||||
|
||||
@@ -43,16 +43,21 @@ class Redirect(Resource):
|
||||
# requested with curl.
|
||||
return b"hello" # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=b"hello"
|
||||
|
||||
################################################################################
|
||||
# Cookies
|
||||
################################################################################
|
||||
|
||||
class NonHttpBodyOutput(Resource):
|
||||
class CookieWriting(Resource):
|
||||
"""Examples of providing values in response that is not in the body
|
||||
"""
|
||||
def render_GET(self, request: Request): # $ requestHandler
|
||||
request.responseHeaders.addRawHeader("key", "value")
|
||||
request.setHeader("key2", "value")
|
||||
request.addCookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
|
||||
request.addCookie(k="key", v="value") # $ CookieWrite CookieName="key" CookieValue="value"
|
||||
val = "key2=value"
|
||||
request.cookies.append(val) # $ CookieWrite CookieRawHeader=val
|
||||
|
||||
request.addCookie("key", "value")
|
||||
request.cookies.append(b"key2=value")
|
||||
request.responseHeaders.addRawHeader("key", "value")
|
||||
request.setHeader("Set-Cookie", "key3=value3") # $ MISSING: CookieWrite CookieRawHeader="key3=value3"
|
||||
|
||||
return b"" # $ HttpResponse mimetype=text/html responseBody=b""
|
||||
|
||||
@@ -62,7 +67,7 @@ root.putChild(b"also-now", AlsoNow())
|
||||
root.putChild(b"later", Later())
|
||||
root.putChild(b"plain-text", PlainText())
|
||||
root.putChild(b"redirect", Redirect())
|
||||
root.putChild(b"non-body", NonHttpBodyOutput())
|
||||
root.putChild(b"setting_cookie", CookieWriting())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
edges
|
||||
| test.py:19:16:19:29 | ControlFlowNode for get_password() | test.py:20:48:20:55 | ControlFlowNode for password |
|
||||
| test.py:19:16:19:29 | ControlFlowNode for get_password() | test.py:22:58:22:65 | ControlFlowNode for password |
|
||||
| test.py:19:16:19:29 | ControlFlowNode for get_password() | test.py:23:58:23:65 | ControlFlowNode for password |
|
||||
| test.py:19:16:19:29 | ControlFlowNode for get_password() | test.py:27:40:27:47 | ControlFlowNode for password |
|
||||
| test.py:19:16:19:29 | ControlFlowNode for get_password() | test.py:30:58:30:65 | ControlFlowNode for password |
|
||||
nodes
|
||||
| test.py:19:16:19:29 | ControlFlowNode for get_password() | semmle.label | ControlFlowNode for get_password() |
|
||||
| test.py:20:48:20:55 | ControlFlowNode for password | semmle.label | ControlFlowNode for password |
|
||||
| test.py:22:58:22:65 | ControlFlowNode for password | semmle.label | ControlFlowNode for password |
|
||||
| test.py:23:58:23:65 | ControlFlowNode for password | semmle.label | ControlFlowNode for password |
|
||||
| test.py:27:40:27:47 | ControlFlowNode for password | semmle.label | ControlFlowNode for password |
|
||||
| test.py:30:58:30:65 | ControlFlowNode for password | semmle.label | ControlFlowNode for password |
|
||||
| test.py:34:30:34:39 | ControlFlowNode for get_cert() | semmle.label | ControlFlowNode for get_cert() |
|
||||
| test.py:37:11:37:24 | ControlFlowNode for get_password() | semmle.label | ControlFlowNode for get_password() |
|
||||
| test.py:39:22:39:35 | ControlFlowNode for get_password() | semmle.label | ControlFlowNode for get_password() |
|
||||
| test.py:40:22:40:35 | ControlFlowNode for get_password() | semmle.label | ControlFlowNode for get_password() |
|
||||
#select
|
||||
| test.py:20:48:20:55 | ControlFlowNode for password | test.py:19:16:19:29 | ControlFlowNode for get_password() | test.py:20:48:20:55 | ControlFlowNode for password | $@ is logged here. | test.py:19:16:19:29 | ControlFlowNode for get_password() | Sensitive data (password) |
|
||||
| test.py:22:58:22:65 | ControlFlowNode for password | test.py:19:16:19:29 | ControlFlowNode for get_password() | test.py:22:58:22:65 | ControlFlowNode for password | $@ is logged here. | test.py:19:16:19:29 | ControlFlowNode for get_password() | Sensitive data (password) |
|
||||
| test.py:23:58:23:65 | ControlFlowNode for password | test.py:19:16:19:29 | ControlFlowNode for get_password() | test.py:23:58:23:65 | ControlFlowNode for password | $@ is logged here. | test.py:19:16:19:29 | ControlFlowNode for get_password() | Sensitive data (password) |
|
||||
| test.py:27:40:27:47 | ControlFlowNode for password | test.py:19:16:19:29 | ControlFlowNode for get_password() | test.py:27:40:27:47 | ControlFlowNode for password | $@ is logged here. | test.py:19:16:19:29 | ControlFlowNode for get_password() | Sensitive data (password) |
|
||||
| test.py:30:58:30:65 | ControlFlowNode for password | test.py:19:16:19:29 | ControlFlowNode for get_password() | test.py:30:58:30:65 | ControlFlowNode for password | $@ is logged here. | test.py:19:16:19:29 | ControlFlowNode for get_password() | Sensitive data (password) |
|
||||
| test.py:34:30:34:39 | ControlFlowNode for get_cert() | test.py:34:30:34:39 | ControlFlowNode for get_cert() | test.py:34:30:34:39 | ControlFlowNode for get_cert() | $@ is logged here. | test.py:34:30:34:39 | ControlFlowNode for get_cert() | Sensitive data (certificate) |
|
||||
| test.py:37:11:37:24 | ControlFlowNode for get_password() | test.py:37:11:37:24 | ControlFlowNode for get_password() | test.py:37:11:37:24 | ControlFlowNode for get_password() | $@ is logged here. | test.py:37:11:37:24 | ControlFlowNode for get_password() | Sensitive data (password) |
|
||||
| test.py:39:22:39:35 | ControlFlowNode for get_password() | test.py:39:22:39:35 | ControlFlowNode for get_password() | test.py:39:22:39:35 | ControlFlowNode for get_password() | $@ is logged here. | test.py:39:22:39:35 | ControlFlowNode for get_password() | Sensitive data (password) |
|
||||
| test.py:40:22:40:35 | ControlFlowNode for get_password() | test.py:40:22:40:35 | ControlFlowNode for get_password() | test.py:40:22:40:35 | ControlFlowNode for get_password() | $@ is logged here. | test.py:40:22:40:35 | ControlFlowNode for get_password() | Sensitive data (password) |
|
||||
@@ -0,0 +1,46 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
LOGGER = logging.getLogger("LOGGER")
|
||||
|
||||
def get_logger():
|
||||
return LOGGER
|
||||
|
||||
|
||||
def get_password():
|
||||
return "<PASSWORD>"
|
||||
|
||||
|
||||
def get_cert():
|
||||
return "<CERT>"
|
||||
|
||||
|
||||
def log_password():
|
||||
password = get_password()
|
||||
logging.info("logging.info Password '%s'", password) # NOT OK
|
||||
|
||||
LOGGER.log(logging.INFO, "LOGGER.log Password '%s'", password) # NOT OK
|
||||
logging.root.info("logging.root.info Password '%s'", password) # NOT OK
|
||||
|
||||
# name of logger variable should not matter
|
||||
foo = LOGGER
|
||||
foo.info("foo.info Password '%s'", password) # NOT OK
|
||||
|
||||
# return value from function
|
||||
get_logger().info("get_logger().info Password '%s'", password) # NOT OK
|
||||
|
||||
|
||||
def log_cert():
|
||||
logging.debug("Cert=%s", get_cert()) # NOT OK
|
||||
|
||||
def print_password():
|
||||
print(get_password()) # NOT OK
|
||||
|
||||
sys.stdout.write(get_password()) # NOT OK
|
||||
sys.stderr.write(get_password()) # NOT OK
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
log_password()
|
||||
log_cert()
|
||||
print_password()
|
||||
@@ -0,0 +1,13 @@
|
||||
edges
|
||||
| test.py:9:12:9:21 | ControlFlowNode for get_cert() | test.py:12:21:12:24 | ControlFlowNode for cert |
|
||||
| test.py:9:12:9:21 | ControlFlowNode for get_cert() | test.py:13:22:13:41 | ControlFlowNode for Attribute() |
|
||||
| test.py:9:12:9:21 | ControlFlowNode for get_cert() | test.py:15:26:15:29 | ControlFlowNode for cert |
|
||||
nodes
|
||||
| test.py:9:12:9:21 | ControlFlowNode for get_cert() | semmle.label | ControlFlowNode for get_cert() |
|
||||
| test.py:12:21:12:24 | ControlFlowNode for cert | semmle.label | ControlFlowNode for cert |
|
||||
| test.py:13:22:13:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| test.py:15:26:15:29 | ControlFlowNode for cert | semmle.label | ControlFlowNode for cert |
|
||||
#select
|
||||
| test.py:12:21:12:24 | ControlFlowNode for cert | test.py:9:12:9:21 | ControlFlowNode for get_cert() | test.py:12:21:12:24 | ControlFlowNode for cert | $@ is stored here. | test.py:9:12:9:21 | ControlFlowNode for get_cert() | Sensitive data (certificate) |
|
||||
| test.py:13:22:13:41 | ControlFlowNode for Attribute() | test.py:9:12:9:21 | ControlFlowNode for get_cert() | test.py:13:22:13:41 | ControlFlowNode for Attribute() | $@ is stored here. | test.py:9:12:9:21 | ControlFlowNode for get_cert() | Sensitive data (certificate) |
|
||||
| test.py:15:26:15:29 | ControlFlowNode for cert | test.py:9:12:9:21 | ControlFlowNode for get_cert() | test.py:15:26:15:29 | ControlFlowNode for cert | $@ is stored here. | test.py:9:12:9:21 | ControlFlowNode for get_cert() | Sensitive data (certificate) |
|
||||
@@ -0,0 +1,15 @@
|
||||
import pathlib
|
||||
|
||||
|
||||
def get_cert():
|
||||
return "<CERT>"
|
||||
|
||||
|
||||
def write_password(filename):
|
||||
cert = get_cert()
|
||||
|
||||
path = pathlib.Path(filename)
|
||||
path.write_text(cert) # NOT OK
|
||||
path.write_bytes(cert.encode("utf-8")) # NOT OK
|
||||
|
||||
path.open("w").write(cert) # NOT OK
|
||||
@@ -0,0 +1,20 @@
|
||||
edges
|
||||
| password_in_cookie.py:7:16:7:43 | ControlFlowNode for Attribute() | password_in_cookie.py:9:33:9:40 | ControlFlowNode for password |
|
||||
| password_in_cookie.py:14:16:14:43 | ControlFlowNode for Attribute() | password_in_cookie.py:16:33:16:40 | ControlFlowNode for password |
|
||||
| test.py:6:12:6:21 | ControlFlowNode for get_cert() | test.py:8:20:8:23 | ControlFlowNode for cert |
|
||||
| test.py:6:12:6:21 | ControlFlowNode for get_cert() | test.py:9:17:9:29 | ControlFlowNode for List |
|
||||
| test.py:9:17:9:29 | ControlFlowNode for List | test.py:10:25:10:29 | ControlFlowNode for lines |
|
||||
nodes
|
||||
| password_in_cookie.py:7:16:7:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| password_in_cookie.py:9:33:9:40 | ControlFlowNode for password | semmle.label | ControlFlowNode for password |
|
||||
| password_in_cookie.py:14:16:14:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| password_in_cookie.py:16:33:16:40 | ControlFlowNode for password | semmle.label | ControlFlowNode for password |
|
||||
| test.py:6:12:6:21 | ControlFlowNode for get_cert() | semmle.label | ControlFlowNode for get_cert() |
|
||||
| test.py:8:20:8:23 | ControlFlowNode for cert | semmle.label | ControlFlowNode for cert |
|
||||
| test.py:9:17:9:29 | ControlFlowNode for List | semmle.label | ControlFlowNode for List |
|
||||
| test.py:10:25:10:29 | ControlFlowNode for lines | semmle.label | ControlFlowNode for lines |
|
||||
#select
|
||||
| password_in_cookie.py:9:33:9:40 | ControlFlowNode for password | password_in_cookie.py:7:16:7:43 | ControlFlowNode for Attribute() | password_in_cookie.py:9:33:9:40 | ControlFlowNode for password | $@ is stored here. | password_in_cookie.py:7:16:7:43 | ControlFlowNode for Attribute() | Sensitive data (password) |
|
||||
| password_in_cookie.py:16:33:16:40 | ControlFlowNode for password | password_in_cookie.py:14:16:14:43 | ControlFlowNode for Attribute() | password_in_cookie.py:16:33:16:40 | ControlFlowNode for password | $@ is stored here. | password_in_cookie.py:14:16:14:43 | ControlFlowNode for Attribute() | Sensitive data (password) |
|
||||
| test.py:8:20:8:23 | ControlFlowNode for cert | test.py:6:12:6:21 | ControlFlowNode for get_cert() | test.py:8:20:8:23 | ControlFlowNode for cert | $@ is stored here. | test.py:6:12:6:21 | ControlFlowNode for get_cert() | Sensitive data (certificate) |
|
||||
| test.py:10:25:10:29 | ControlFlowNode for lines | test.py:6:12:6:21 | ControlFlowNode for get_cert() | test.py:10:25:10:29 | ControlFlowNode for lines | $@ is stored here. | test.py:6:12:6:21 | ControlFlowNode for get_cert() | Sensitive data (certificate) |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-312/CleartextStorage.ql
|
||||
@@ -6,12 +6,12 @@ app = Flask("Leak password")
|
||||
def index():
|
||||
password = request.args.get("password")
|
||||
resp = make_response(render_template(...))
|
||||
resp.set_cookie("password", password)
|
||||
resp.set_cookie("password", password) # NOT OK
|
||||
return resp
|
||||
|
||||
@app.route('/')
|
||||
def index2():
|
||||
password = request.args.get("password")
|
||||
resp = Response(...)
|
||||
resp.set_cookie("password", password)
|
||||
resp.set_cookie("password", password) # NOT OK
|
||||
return resp
|
||||
@@ -0,0 +1,10 @@
|
||||
def get_cert():
|
||||
return "<CERT>"
|
||||
|
||||
|
||||
def write_cert(filename):
|
||||
cert = get_cert()
|
||||
with open(filename, "w") as file:
|
||||
file.write(cert) # NOT OK
|
||||
lines = [cert + "\n"]
|
||||
file.writelines(lines) # NOT OK
|
||||
@@ -1,10 +0,0 @@
|
||||
edges
|
||||
| password_in_cookie.py:7:16:7:43 | a password | password_in_cookie.py:9:33:9:40 | a password |
|
||||
| password_in_cookie.py:14:16:14:43 | a password | password_in_cookie.py:16:33:16:40 | a password |
|
||||
| test.py:7:16:7:29 | a password | test.py:8:35:8:42 | a password |
|
||||
| test.py:7:16:7:29 | a password | test.py:8:35:8:42 | a password |
|
||||
| test.py:20:12:20:21 | a certificate or key | test.py:22:20:22:23 | a certificate or key |
|
||||
#select
|
||||
| test.py:8:35:8:42 | password | test.py:7:16:7:29 | a password | test.py:8:35:8:42 | a password | Sensitive data returned by $@ is logged here. | test.py:7:16:7:29 | get_password() | a call returning a password |
|
||||
| test.py:14:30:14:39 | get_cert() | test.py:14:30:14:39 | a certificate or key | test.py:14:30:14:39 | a certificate or key | Sensitive data returned by $@ is logged here. | test.py:14:30:14:39 | get_cert() | a call returning a certificate or key |
|
||||
| test.py:17:11:17:24 | get_password() | test.py:17:11:17:24 | a password | test.py:17:11:17:24 | a password | Sensitive data returned by $@ is logged here. | test.py:17:11:17:24 | get_password() | a call returning a password |
|
||||
@@ -1,12 +0,0 @@
|
||||
edges
|
||||
| password_in_cookie.py:7:16:7:43 | a password | password_in_cookie.py:9:33:9:40 | a password |
|
||||
| password_in_cookie.py:7:16:7:43 | a password | password_in_cookie.py:9:33:9:40 | a password |
|
||||
| password_in_cookie.py:14:16:14:43 | a password | password_in_cookie.py:16:33:16:40 | a password |
|
||||
| password_in_cookie.py:14:16:14:43 | a password | password_in_cookie.py:16:33:16:40 | a password |
|
||||
| test.py:7:16:7:29 | a password | test.py:8:35:8:42 | a password |
|
||||
| test.py:20:12:20:21 | a certificate or key | test.py:22:20:22:23 | a certificate or key |
|
||||
| test.py:20:12:20:21 | a certificate or key | test.py:22:20:22:23 | a certificate or key |
|
||||
#select
|
||||
| password_in_cookie.py:9:33:9:40 | password | password_in_cookie.py:7:16:7:43 | a password | password_in_cookie.py:9:33:9:40 | a password | Sensitive data from $@ is stored here. | password_in_cookie.py:7:16:7:43 | Attribute() | a request parameter containing a password |
|
||||
| password_in_cookie.py:16:33:16:40 | password | password_in_cookie.py:14:16:14:43 | a password | password_in_cookie.py:16:33:16:40 | a password | Sensitive data from $@ is stored here. | password_in_cookie.py:14:16:14:43 | Attribute() | a request parameter containing a password |
|
||||
| test.py:22:20:22:23 | cert | test.py:20:12:20:21 | a certificate or key | test.py:22:20:22:23 | a certificate or key | Sensitive data from $@ is stored here. | test.py:20:12:20:21 | get_cert() | a call returning a certificate or key |
|
||||
@@ -1 +0,0 @@
|
||||
semmle-extractor-options: -p ../lib/ --max-import-depth=3
|
||||
@@ -1,22 +0,0 @@
|
||||
#Don't import logging; it transitively imports a lot of stuff
|
||||
|
||||
def get_password():
|
||||
pass
|
||||
|
||||
def log_password():
|
||||
password = get_password()
|
||||
logging.info("Password '%s'", password)
|
||||
|
||||
def get_cert():
|
||||
pass
|
||||
|
||||
def log_cert():
|
||||
logging.debug("Cert=%s", get_cert())
|
||||
|
||||
def print_password():
|
||||
print(get_password())
|
||||
|
||||
def write_cert(filename):
|
||||
cert = get_cert()
|
||||
with open(filename, "w") as file:
|
||||
file.write(cert)
|
||||
Reference in New Issue
Block a user