source: trunk/bin/bletchley-http2py @ 79

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

.

  • Property svn:executable set to *
File size: 8.6 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
39
40bopen = lambda f: open(f, 'rb')
41
42parser = argparse.ArgumentParser(
43    description='A script which accepts an HTTP request and prints out a'
44    ' generated Python script which sends a similar request.  This is useful'
45    ' when one wants to automate sending a large number of requests to a'
46    ' particular page or application.'
47    ' For more information, see: http://code.google.com/p/bletchley/wiki/Overview')
48parser.add_argument(
49    'requestfile', type=bopen, nargs='?', default=sys.stdin.buffer, 
50    help='A file containing an HTTP request.  Defaults to stdin if omitted.')
51parser.add_argument(
52    '--requests', action='store_true', help='Generate a script that uses the'
53    ' python-requests module rather than httplib/http.client (experimental).')
54
55args = parser.parse_args()
56input_req = args.requestfile.read()
57
58
59if b'\r\n\r\n' in input_req:
60    raw_headers,body = input_req.split(b'\r\n\r\n', 1)
61elif b'\n\n' in input_req:
62    raw_headers,body = input_req.split(b'\n\n', 1)
63else:
64    raw_headers = input_req
65    body = b''
66
67raw_headers = raw_headers.decode('utf-8')
68
69header_lines = raw_headers.split('\n')
70method,path,version = header_lines[0].split(' ', 2)
71
72host = 'TODO'
73port = 80
74use_ssl = False
75protocol = 'http'
76
77headers = []
78for l in header_lines[1:]:
79    if len(l) < 1: 
80        break
81    # Handle header line continuations
82    if l[0] in ' \t':
83        if len(headers) == 0:
84            continue
85        name,values = headers[-1]
86        values.append(l.lstrip('\t'))
87        headers[-1] = (name,values)
88        continue
89
90    name,value = l.split(':',1)
91    value = value.lstrip(' ').rstrip('\r')
92
93    # Skip headers that have to do with transfer encodings and connection longevity
94    if name.lower() not in ['accept','accept-language',
95                            'accept-encoding','accept-charset',
96                            'connection', 'keep-alive', 'host', 
97                            'content-length', 'proxy-connection']:
98        headers.append((name,[value]))
99
100    if name.lower() == 'host':
101        if ':' in value:
102            host,port = value.split(':',1)
103            port = int(port, 10)
104            if port == 443:
105                use_ssl = True
106                protocol = 'https'
107        else:
108            host = value
109
110
111formatted_body = '\n            '.join([repr(body[i:i+40]) for i in range(0,len(body),40)])
112if formatted_body == '':
113    formatted_body = "b''"
114
115
116print('''#!/usr/bin/env python3
117# This script was generated by bletchley-http2py
118# See the "TODO" comments below for places to edit your request as needed for your situation.
119
120import sys
121from bletchley import blobtools,buffertools
122from bletchley import chosenct
123from bletchley.CBC import *
124
125# TODO: ensure the host, port, and SSL settings are correct.
126host = %s
127port = %s
128protocol = %s
129
130def decode(token):
131    # TODO: Perhaps you needs something like this?
132    #       (See 'bletchley-decode -e ?' for a list of encodings)
133    # return blobtools.decodeAll(['percent/mixed','base64/rfc3548'], data)
134    return token
135
136
137def encode(binary):
138    # TODO: Perhaps you needs something like this?
139    # return blobtools.encodeAll(['base64/rfc3548', 'percent/mixed'], data)
140    return binary
141''' % (repr(host),repr(port),repr(protocol)))
142
143if args.requests:
144    print('''
145try:
146    import requests
147except:
148    sys.stderr.write('ERROR: Could not import requests module.  Ensure it is installed.\\n')
149    sys.stderr.write('       Under Debian, the package name is "python3-requests"\\n.')
150    sys.exit(1)
151''')
152
153    headers = dict(headers)
154    # XXX: We don't currently support exactly formatted header
155    #      continuations with python requests, but this should be
156    #      semantically equivalent.
157    for h in headers.keys():
158        headers[h] = ' '.join(headers[h])
159
160    print('''
161session = requests.Session()
162def sendRequest(session, data=None):
163    data = data.decode('utf-8')
164    # TODO: use "data" below, wherever your token normally appears
165    method = %s
166    path = %s
167    headers = %s
168    url = "%%s://%%s:%%d%%s" %% (protocol,host,port,path)
169    body = (%s)
170
171    return session.request(method, url, headers=headers, data=body, allow_redirects=False)
172    ''' % (repr(method), repr(path), repr(headers), formatted_body))
173
174    print('''   
175
176def fetch(data, other=None):
177    global session
178    ret_val = None
179    response = sendRequest(session, encode(data))
180
181    # TODO: customize code here to retrieve what you need from the response(s)
182    # For information on the response object's interface, see:
183    #   http://docs.python-requests.org/en/latest/api/#requests.Response
184
185    # These are useful for debugging, but once your response processing is working,
186    # remove them so it isn't so verbose.
187    print(response.headers)
188    print(repr(response.content))
189
190    return ret_val
191''')
192
193
194
195else:
196    print('''
197import http.client as httpc
198
199def sendRequest(connection, data=None):
200    data = data.decode('utf-8')
201    # TODO: use "data" below, wherever your token normally appears
202    method = %s
203    path = %s
204    body = (%s)
205   
206    connection.putrequest(method, path)
207    ''' % (repr(method), repr(path), formatted_body))
208
209    for name,values in headers:
210        if len(values) > 1:
211            continuations = ','.join([repr(v) for v in values[1:]])
212            print('''    connection.putheader(%s, %s, %s)''' % (repr(name),repr(values[0]),continuations))
213        else:
214            print('''    connection.putheader(%s, %s)''' % (repr(name),repr(values[0])))
215
216    print('''   
217    if len(body) > 0:
218        connection.putheader('Content-Length', len(body))
219    connection.endheaders()
220    connection.send(body)
221   
222    return connection.getresponse()
223
224
225def newConnection():
226    if use_ssl:
227        return httpc.HTTPSConnection(host, port)
228    else:
229        return httpc.HTTPConnection(host, port)
230
231
232def fetch(data, other=None):
233    ret_val = False
234    connection = newConnection()
235    response = sendRequest(connection, encode(data))
236
237    # TODO: customize code here to retrieve what you need from the response(s)
238    # For information on the response object's interface, see:
239    #   http://docs.python.org/library/httplib.html#httpresponse-objects
240
241    # These are useful for debugging, but once your response processing is working,
242    # remove them so it isn't so verbose.
243    print(response.getheaders())
244    print(repr(response.read()))
245
246    connection.close()
247    return ret_val
248''')
249
250
251print('''
252token = b'TODO: paste your encoded ciphertext here'
253ciphertext = decode(token)
254
255# TODO: Use this to verify you get the response you expect. 
256#       Once everything is working, use the commented code below to conduct specific attacks.
257fetch(ciphertext)
258
259
260# Padding Oracle Attacks
261# poa = POA(fetch, {block size}, ciphertext, threads=1, log_file=sys.stderr)
262# print(poa.probe_padding()) # sanity check
263# print(poa.decrypt())
264
265
266# Byte-by-byte probing of ciphertext
267#   Maybe start with this as a fast but gentle probe:
268# result = chosenct.probe_bytes(fetch, ciphertext, [1,128], max_threads=2)
269#   This is more in-depth (every bit of each byte) and more threads
270# result = chosenct.probe_bytes(fetch, ciphertext, [1,2,4,8,16,32,64,128], max_threads=5)
271#   Yet more intensive (every byte value against every byte):
272# result = chosenct.probe_bytes(fetch, ciphertext, list(range(1,256)), max_threads=8)
273#
274# print(result.toHTML())
275''')
Note: See TracBrowser for help on using the repository browser.