Skip to content

Commit

Permalink
* Test to reproduce issue when http headers with non-ascii chars wer…
Browse files Browse the repository at this point in the history
…e sent

 * Modified handler to use "the best available" (but poorly supported) encoding for http header values
  • Loading branch information
andresriancho committed Jun 11, 2019
1 parent 1022b7d commit 36cfc3a
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 6 deletions.
56 changes: 50 additions & 6 deletions w3af/core/data/url/handlers/keepalive/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
reads, and added a hack for the HEAD method.
- SNI support for SSL
"""
import threading
import time
import socket
import urllib2
import httplib
import socket
import time

import OpenSSL
import threading

from email.base64mime import header_encode
from httplib import _is_legal_header_name, _is_illegal_header_value

from .utils import debug, error, to_utf8_raw
from .connection_manager import ConnectionManager
Expand Down Expand Up @@ -362,8 +364,50 @@ def _start_transaction(self, conn, req):
header_dict.update(req.unredirected_hdrs)

for k, v in header_dict.iteritems():
conn.putheader(to_utf8_raw(k),
to_utf8_raw(v))
#
# Handle case where the key or value is None (strange but could happen)
#
if k is None:
continue

if v is None:
v = ''

#
# Encode the key and value as UTF-8 and try to send them to the wire
#
k = to_utf8_raw(k)
v = to_utf8_raw(v)

try:
conn.putheader(k, v)
except ValueError:
#
# The httplib adds some restrictions to the characters which can
# be sent in header names and values (see putheader function
# definition).
#
# Sending non-ascii characters in HTTP header values is difficult,
# since servers usually ignore the encoding. From stackoverflow:
#
# Historically, HTTP has allowed field content with text in the
# ISO-8859-1 charset [ISO-8859-1], supporting other charsets only
# through use of [RFC2047] encoding. In practice, most HTTP header
# field values use only a subset of the US-ASCII charset [USASCII].
# Newly defined header fields SHOULD limit their field values to
# US-ASCII octets. A recipient SHOULD treat other octets in field
# content (obs-text) as opaque data.
#
# TL;DR: we use RFC2047 encoding here, knowing that it will only
# work in 1% of the remote servers, but it is our best bet
#
if not _is_legal_header_name(k):
k = header_encode(k, charset='utf-8', keep_eols=True)

if _is_illegal_header_value(v):
v = header_encode(v, charset='utf-8', keep_eols=True)

conn.putheader(k, v)

conn.endheaders()

Expand Down
58 changes: 58 additions & 0 deletions w3af/plugins/tests/audit/test_response_splitting.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import urllib

from nose.plugins.attrib import attr
from email.header import decode_header

from w3af.plugins.tests.helper import PluginTest, PluginConfig, MockResponse

Expand Down Expand Up @@ -137,3 +138,60 @@ def test_found_response_splitting_modifies_response(self):
self.assertEquals('Parameter modifies response headers', vuln.get_name())
self.assertEquals('http://w3af.org/', str(vuln.get_url()))
self.assertEquals('header', vuln.get_token_name())


class ResponseSplittingHeaderMockResponse(MockResponse):
def get_response(self, http_request, uri, response_headers):
referer = http_request.headers.get('Referer') or ''
headers_to_inject = decode_header(referer)[0][0]

header_name_1 = 'somevalue'

try:
headers_to_inject = headers_to_inject.split('\n')
header_value_1 = headers_to_inject[0].strip()

headers_to_inject = headers_to_inject[1]
header_name_2, header_value_2 = headers_to_inject.split(':')
header_name_2 = header_name_2.strip()
header_value_2 = header_value_2.strip()
except:
return self.status, response_headers, self.body
else:
response_headers[header_name_1] = header_value_1
response_headers[header_name_2] = header_value_2
return self.status, response_headers, self.body


class TestResponseSplittingHeader(PluginTest):
target_url = 'http://w3af.org/'
target_url_re = re.compile('http://w3af\\.org/.*')

MOCK_RESPONSES = [ResponseSplittingHeaderMockResponse(target_url_re,
body='',
method='GET',
status=200)]
_run_configs = {
'cfg': {
'target': target_url,
'plugins': {
'audit': (PluginConfig('response_splitting'),),
},
'misc_settings': {'fuzzable_headers': ['referer']}
},
}

def test_response_splitting_headers(self):
cfg = self._run_configs['cfg']
self._scan(cfg['target'],
cfg['plugins'],
misc_settings=cfg['misc_settings'])

vulns = self.kb.get('response_splitting', 'response_splitting')
self.assertEquals(1, len(vulns), vulns)

# Now some tests around specific details of the found vuln
vuln = vulns[0]
self.assertEquals('Response splitting vulnerability', vuln.get_name())
self.assertEquals('http://w3af.org/', str(vuln.get_url()))
self.assertEquals('referer', vuln.get_token_name())

0 comments on commit 36cfc3a

Please sign in to comment.