mirror of https://github.com/python/cpython.git
gh-100001: Omit control characters in http.server stderr logs. (GH-100002)
Replace control characters in http.server.BaseHTTPRequestHandler.log_message with an escaped \xHH sequence to avoid causing problems for the terminal the output is printed to.
(cherry picked from commit d8ab0a4dfa
)
Co-authored-by: Gregory P. Smith <greg@krypto.org>
This commit is contained in:
parent
f3a58cece6
commit
a726f747e6
|
@ -512,3 +512,10 @@ Security Considerations
|
||||||
:class:`SimpleHTTPRequestHandler` will follow symbolic links when handling
|
:class:`SimpleHTTPRequestHandler` will follow symbolic links when handling
|
||||||
requests, this makes it possible for files outside of the specified directory
|
requests, this makes it possible for files outside of the specified directory
|
||||||
to be served.
|
to be served.
|
||||||
|
|
||||||
|
Earlier versions of Python did not scrub control characters from the
|
||||||
|
log messages emitted to stderr from ``python -m http.server`` or the
|
||||||
|
default :class:`BaseHTTPRequestHandler` ``.log_message``
|
||||||
|
implementation. This could allow to remote clients connecting to your
|
||||||
|
server to send nefarious control codes to your terminal.
|
||||||
|
|
||||||
|
|
|
@ -93,6 +93,7 @@
|
||||||
import html
|
import html
|
||||||
import http.client
|
import http.client
|
||||||
import io
|
import io
|
||||||
|
import itertools
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import posixpath
|
import posixpath
|
||||||
|
@ -562,6 +563,10 @@ def log_error(self, format, *args):
|
||||||
|
|
||||||
self.log_message(format, *args)
|
self.log_message(format, *args)
|
||||||
|
|
||||||
|
# https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
|
||||||
|
_control_char_table = str.maketrans(
|
||||||
|
{c: fr'\x{c:02x}' for c in itertools.chain(range(0x20), range(0x7f,0xa0))})
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
"""Log an arbitrary message.
|
"""Log an arbitrary message.
|
||||||
|
|
||||||
|
@ -577,12 +582,16 @@ def log_message(self, format, *args):
|
||||||
The client ip and current date/time are prefixed to
|
The client ip and current date/time are prefixed to
|
||||||
every message.
|
every message.
|
||||||
|
|
||||||
|
Unicode control characters are replaced with escaped hex
|
||||||
|
before writing the output to stderr.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
message = format % args
|
||||||
sys.stderr.write("%s - - [%s] %s\n" %
|
sys.stderr.write("%s - - [%s] %s\n" %
|
||||||
(self.address_string(),
|
(self.address_string(),
|
||||||
self.log_date_time_string(),
|
self.log_date_time_string(),
|
||||||
format%args))
|
message.translate(self._control_char_table)))
|
||||||
|
|
||||||
def version_string(self):
|
def version_string(self):
|
||||||
"""Return the server software version string."""
|
"""Return the server software version string."""
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
import datetime
|
import datetime
|
||||||
import threading
|
import threading
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from io import BytesIO
|
from io import BytesIO, StringIO
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from test import support
|
from test import support
|
||||||
|
@ -990,6 +990,25 @@ def verify_http_server_response(self, response):
|
||||||
match = self.HTTPResponseMatch.search(response)
|
match = self.HTTPResponseMatch.search(response)
|
||||||
self.assertIsNotNone(match)
|
self.assertIsNotNone(match)
|
||||||
|
|
||||||
|
def test_unprintable_not_logged(self):
|
||||||
|
# We call the method from the class directly as our Socketless
|
||||||
|
# Handler subclass overrode it... nice for everything BUT this test.
|
||||||
|
self.handler.client_address = ('127.0.0.1', 1337)
|
||||||
|
log_message = BaseHTTPRequestHandler.log_message
|
||||||
|
with mock.patch.object(sys, 'stderr', StringIO()) as fake_stderr:
|
||||||
|
log_message(self.handler, '/foo')
|
||||||
|
log_message(self.handler, '/\033bar\000\033')
|
||||||
|
log_message(self.handler, '/spam %s.', 'a')
|
||||||
|
log_message(self.handler, '/spam %s.', '\033\x7f\x9f\xa0beans')
|
||||||
|
stderr = fake_stderr.getvalue()
|
||||||
|
self.assertNotIn('\033', stderr) # non-printable chars are caught.
|
||||||
|
self.assertNotIn('\000', stderr) # non-printable chars are caught.
|
||||||
|
lines = stderr.splitlines()
|
||||||
|
self.assertIn('/foo', lines[0])
|
||||||
|
self.assertIn(r'/\x1bbar\x00\x1b', lines[1])
|
||||||
|
self.assertIn('/spam a.', lines[2])
|
||||||
|
self.assertIn('/spam \\x1b\\x7f\\x9f\xa0beans.', lines[3])
|
||||||
|
|
||||||
def test_http_1_1(self):
|
def test_http_1_1(self):
|
||||||
result = self.send_typical_request(b'GET / HTTP/1.1\r\n\r\n')
|
result = self.send_typical_request(b'GET / HTTP/1.1\r\n\r\n')
|
||||||
self.verify_http_server_response(result[0])
|
self.verify_http_server_response(result[0])
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
``python -m http.server`` no longer allows terminal control characters sent
|
||||||
|
within a garbage request to be printed to the stderr server log.
|
||||||
|
|
||||||
|
This is done by changing the :mod:`http.server` :class:`BaseHTTPRequestHandler`
|
||||||
|
``.log_message`` method to replace control characters with a ``\xHH`` hex escape
|
||||||
|
before printing.
|
Loading…
Reference in New Issue