Add to header write concept a specification of whether the name or value arg allows newlines.

Ported sink defenitions from Flask and Werzeug from experimental to main.
Removed experimental sink definitions for Django, as neither name nor value are vulnerable.
This commit is contained in:
Joe Farebrother
2024-04-02 17:00:17 +01:00
parent 25ffcb2fde
commit 68d90918cf
8 changed files with 77 additions and 87 deletions

View File

@@ -1041,6 +1041,16 @@ module Http {
* Gets the argument containing the header value.
*/
DataFlow::Node getValueArg() { result = super.getValueArg() }
/**
* Holds if newlines are accepted in the header name argument.
*/
predicate nameAllowsNewline() { super.nameAllowsNewline() }
/**
* Holds if newlines are accepted in the header value argument.
*/
predicate valueAllowsNewline() { super.valueAllowsNewline() }
}
/** Provides a class for modelling header writes on HTTP responses. */
@@ -1061,6 +1071,16 @@ module Http {
* Gets the argument containing the header value.
*/
abstract DataFlow::Node getValueArg();
/**
* Holds if newlines are accepted in the header name argument.
*/
abstract predicate nameAllowsNewline();
/**
* Holds if newlines are accepted in the header value argument.
*/
abstract predicate valueAllowsNewline();
}
}

View File

@@ -220,6 +220,13 @@ module Flask {
/** Gets a reference to an instance of `flask.Response`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/** An `Headers` instance that is part of a Flask response. */
private class FlaskResponseHeadersInstances extends Werkzeug::Headers::InstanceSource {
FlaskResponseHeadersInstances() { this = request().getMember("headers").asSource() }
}
// TODO: headers arg to make_response
}
// ---------------------------------------------------------------------------

View File

@@ -182,8 +182,52 @@ module Werkzeug {
override string getAsyncMethodName() { none() }
}
/** A call to a method that writes to a header, assumed to be a response header. */
private class HeaderWriteCall extends Http::Server::ResponseHeaderWrite::Range,
DataFlow::MethodCallNode
{
HeaderWriteCall() {
this.getObject() = instance() and
this.getMethodName() = ["add", "add_header", "set", "set_default", "__setitem__"]
}
override DataFlow::Node getNameArg() { result = this.getArg(0) }
override DataFlow::Node getValueArg() { result = this.getArg(1) }
override predicate nameAllowsNewline() { any() }
override predicate valueAllowsNewline() { none() }
}
/** A dict-like write to a header, assumed to be a response header. */
private class HeaderWriteSubscript extends Http::Server::ResponseHeaderWrite::Range,
DataFlow::Node
{
DataFlow::Node name;
DataFlow::Node value;
HeaderWriteSubscript() {
exists(SubscriptNode subscript |
this.asCfgNode() = subscript and
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
name.asCfgNode() = subscript.getIndex() and
subscript.getObject() = instance().asCfgNode()
)
}
override DataFlow::Node getNameArg() { result = name }
override DataFlow::Node getValueArg() { result = value }
override predicate nameAllowsNewline() { any() }
override predicate valueAllowsNewline() { none() }
}
}
// TODO: `extend` bulk header update
/**
* Provides models for the `werkzeug.datastructures.Authorization` class
*

View File

@@ -41,8 +41,12 @@ module HttpHeaderInjection {
*/
class HeaderWriteAsSink extends Sink {
HeaderWriteAsSink() {
exists(Http::Server::ResponseHeaderWrite headerDeclaration |
this in [headerDeclaration.getNameArg(), headerDeclaration.getValueArg()]
exists(Http::Server::ResponseHeaderWrite headerWrite |
headerWrite.nameAllowsNewline() and
this = headerWrite.getNameArg()
or
headerWrite.valueAllowsNewline() and
this = headerWrite.getValueArg()
)
}
}