Implemented sinks for bulk header updates, and added corresponding tests.

This commit is contained in:
Joe Farebrother
2024-04-03 15:47:01 +01:00
parent 3e9341ff8a
commit a88ad62c00
7 changed files with 158 additions and 50 deletions

View File

@@ -1084,6 +1084,55 @@ module Http {
}
}
/**
* A data-flow node that sets multiple headers in an HTTP response using a dict.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `ResponseHeaderBulkWrite::Range` instead.
*/
class ResponseHeaderBulkWrite extends DataFlow::Node instanceof ResponseHeaderBulkWrite::Range {
/**
* Gets the argument containing the headers dictionary.
*/
DataFlow::Node geBulkArg() { result = super.getBulkArg() }
/**
* Holds if newlines are accepted in the header name argument.
*/
predicate nameAllowsNewline() { super.nameAllowsNewline() }
/**
* Holds if newlines are accepted in the header value argument.
*/
predicate valueAllowsNewline() { super.valueAllowsNewline() }
}
/** Provides a class for modelling bulk header writes on HTTP responses. */
module ResponseHeaderBulkWrite {
/**
*sets multiple headers in an HTTP response using a dict.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `ResponseHeaderBulkWrite` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the argument containing the headers dictionary.
*/
abstract DataFlow::Node getBulkArg();
/**
* Holds if newlines are accepted in the header name argument.
*/
abstract predicate nameAllowsNewline();
/**
* Holds if newlines are accepted in the header value argument.
*/
abstract predicate valueAllowsNewline();
}
}
/**
* A data-flow node that sets a cookie in an HTTP response.
*

View File

@@ -222,14 +222,41 @@ module Flask {
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
{
private class FlaskResponseHeadersInstances extends Werkzeug::Headers::InstanceSource {
FlaskResponseHeadersInstances() {
this.(DataFlow::AttrRead).getObject() = instance() and
this.(DataFlow::AttrRead).getAttributeName() = "headers"
}
}
// TODO: headers arg to make_response
/** A class instantiation of `Response` that sets response headers. */
private class ResponseClassHeadersWrite extends Http::Server::ResponseHeaderBulkWrite::Range,
ClassInstantiation
{
override DataFlow::Node getBulkArg() {
result = [this.getArg(2), this.getArgByName("headers")]
}
override predicate nameAllowsNewline() { any() }
override predicate valueAllowsNewline() { none() }
}
/** A call to `make_response that sets response headers. */
private class MakeResponseHeadersWrite extends Http::Server::ResponseHeaderBulkWrite::Range,
FlaskMakeResponseCall
{
override DataFlow::Node getBulkArg() {
result = this.getArg(2)
or
strictcount(this.getArg(_)) = 2 and
result = this.getArg(1)
}
override predicate nameAllowsNewline() { any() }
override predicate valueAllowsNewline() { none() }
}
}
// ---------------------------------------------------------------------------

View File

@@ -237,9 +237,21 @@ module Werkzeug {
override predicate valueAllowsNewline() { none() }
}
/** A call to `Headers.extend`, assumed to be a response header. */
private class HeaderExtendCall extends Http::Server::ResponseHeaderBulkWrite::Range,
DataFlow::MethodCallNode
{
HeaderExtendCall() { this.calls(instance(), "extend") }
override DataFlow::Node getBulkArg() { result = this.getArg(0) }
override predicate nameAllowsNewline() { any() }
override predicate valueAllowsNewline() { none() }
}
}
// TODO: `extend` bulk header update
/**
* Provides models for the `werkzeug.datastructures.Authorization` class
*

View File

@@ -50,4 +50,25 @@ module HttpHeaderInjection {
)
}
}
/** A key-value pair in a literal for a bulk header update, considered as a single header update. */
// TODO: We could instead consider bulk writes as sinks with an implicit read step of DictionaryKey/DictionaryValue content as needed.
private class HeaderBulkWriteDictLiteral extends Http::Server::ResponseHeaderWrite::Range instanceof Http::Server::ResponseHeaderBulkWrite
{
KeyValuePair item;
HeaderBulkWriteDictLiteral() { item = super.geBulkArg().asExpr().(Dict).getAnItem() }
override DataFlow::Node getNameArg() { result.asExpr() = item.getKey() }
override DataFlow::Node getValueArg() { result.asExpr() = item.getValue() }
override predicate nameAllowsNewline() {
Http::Server::ResponseHeaderBulkWrite.super.nameAllowsNewline()
}
override predicate valueAllowsNewline() {
Http::Server::ResponseHeaderBulkWrite.super.valueAllowsNewline()
}
}
}