Skip to content
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

Deny more potentially malicious requests with BadRequest #1247

Merged
merged 1 commit into from
Jan 16, 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
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst
- Enable ZMI History tab for ``OFS.Image.File``.
(`#396 <https://github.com/zopefoundation/Zope/pull/396>`_)

- Fix requests from spam/pentests to return BadRequest/400 errors
- Deny some spam/pentests requests with BadRequest/400 or NotFound/404 errors.

- Fix a ``ResourceWarning`` emitted when uploading large files.
(`#1242 <https://github.com/zopefoundation/Zope/issues/1242>`_)
Expand Down
17 changes: 14 additions & 3 deletions src/ZPublisher/HTTPRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from AccessControl.tainted import should_be_tainted as base_should_be_tainted
from AccessControl.tainted import taint_string
from multipart import Headers
from multipart import MultipartError
from multipart import MultipartParser
from multipart import parse_options_header
from zExceptions import BadRequest
Expand Down Expand Up @@ -499,7 +500,10 @@ def processInputs(

meth = None

self._fs = fs = ZopeFieldStorage(fp, environ)
try:
self._fs = fs = ZopeFieldStorage(fp, environ)
except MultipartError as exc:
raise BadRequest(exc)
self._file = fs.file

if 'HTTP_SOAPACTION' in environ:
Expand Down Expand Up @@ -545,8 +549,15 @@ def processInputs(
if key is None:
continue
character_encoding = ""
key = item.name.encode("latin-1").decode(
item.name_charset or self.charset)

try:
key = item.name.encode("latin-1").decode(
item.name_charset or self.charset)
except UnicodeDecodeError as exc:
# Incoming request contains fields with names that cannot
# be represented in the server character set. Potentially
# malicious, so raise BadRequest.
raise BadRequest(exc)

if hasattr(item, 'file') and \
hasattr(item, 'filename') and \
Expand Down
46 changes: 46 additions & 0 deletions src/ZPublisher/tests/testHTTPRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,20 @@ def test_processInputs_xmlrpc_method(self):
with self.assertRaises(BadRequest):
req.processInputs()

def test_processInputs_xmlrpc_ResponseError(self):
# xmlrpc ResponseErrors should be caught and converted to
# zExceptions.BadRequest to prevent error messages from spam or pen
# test requests.
TEST_METHOD_CALL = (
b'<?xml version="1.0"?>'
b'<unknown><foo>test</foo></unknown>'
)
environ = self._makePostEnviron(body=TEST_METHOD_CALL)
environ['CONTENT_TYPE'] = 'text/xml'
req = self._makeOne(stdin=BytesIO(TEST_METHOD_CALL), environ=environ)
with self.assertRaises(BadRequest):
req.processInputs()

def test_processInputs_SOAP(self):
# ZPublisher does not really have SOAP support
# all it does is put the body into ``SOAPXML``
Expand Down Expand Up @@ -968,6 +982,22 @@ def test_processInputs_unspecified_file(self):
self.assertEqual(f.filename, "")
self.assertEqual(list(f), [])

def test_processInputs_no_multipart_boundaries(self):
s = BytesIO(TEST_NO_BOUNDARIES)
environ = self._makePostEnviron(body=TEST_NO_BOUNDARIES)
req = self._makeOne(stdin=s, environ=environ)
with self.assertRaises(BadRequest):
req.processInputs()

def test_processInputs_invalid_field_name(self):
# Request fields with names that cannot be decoded should raise
# a BadRequest
s = BytesIO(TEST_FIELD_INVALID_NAME)
environ = self._makePostEnviron(body=TEST_FIELD_INVALID_NAME)
req = self._makeOne(stdin=s, environ=environ)
with self.assertRaises(BadRequest):
req.processInputs()

def test__authUserPW_simple(self):
user_id = 'user'
password = 'password'
Expand Down Expand Up @@ -1668,3 +1698,19 @@ def __init__(self, file):
%s
--12345--
''' % 'äöü'.encode("latin-1")

TEST_NO_BOUNDARIES = b'''
Content-Disposition: form-data; name="smallfile"; filename="smallfile"
Content-Type: application/octet-stream

test
'''

TEST_FIELD_INVALID_NAME = b'''
--12345
Content-Disposition: form-data; name="%sxxx"
Content-Type: text/plain; charset=latin-1

test
--12345--
''' % chr(173).encode("latin-1")
Loading