Skip to content

Commit

Permalink
make Zope's parameters for denial of service protection configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
d-maurer committed Jul 13, 2023
1 parent 345d656 commit 9282eb5
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -29,6 +29,9 @@ https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst
Note: ``mapply`` still does not support keyword only, var positional
and var keyword parameters.

- Make Zope's parameters for denial of service protection configurable
`#1141 <https://github.com/zopefoundation/Zope/issues/1141>_`.


5.8.3 (2023-06-15)
------------------
Expand Down
10 changes: 7 additions & 3 deletions src/ZPublisher/HTTPRequest.py
Expand Up @@ -53,9 +53,9 @@

# DOS attack protection -- limiting the amount of memory for forms
# probably should become configurable
FORM_MEMORY_LIMIT = 2 ** 20 # memory limit for forms
FORM_DISK_LIMIT = 2 ** 30 # disk limit for forms
FORM_MEMFILE_LIMIT = 4000 # limit for `BytesIO` -> temporary file switch
FORM_MEMORY_LIMIT = 2 ** 20 # memory limit for forms
FORM_DISK_LIMIT = 2 ** 30 # disk limit for forms
FORM_MEMFILE_LIMIT = 2 ** 12 # limit for `BytesIO` -> temporary file switch


# This may get overwritten during configuration
Expand Down Expand Up @@ -1354,6 +1354,8 @@ def sane_environment(env):

class ValueDescriptor:
"""(non data) descriptor to compute `value` from `file`."""
VALUE_LIMIT = FORM_MEMORY_LIMIT

def __get__(self, inst, owner=None):
if inst is None:
return self
Expand All @@ -1364,6 +1366,8 @@ def __get__(self, inst, owner=None):
fpos = None
try:
v = file.read()
if self.VALUE_LIMIT and file.read(1):
raise BadRequest("data exceeds memory limit")
if fpos is None:
# store the value as we cannot read it again
inst.value = v
Expand Down
8 changes: 8 additions & 0 deletions src/Zope2/Startup/handlers.py
Expand Up @@ -90,3 +90,11 @@ def handleWSGIConfig(cfg, multihandler):
if not name.startswith('_'):
handlers[name] = value
return multihandler(handlers)


def dos_protection(cfg):
if cfg is None:
return
from ZPublisher import HTTPRequest
for attr in cfg.getSectionAttributes():
setattr(HTTPRequest, attr.upper(), getattr(cfg, attr))
43 changes: 43 additions & 0 deletions src/Zope2/Startup/tests/test_schema.py
Expand Up @@ -190,3 +190,46 @@ def test_ms_public_header(self):
self.assertFalse(webdav.enable_ms_public_header)
finally:
webdav.enable_ms_public_header = default_setting

def test_dos_protection(self):
from ZPublisher import HTTPRequest

params = ["FORM_%s_LIMIT" % name
for name in ("MEMORY", "DISK", "MEMFILE")]
defaults = dict((name, getattr(HTTPRequest, name)) for name in params)

try:
# missing section
conf, handler = self.load_config_text("""\
instancehome <<INSTANCE_HOME>>
""")
handleWSGIConfig(None, handler)
for name in params:
self.assertEqual(getattr(HTTPRequest, name), defaults[name])

# empty section
conf, handler = self.load_config_text("""\
instancehome <<INSTANCE_HOME>>
<dos_protection />
""")
handleWSGIConfig(None, handler)
for name in params:
self.assertEqual(getattr(HTTPRequest, name), defaults[name])

# configured values

# empty section
conf, handler = self.load_config_text("""\
instancehome <<INSTANCE_HOME>>
<dos_protection>
form-memory-limit 1KB
form-disk-limit 1KB
form-memfile-limit 1KB
</dos_protection>
""")
handleWSGIConfig(None, handler)
for name in params:
self.assertEqual(getattr(HTTPRequest, name), 1024)
finally:
for name in params:
setattr(HTTPRequest, name, defaults[name])
31 changes: 31 additions & 0 deletions src/Zope2/Startup/wsgischema.xml
Expand Up @@ -80,6 +80,34 @@

</sectiontype>

<sectiontype name="dos_protection">

<description>Defines parameters for DOS attack protection</description>

<key name="form-memory-limit" datatype="byte-size" default="1MB">
<description>
The maximum size for each part in a multipart post request,
for the complete body in an urlencoded post request
and for the complete request body when accessed as bytes
(rather than a file).
</description>
</key>

<key name="form-disk-limit" datatype="byte-size" default="1GB">
<description>
The maximum size of a POST request body
</description>
</key>

<key name="form-memfile-limit" datatype="byte-size" default="4KB">
<description>
The value of form variables of type file with larger size
are stored on disk rather than in memory.
</description>
</key>
</sectiontype>


<!-- end of type definitions -->

<!-- schema begins -->
Expand Down Expand Up @@ -385,4 +413,7 @@
<metadefault>off</metadefault>
</key>

<section type="dos_protection" handler="dos_protection"
name="*" attribute="dos_protection" />

</schema>
30 changes: 30 additions & 0 deletions src/Zope2/utilities/skel/etc/zope.conf.in
Expand Up @@ -250,3 +250,33 @@ instancehome $INSTANCE
# security-policy-implementation python
# verbose-security on

<dos_protection>
#
# Description:
# You can use this section to configure Zope's
# parameters for denial of service attack protection.
# The examples below document the default values.

# Parameter: form-memory-limit
# Description:
# The maximum size for each part in a multipart post request,
# for the complete body in an urlencoded post request
# and for the complete request body when accessed as bytes
# (rather than a file).
# Example:
# form-memory-limit 1MB

# Parameter: form-disk-limit
# Description:
# The maximum size of a POST request body
# Example:
# form-disk-limit 1GB

# Parameter: form-memfile-limit
# Description:
# The value of form variables of type file with larger size
# are stored on disk rather than in memory.
# Example:
# form-memfile-limit 4KB

</dos_protection>

0 comments on commit 9282eb5

Please sign in to comment.