mirror of
https://github.com/github/codeql.git
synced 2026-02-23 10:23:41 +01:00
Merge remote-tracking branch 'origin/jorgectf/python/headerInjection' into jorgectf/python/insecure-cookie
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @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.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @id py/header-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-113
|
||||
* external/cwe/cwe-079
|
||||
*/
|
||||
|
||||
// determine precision above
|
||||
import python
|
||||
import experimental.semmle.python.security.injection.HTTPHeaders
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from HeaderInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ HTTP header is constructed from a $@.", sink.getNode(),
|
||||
"This", source.getNode(), "user-provided value"
|
||||
@@ -0,0 +1,9 @@
|
||||
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
|
||||
@@ -209,3 +209,36 @@ class SQLEscape extends DataFlow::Node {
|
||||
*/
|
||||
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling HTTP Header APIs. */
|
||||
module HeaderDeclaration {
|
||||
/**
|
||||
* A data-flow node that collects functions setting HTTP Headers' content.
|
||||
*
|
||||
* 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 value.
|
||||
*/
|
||||
abstract DataFlow::Node getAnInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that collects functions setting HTTP Headers' content.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `HeaderDeclaration` instead.
|
||||
*/
|
||||
class HeaderDeclaration extends DataFlow::Node {
|
||||
HeaderDeclaration::Range range;
|
||||
|
||||
HeaderDeclaration() { this = range }
|
||||
|
||||
/**
|
||||
* Gets the argument containing the header value.
|
||||
*/
|
||||
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
||||
}
|
||||
|
||||
@@ -3,4 +3,7 @@
|
||||
*/
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `django` PyPI package.
|
||||
* See https://www.djangoproject.com/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.frameworks.Django
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
private module PrivateDjango {
|
||||
API::Node django() { result = API::moduleImport("django") }
|
||||
|
||||
private module django {
|
||||
API::Node http() { result = django().getMember("http") }
|
||||
|
||||
module http {
|
||||
API::Node response() { result = http().getMember("response") }
|
||||
|
||||
module response {
|
||||
module HttpResponse {
|
||||
API::Node baseClassRef() {
|
||||
result = response().getMember("HttpResponse").getReturn()
|
||||
or
|
||||
// Handle `django.http.HttpResponse` alias
|
||||
result = http().getMember("HttpResponse").getReturn()
|
||||
}
|
||||
|
||||
/** Gets a reference to a header instance. */
|
||||
private DataFlow::LocalSourceNode headerInstance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
exists(SubscriptNode subscript |
|
||||
subscript.getObject() = baseClassRef().getAUse().asCfgNode() and
|
||||
result.asCfgNode() = subscript
|
||||
)
|
||||
or
|
||||
result.(DataFlow::AttrRead).getObject() = baseClassRef().getAUse()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to a header instance use. */
|
||||
private DataFlow::Node headerInstance() {
|
||||
headerInstance(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
|
||||
/** Gets a reference to a header instance call with `__setitem__`. */
|
||||
private DataFlow::Node headerSetItemCall() {
|
||||
result = headerInstance() and
|
||||
result.(DataFlow::AttrRead).getAttributeName() = "__setitem__"
|
||||
}
|
||||
|
||||
class DjangoResponseSetItemCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
|
||||
DjangoResponseSetItemCall() { this.getFunction() = headerSetItemCall() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg([0, 1]) }
|
||||
}
|
||||
|
||||
class DjangoResponseDefinition extends DataFlow::Node, HeaderDeclaration::Range {
|
||||
DataFlow::Node headerInput;
|
||||
|
||||
DjangoResponseDefinition() {
|
||||
this.asCfgNode().(DefinitionNode) = headerInstance().asCfgNode() and
|
||||
headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() {
|
||||
result.asExpr() in [headerInput.asExpr(), this.asExpr().(Subscript).getIndex()]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `flask` PyPI package.
|
||||
* See https://flask.palletsprojects.com/en/1.1.x/.
|
||||
*/
|
||||
|
||||
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
|
||||
|
||||
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(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result.(DataFlow::AttrRead).getObject().getALocalSource() =
|
||||
[Flask::Response::classRef(), flaskMakeResponse()].getReturn().getAUse()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to a header instance use. */
|
||||
private DataFlow::Node headerInstance() {
|
||||
headerInstance(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
|
||||
/** 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 getAnInput() {
|
||||
result.asExpr() in [headerInput.asExpr(), this.asExpr().(Subscript).getIndex()]
|
||||
}
|
||||
}
|
||||
|
||||
private class FlaskMakeResponseExtend extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
|
||||
FlaskMakeResponseExtend() { this.getFunction() = headerInstanceCall() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(_) }
|
||||
}
|
||||
|
||||
private class FlaskResponse extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
|
||||
FlaskResponse() { this = Flask::Response::classRef().getACall() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArgByName("headers") }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 getAnInput() { result = this.getArg(_) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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.
|
||||
*/
|
||||
class HeaderInjectionFlowConfig extends TaintTracking::Configuration {
|
||||
HeaderInjectionFlowConfig() { this = "HeaderInjectionFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(HeaderDeclaration headerDeclaration).getAnInput()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user