You are viewing a plain text version of this content. The canonical link for it is here.
Posted to mod_python-dev@quetz.apache.org by Steven Hazel <sa...@thalassocracy.org> on 2004/02/15 10:07:11 UTC

FieldStorage can't limit Content-Length

I'm using mod_python to build a web site that allows photo uploads, and 
was surpised to find that the FieldStorage class doesn't provide a way 
to limit the Content-Length of requests.  That's a potential Denial of 
Service vulnerability.

I'm attaching the wrapper module I wrote to fix the problem for my site 
-- the __init__ method could be dropped into FieldStorage to fix the 
problem in the mod_python distribution.

-- 
Steven Hazel
sah@thalassocracy.org


Re: FieldStorage can't limit Content-Length

Posted by Steven Hazel <sa...@thalassocracy.org>.
David Fraser wrote:

> Note that you can do this directly with an Apache Configuration 
> Directive:
> http://httpd.apache.org/docs-2.0/mod/core.html#limitrequestbody
> LimitRequestBody limits the total size of input fields...
> In many ways this is more effective than the solution provided below 
> as it is the standard Apache solution - unless for some reason this 
> needs to be done within mod_python

It would be a lot more convenient for me to control this from within 
mod_python, so that I can specify a reasonable limit for each web 
service along with all of the web service's other configuration stuff.  
I'll possibly even want to set different limits for different user accounts.

Is there a way to set Apache directives from Python code?  I can't find 
one...

-- 
Steven Hazel
sah@thalassocracy.org


Re: FieldStorage can't limit Content-Length

Posted by David Fraser <da...@sjsoft.com>.
Steven Hazel wrote:

> I'm using mod_python to build a web site that allows photo uploads, 
> and was surpised to find that the FieldStorage class doesn't provide a 
> way to limit the Content-Length of requests.  That's a potential 
> Denial of Service vulnerability.
>
> I'm attaching the wrapper module I wrote to fix the problem for my 
> site -- the __init__ method could be dropped into FieldStorage to fix 
> the problem in the mod_python distribution.

Note that you can do this directly with an Apache Configuration Directive:
http://httpd.apache.org/docs-2.0/mod/core.html#limitrequestbody
LimitRequestBody limits the total size of input fields...
In many ways this is more effective than the solution provided below as 
it is the standard Apache solution - unless for some reason this needs 
to be done within mod_python

David

>
>------------------------------------------------------------------------
>
>import StringIO
>from mod_python import apache
>from mod_python import util
>from mod_python.util import Field
>from mod_python.util import parse_header
>from mod_python.util import parse_qsl
>
>class FieldStorage(util.FieldStorage):
>
>    def __init__(self, req, keep_blank_values=0, strict_parsing=0,
>                 max_content_length=-1):
>
>        self.list = []
>
>        # always process GET-style parameters
>        if req.args:
>            pairs = parse_qsl(req.args, keep_blank_values)
>            for pair in pairs:
>                file = StringIO.StringIO(pair[1])
>                self.list.append(Field(pair[0], file, "text/plain", {},
>                                       None, {}))
>
>        if req.method == "POST":
>
>            try:
>                clen = int(req.headers_in["content-length"])
>            except (KeyError, ValueError):
>                # absent content-length is not acceptable
>                raise apache.SERVER_RETURN, apache.HTTP_LENGTH_REQUIRED
>
>            if ((max_content_length != -1) and
>                (clen > max_content_length)):
>                raise ValueError, ("content length exceeds maximum", clen)
>
>
>            if not req.headers_in.has_key("content-type"):
>                ctype = "application/x-www-form-urlencoded"
>            else:
>                ctype = req.headers_in["content-type"]
>
>            if ctype == "application/x-www-form-urlencoded":
>
>                pairs = parse_qsl(req.read(clen), keep_blank_values)
>                for pair in pairs:
>                    file = StringIO.StringIO(pair[1])
>                    self.list.append(Field(pair[0], file, "text/plain",
>                                     {}, None, {}))
>
>            elif ctype[:10] == "multipart/":
>
>                # figure out boundary
>                try:
>                    i = ctype.lower().rindex("boundary=")
>                    boundary = ctype[i+9:]
>                    if len(boundary) >= 2 and boundary[0] == boundary[-1] == '"':
>                        boundary = boundary[1:-1]
>                    boundary = "--" + boundary
>                except ValueError:
>                    raise apache.SERVER_RETURN, apache.HTTP_BAD_REQUEST
>
>                #read until boundary
>                line = req.readline()
>                sline = line.strip()
>                while line and sline != boundary:
>                    line = req.readline()
>                    sline = line.strip()
>
>                while 1:
>
>                    ## parse headers
>
>                    ctype, type_options = "text/plain", {}
>                    disp, disp_options = None, {}
>                    headers = apache.make_table()
>
>                    line = req.readline()
>                    sline = line.strip()
>                    if not line or sline == (boundary + "--"):
>                        break
>
>                    while line and line not in ["\n", "\r\n"]:
>                        h, v = line.split(":", 1)
>                        headers.add(h, v)
>                        h = h.lower()
>                        if h == "content-disposition":
>                            disp, disp_options = parse_header(v)
>                        elif h == "content-type":
>                            ctype, type_options = parse_header(v)
>                        line = req.readline()
>                        sline = line.strip()
>
>                    if disp_options.has_key("name"):
>                        name = disp_options["name"]
>                    else:
>                        name = None
>
>                    # is this a file?
>                    if disp_options.has_key("filename"):
>                        file = self.make_file()
>                    else:
>                        file = StringIO.StringIO()
>
>                    # read it in
>                    self.read_to_boundary(req, boundary, file)
>                    file.seek(0)
>
>                    # make a Field
>                    field = Field(name, file, ctype, type_options,
>                                  disp, disp_options)
>                    field.headers = headers
>                    if disp_options.has_key("filename"):
>                        field.filename = disp_options["filename"]
>
>                    self.list.append(field)
>
>            else:
>                # we don't understand this content-type
>                raise apache.SERVER_RETURN, apache.HTTP_NOT_IMPLEMENTED
>  
>