BaseHTTPRequestHandler.send_header() builds a header line with "%s: %s\r\n" % (keyword, value) and send_response_only() builds the status line with "%s %d %s\r\n" % (...), both without rejecting CR or LF in the supplied arguments. A value reflected from a request (e.g. into a Location, Set-Cookie or other header) can therefore inject additional headers or split the response.
from http.server import BaseHTTPRequestHandler
class H(BaseHTTPRequestHandler):
def __init__(self):
self.request_version='HTTP/1.1'; self.protocol_version='HTTP/1.1'; self._headers_buffer=[]
h=H(); h.send_header('X-Reflected', 'v\r\nSet-Cookie: injected=1')
print(b''.join(h._headers_buffer))
# b'X-Reflected: v\r\nSet-Cookie: injected=1\r\n'
http.client.putheader, wsgiref.headers.Headers and http.cookies already reject control characters in header names/values; the http.server writers are the remaining ones that interpolate raw. They should reject CR and LF as well.
Linked PRs
BaseHTTPRequestHandler.send_header()builds a header line with"%s: %s\r\n" % (keyword, value)andsend_response_only()builds the status line with"%s %d %s\r\n" % (...), both without rejecting CR or LF in the supplied arguments. A value reflected from a request (e.g. into a Location, Set-Cookie or other header) can therefore inject additional headers or split the response.http.client.putheader,wsgiref.headers.Headersandhttp.cookiesalready reject control characters in header names/values; thehttp.serverwriters are the remaining ones that interpolate raw. They should reject CR and LF as well.Linked PRs