-
Notifications
You must be signed in to change notification settings - Fork 12
/
http_header_field_manipulation.py
180 lines (151 loc) · 6.13 KB
/
http_header_field_manipulation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# -*- encoding: utf-8 -*-
#
# :authors: Arturo Filastò
# :licence: see LICENSE
import random
import json
import yaml
from twisted.python import usage
from ooni.utils import log, net, randomStr
from ooni.templates import httpt
from ooni.common.txextra import TrueHeaders
def random_capitalization(string):
output = ""
original_string = string
string = string.swapcase()
for i in range(len(string)):
if random.randint(0, 1):
output += string[i].swapcase()
else:
output += string[i]
if original_string == output:
return random_capitalization(output)
else:
return output
class UsageOptions(usage.Options):
optParameters = [
['backend', 'b', None,
'URL of the backend to use for sending the requests.'],
['headers', 'h', None,
'Specify a yaml formatted file from which to read '
'the request headers to send.']
]
class HTTPHeaderFieldManipulation(httpt.HTTPTest):
"""
It performes HTTP requests with request headers that vary capitalization
towards a backend. If the headers reported by the server differ from
the ones we sent, then we have detected tampering.
"""
name = "HTTP Header Field Manipulation"
description = "Checks if the HTTP request the server " \
"sees is the same as the one that the client has created."
author = "Arturo Filastò"
version = "0.2.0"
randomizeUA = False
usageOptions = UsageOptions
requiredTestHelpers = {'backend': 'http-return-json-headers'}
requiredOptions = ['backend']
requiresTor = False
requiresRoot = False
def setUp(self):
super(HTTPHeaderFieldManipulation, self).setUp()
self.url = self.localOptions['backend']
def get_headers(self):
headers = {}
if self.localOptions['headers']:
try:
f = open(self.localOptions['headers'])
except IOError:
raise Exception("Specified input file does not exist")
content = ''.join(f.readlines())
f.close()
headers = yaml.safe_load(content)
return headers
else:
# XXX generate these from a random choice taken from
# whatheaders.com
# http://s3.amazonaws.com/data.whatheaders.com/whatheaders-latest.xml.zip
headers = {
"User-Agent": [
random.choice(
net.userAgents)],
"Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],
"Accept-Encoding": ["gzip,deflate,sdch"],
"Accept-Language": ["en-US,en;q=0.8"],
"Accept-Charset": ["ISO-8859-1,utf-8;q=0.7,*;q=0.3"],
"Host": [
randomStr(15) +
'.com']}
return headers
def get_random_caps_headers(self):
headers = {}
normal_headers = self.get_headers()
for k, v in normal_headers.items():
new_key = random_capitalization(k)
headers[new_key] = v
return headers
def processResponseBody(self, data):
self.check_for_tampering(data)
def check_for_tampering(self, data):
"""
Here we do checks to verify if the request we made has been tampered
with. We have 3 categories of tampering:
* **total** when the response is not a json object and therefore we were not
able to reach the ooniprobe test backend
* **request_line_capitalization** when the HTTP Request line (e.x. GET /
HTTP/1.1) does not match the capitalization we set.
* **header_field_number** when the number of headers we sent does not match
with the ones the backend received
* **header_name_capitalization** when the header field names do not match
those that we sent.
* **header_field_value** when the header field value does not match with the
one we transmitted.
"""
log.msg("Checking for tampering on %s" % self.url)
self.report['tampering'] = {
'total': False,
'request_line_capitalization': False,
'header_name_capitalization': False,
'header_field_value': False,
'header_field_number': False
}
try:
response = json.loads(data)
except ValueError:
self.report['tampering']['total'] = True
return
request_request_line = "%s / HTTP/1.1" % self.request_method
try:
response_request_line = response['request_line']
response_headers_dict = response['headers_dict']
except KeyError:
self.report['tampering']['total'] = True
return
if request_request_line != response_request_line:
self.report['tampering']['request_line_capitalization'] = True
request_headers = TrueHeaders(self.request_headers)
diff = request_headers.getDiff(TrueHeaders(response_headers_dict),
ignore=['Connection'])
if diff:
self.report['tampering']['header_field_name'] = True
else:
self.report['tampering']['header_field_name'] = False
self.report['tampering']['header_name_diff'] = list(diff)
log.msg(" total: %(total)s" % self.report['tampering'])
log.msg(
" request_line_capitalization: %(request_line_capitalization)s" %
self.report['tampering'])
log.msg(
" header_name_capitalization: %(header_name_capitalization)s" %
self.report['tampering'])
log.msg(
" header_field_value: %(header_field_value)s" %
self.report['tampering'])
log.msg(
" header_field_number: %(header_field_number)s" %
self.report['tampering'])
def test_get_random_capitalization(self):
self.request_method = random_capitalization("GET")
self.request_headers = self.get_random_caps_headers()
return self.doRequest(self.url, self.request_method,
headers=self.request_headers)