Skip to content

python: model send_header from http.server #19432

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions python/ql/lib/change-notes/2025-04-30-model-send-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added header write model for `send_header` in `http.server`.
15 changes: 15 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Stdlib.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1963,6 +1963,21 @@ module StdlibPrivate {
/** Gets a reference to an instance of the `BaseHttpRequestHandler` class or any subclass. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }

/** A call to a method that writes to a response header. */
private class HeaderWriteCall extends Http::Server::ResponseHeaderWrite::Range,
DataFlow::MethodCallNode
{
HeaderWriteCall() { this.calls(instance(), "send_header") }

override DataFlow::Node getNameArg() { result = this.getArg(0) }

override DataFlow::Node getValueArg() { result = this.getArg(1) }

override predicate nameAllowsNewline() { any() }
Copy link
Preview

Copilot AI Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider refining the predicate for nameAllowsNewline if possible, rather than using a blanket 'any()' which may reduce the precision of the taint tracking.

Suggested change
override predicate nameAllowsNewline() { any() }
override predicate nameAllowsNewline() { not exists(this.getNameArg().toString().regexpMatch(".*[\n\r].*")) }

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not about the argument itself, but rather wether the server configuration would allow a newline to appear here. http.server does not have any protection against this, so any is correct.


override predicate valueAllowsNewline() { any() }
Copy link
Preview

Copilot AI Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider refining the predicate for valueAllowsNewline if feasible, to improve the accuracy of tracking header values in the taint analysis.

Suggested change
override predicate valueAllowsNewline() { any() }
override predicate valueAllowsNewline() {
not exists(DataFlow::Node value | value = this.getValueArg() |
value.toString().matches("%0A") or value.toString().matches("%0D")
)
}

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for the name argument above.

}

private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
nodeFrom = instance() and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def taint_sources(self):
def do_GET(self): # $ requestHandler
# send_response will log a line to stderr
self.send_response(200)
self.send_header("Content-type", "text/plain; charset=utf-8")
self.send_header("Content-type", "text/plain; charset=utf-8") # $ headerWriteNameUnsanitized="Content-type" headerWriteValueUnsanitized="text/plain; charset=utf-8"
self.end_headers()
self.wfile.write(b"Hello BaseHTTPRequestHandler\n")
self.wfile.writelines([b"1\n", b"2\n", b"3\n"])
Expand Down