source: trunk/bin/bletchley-http2py @ 85

Last change on this file since 85 was 82, checked in by tim, 9 years ago

minor fixes

  • Property svn:executable set to *
File size: 9.5 KB
Line 
1#!/usr/bin/env python3
2#-*- mode: Python;-*-
3#
4# Requires Python 3+
5
6'''
7This script reads a raw HTTP request and writes to stdout a Python
8script.  The generated script sends the same (or a very similar)
9request using the standard httplib/http.client library, or optionally
10using the more user friendly python-requests library.
11
12Certainly if you have a raw request, you could simply send it via TCP
13sockets, but if for some reason the server behaves oddly with flow control,
14insists on using gzip/deflate encoding, insists on using chunked encoding,
15or any number of other annoying things, then using an HTTP library is a
16lot more convenient.  This script attempts to make the conversion from a
17raw HTTP request to HTTP library calls easy.
18
19
20Copyright (C) 2011-2013 Virtual Security Research, LLC
21Copyright (C) 2014-2015 Blindspot Security LLC
22Author: Timothy D. Morgan
23
24 This program is free software: you can redistribute it and/or modify
25 it under the terms of the GNU Lesser General Public License, version 3,
26 as published by the Free Software Foundation.
27
28 This program is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31 GNU General Public License for more details.
32
33 You should have received a copy of the GNU General Public License
34 along with this program.  If not, see <http://www.gnu.org/licenses/>.
35'''
36
37import sys
38import argparse
39import pprint
40import urllib.parse
41
42bopen = lambda f: open(f, 'rb')
43
44parser = argparse.ArgumentParser(
45    description='A script which accepts an HTTP request and prints out a'
46    ' generated Python script which sends a similar request.  This is useful'
47    ' when one wants to automate sending a large number of requests to a'
48    ' particular page or application.'
49    ' For more information, see: http://code.google.com/p/bletchley/wiki/Overview')
50parser.add_argument(
51    'requestfile', type=bopen, nargs='?', default=sys.stdin.buffer, 
52    help='A file containing an HTTP request.  Defaults to stdin if omitted.')
53parser.add_argument(
54    '--requests', action='store_true', help='Generate a script that uses the'
55    ' python-requests module rather than http.client (this will likely become'
56    ' the default in the future).')
57
58args = parser.parse_args()
59input_req = args.requestfile.read()
60
61
62if b'\r\n\r\n' in input_req:
63    raw_headers,body = input_req.split(b'\r\n\r\n', 1)
64elif b'\n\n' in input_req:
65    raw_headers,body = input_req.split(b'\n\n', 1)
66else:
67    raw_headers = input_req
68    body = b''
69
70raw_headers = raw_headers.decode('utf-8')
71
72header_lines = raw_headers.split('\n')
73method,path,version = header_lines[0].split(' ', 2)
74
75host = 'TODO'
76port = None
77protocol = None
78
79headers = []
80for l in header_lines[1:]:
81    if len(l) < 1: 
82        break
83    # Handle header line continuations
84    if l[0] in ' \t':
85        if len(headers) == 0:
86            continue
87        name,values = headers[-1]
88        values.append(l.lstrip('\t'))
89        headers[-1] = (name,values)
90        continue
91
92    name,value = l.split(':',1)
93    value = value.lstrip(' ').rstrip('\r')
94
95    # Skip headers that have to do with transfer encodings and connection longevity
96    if name.lower() not in ['accept','accept-language',
97                            'accept-encoding','accept-charset',
98                            'connection', 'keep-alive', 'host', 
99                            'content-length', 'proxy-connection']:
100        headers.append((name,[value]))
101
102    if name.lower() == 'host':
103        if ':' in value:
104            host,port = value.split(':',1)
105            port = int(port, 10)
106            if port == 443:
107                protocol = 'https'
108        else:
109            host = value
110
111    # Attempt to guess the port and protocol from the referer header, since
112    # often it is the same site.  Defer to the host header though, if the
113    # info is there.
114    elif name.lower() == 'referer':
115        rurl = urllib.parse.urlparse(value)
116        if rurl.netloc == host:
117            if rurl.scheme == 'https' and protocol == None:
118                protocol = 'https'
119            if rurl.port != None and port == None:
120                port = rurl.port
121
122if protocol == None:
123    protocol == 'http'       
124if port == None:
125    if protocol == 'https':
126        port = 443
127    else:
128        port = 80
129
130
131# XXX: use pprint
132formatted_body = '\n            '.join([repr(body[i:i+40]) for i in range(0,len(body),40)])
133if formatted_body == '':
134    formatted_body = "b''"
135
136
137print('''#!/usr/bin/env python3
138# This script was generated by bletchley-http2py
139# See the "TODO" comments below for places to edit your request as needed for your situation.
140
141import sys
142from bletchley import blobtools,buffertools
143from bletchley import chosenct
144from bletchley.CBC import *
145
146# TODO: ensure the host, port, and protocol settings are correct.
147host = %s
148port = %s
149protocol = %s
150
151def decode(token):
152    # TODO: Perhaps you needs something like this?
153    #       (See 'bletchley-decode -e ?' for a list of encodings)
154    # return blobtools.decodeChain(['percent/mixed','base64/rfc3548'], token)
155    return token
156
157
158def encode(binary):
159    # TODO: Perhaps you needs something like this?
160    # return blobtools.encodeChain(['base64/rfc3548', 'percent/mixed'], binary)
161    return binary
162''' % (repr(host),repr(port),repr(protocol)))
163
164if args.requests:
165    print('''
166try:
167    import requests
168except:
169    sys.stderr.write('ERROR: Could not import requests module.  Ensure it is installed.\\n')
170    sys.stderr.write('       Under Debian, the package name is "python3-requests"\\n.')
171    sys.exit(1)
172''')
173
174    headers = dict(headers)
175    # XXX: We don't currently support exactly formatted header
176    #      continuations with python requests, but this should be
177    #      semantically equivalent.
178    for h in headers.keys():
179        headers[h] = ' '.join(headers[h])
180
181    print('''
182session = requests.Session()
183def sendRequest(session, data=None):
184    data = data.decode('utf-8')
185    # TODO: use "data" below, wherever your token normally appears
186    method = %s
187    path = %s
188    headers = %s
189    url = "%%s://%%s:%%d%%s" %% (protocol,host,port,path)
190    body = (%s)
191
192    # Set verify=True if you want to validate the server cert
193    return session.request(method, url, headers=headers, data=body, allow_redirects=False, verify=False)
194    ''' % (repr(method), repr(path), pprint.pformat(headers, indent=14), formatted_body))
195
196    print('''   
197
198def fetch(data, other=None):
199    global session
200    ret_val = None
201    response = sendRequest(session, encode(data))
202
203    # TODO: customize code here to retrieve what you need from the response(s)
204    # For information on the response object's interface, see:
205    #   http://docs.python-requests.org/en/latest/api/#requests.Response
206
207    # These are useful for debugging, but once your response processing is working,
208    # remove them so it isn't so verbose.
209    print(response.status_code)
210    print(response.headers)
211    print(repr(response.content))
212
213    return ret_val
214''')
215
216
217else:
218    print('''
219import http.client
220
221def sendRequest(connection, data=None):
222    data = data.decode('utf-8')
223    # TODO: use "data" below, wherever your token normally appears
224    method = %s
225    path = %s
226    body = (%s)
227   
228    connection.putrequest(method, path)
229    ''' % (repr(method), repr(path), formatted_body))
230
231    for name,values in headers:
232        if len(values) > 1:
233            continuations = ','.join([repr(v) for v in values[1:]])
234            print('''    connection.putheader(%s, %s, %s)''' % (repr(name),repr(values[0]),continuations))
235        else:
236            print('''    connection.putheader(%s, %s)''' % (repr(name),repr(values[0])))
237
238    print('''   
239    if len(body) > 0:
240        connection.putheader('Content-Length', len(body))
241    connection.endheaders()
242    connection.send(body)
243   
244    return connection.getresponse()
245
246
247def newConnection():
248    global protocol
249    if protocol == 'https':
250        return http.client.HTTPSConnection(host, port)
251    else:
252        return http.client.HTTPConnection(host, port)
253
254
255def fetch(data, other=None):
256    ret_val = False
257    connection = newConnection()
258    response = sendRequest(connection, encode(data))
259
260    # TODO: customize code here to retrieve what you need from the response(s)
261    # For information on the response object's interface, see:
262    #   http://docs.python.org/library/httplib.html#httpresponse-objects
263
264    # These are useful for debugging, but once your response processing is working,
265    # remove them so it isn't so verbose.
266    print(response.status)
267    print(response.getheaders())
268    print(repr(response.read()))
269
270    connection.close()
271    return ret_val
272''')
273
274
275print('''
276token = b'TODO: paste your encoded ciphertext here'
277ciphertext = decode(token)
278
279# TODO: Use this to verify you get the response you expect. 
280#       Once everything is working, use the commented code below to conduct specific attacks.
281fetch(ciphertext)
282
283
284# Padding Oracle Attacks
285# poa = POA(fetch, {block size}, ciphertext, threads=1, log_file=sys.stderr)
286# print(poa.probe_padding()) # sanity check
287# print(poa.decrypt())
288
289
290# Byte-by-byte probing of ciphertext
291#   Maybe start with this as a fast but gentle probe:
292# result = chosenct.probe_bytes(fetch, ciphertext, [1,128], max_threads=2)
293#   This is more in-depth (every bit of each byte) and more threads
294# result = chosenct.probe_bytes(fetch, ciphertext, [1,2,4,8,16,32,64,128], max_threads=5)
295#   Yet more intensive (every byte value against every byte):
296# result = chosenct.probe_bytes(fetch, ciphertext, list(range(1,256)), max_threads=8)
297#
298# print(result.toHTML())
299''')
Note: See TracBrowser for help on using the repository browser.