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

fix bugs in CRLF injection protection #2

Merged
merged 9 commits into from
Jun 29, 2015
30 changes: 30 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
environment:
matrix:
- PYTHON: "C:\\Python27"
PYTHON_VERSION: "2.7.x"
PYTHON_ARCH: "32"

- PYTHON: "C:\\Python27-x64"
PYTHON_VERSION: "2.7.x"
PYTHON_ARCH: "64"

install:
- ECHO "Filesystem root:"
- ps: "ls \"C:/\""

- ECHO "Installed SDKs:"
- ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\""

# Check that we have the expected version and architecture for Python
- "python --version"
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""

# Install the build dependencies of the project.
- "python setup.py -q install"
- "pip install --use-mirrors simplejson coverage"

build: false # Not a C# project, build stuff at the test step instead.

test_script:
# Build the compiled extension and run the project tests
- "python build.py analyse"
26 changes: 17 additions & 9 deletions aspen/http/baseheaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,22 @@


from aspen.backcompat import CookieError, SimpleCookie

from aspen.http.mapping import CaseInsensitiveMapping
from aspen.utils import typecheck


def _check_for_CRLF(value):
"""CRLF injection allows an attacker to insert arbitrary headers.

http://www.acunetix.com/websitesecurity/crlf-injection/
https://github.com/gratipay/security-qf35us/issues/1

"""
if b'\r' in value or b'\n' in value:
from aspen.exceptions import CRLFInjection
raise CRLFInjection()


class BaseHeaders(CaseInsensitiveMapping):
"""Represent the headers in an HTTP Request or Response message.
http://stackoverflow.com/questions/5423223/how-to-send-non-english-unicode-string-using-http-header
Expand Down Expand Up @@ -54,16 +65,13 @@ def genheaders():


def __setitem__(self, name, value):
"""Extend to protect against CRLF injection:

http://www.acunetix.com/websitesecurity/crlf-injection/

"""
if b'\n' in value:
from aspen.exceptions import CRLFInjection
raise CRLFInjection()
_check_for_CRLF(value)
super(BaseHeaders, self).__setitem__(name, value)

def add(self, name, value):
_check_for_CRLF(value)
super(BaseHeaders, self).add(name, value)


def raw(self):
"""Return the headers as a string, formatted for an HTTP message.
Expand Down
21 changes: 17 additions & 4 deletions tests/test_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@
from pytest import raises

from aspen import Response

from aspen.exceptions import CRLFInjection
from aspen.http.mapping import Mapping, CaseInsensitiveMapping

from aspen.http.baseheaders import BaseHeaders
from aspen.http.request import Querystring



def test_mapping_subscript_assignment_clobbers():
Expand Down Expand Up @@ -215,3 +212,19 @@ def test_case_insensitive_mapping_ones_is_case_insensitive():
def test_headers_can_be_raw_when_non_ascii():
headers = BaseHeaders(b'Foo: bëar\r\nOh: Yeah!')
assert headers.raw == b'Foo: bëar\r\nOh: Yeah!'

def test_headers_reject_CR_injection():
with raises(CRLFInjection):
BaseHeaders(b'')[b'foo'] = b'\rbar'

def test_headers_reject_LF_injection():
with raises(CRLFInjection):
BaseHeaders(b'')[b'foo'] = b'\nbar'

def test_headers_reject_CR_injection_from_add():
with raises(CRLFInjection):
BaseHeaders(b'').add(b'foo', b'\rbar')

def test_headers_reject_LF_injection_from_add():
with raises(CRLFInjection):
BaseHeaders(b'').add(b'foo', b'\nbar')