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
RevLine 
[40]1#!/usr/bin/env python3
[73]2#-*- mode: Python;-*-
3#
[40]4# Requires Python 3+
[4]5
6'''
[40]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.
[4]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
[79]16lot more convenient.  This script attempts to make the conversion from a
17raw HTTP request to HTTP library calls easy.
[4]18
19
[39]20Copyright (C) 2011-2013 Virtual Security Research, LLC
[79]21Copyright (C) 2014-2015 Blindspot Security LLC
[4]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
[47]40bopen = lambda f: open(f, 'rb')
41
[28]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(
[47]49    'requestfile', type=bopen, nargs='?', default=sys.stdin.buffer, 
[28]50    help='A file containing an HTTP request.  Defaults to stdin if omitted.')
51parser.add_argument(
[40]52    '--requests', action='store_true', help='Generate a script that uses the'
53    ' python-requests module rather than httplib/http.client (experimental).')
54
[4]55args = parser.parse_args()
[40]56input_req = args.requestfile.read()
[4]57
58
[47]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)
[4]63else:
64    raw_headers = input_req
[47]65    body = b''
[4]66
[47]67raw_headers = raw_headers.decode('utf-8')
68
[4]69header_lines = raw_headers.split('\n')
70method,path,version = header_lines[0].split(' ', 2)
71
72host = 'TODO'
[14]73port = 80
[4]74use_ssl = False
[40]75protocol = 'http'
[4]76
77headers = []
78for l in header_lines[1:]:
79    if len(l) < 1: 
80        break
81    # Handle header line continuations
[40]82    if l[0] in ' \t':
[4]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', 
[39]97                            'content-length', 'proxy-connection']:
[4]98        headers.append((name,[value]))
99
100    if name.lower() == 'host':
101        if ':' in value:
102            host,port = value.split(':',1)
[51]103            port = int(port, 10)
[4]104            if port == 443:
105                use_ssl = True
[40]106                protocol = 'https'
[4]107        else:
108            host = value
109
[39]110
[47]111formatted_body = '\n            '.join([repr(body[i:i+40]) for i in range(0,len(body),40)])
[51]112if formatted_body == '':
[40]113    formatted_body = "b''"
[39]114
[40]115
[79]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.
[40]119
[39]120import sys
[79]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('''
[39]145try:
[40]146    import requests
[39]147except:
[40]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)
[79]151''')
[40]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):
[79]163    data = data.decode('utf-8')
164    # TODO: use "data" below, wherever your token normally appears
[40]165    method = %s
166    path = %s
167    headers = %s
168    url = "%%s://%%s:%%d%%s" %% (protocol,host,port,path)
169    body = (%s)
170
[70]171    return session.request(method, url, headers=headers, data=body, allow_redirects=False)
[40]172    ''' % (repr(method), repr(path), repr(headers), formatted_body))
173
174    print('''   
175
[79]176def fetch(data, other=None):
[40]177    global session
178    ret_val = None
[79]179    response = sendRequest(session, encode(data))
[40]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
[79]184
185    # These are useful for debugging, but once your response processing is working,
186    # remove them so it isn't so verbose.
[40]187    print(response.headers)
188    print(repr(response.content))
189
190    return ret_val
[39]191''')
192
193
[40]194
195else:
[79]196    print('''
[40]197import http.client as httpc
198
[39]199def sendRequest(connection, data=None):
[79]200    data = data.decode('utf-8')
201    # TODO: use "data" below, wherever your token normally appears
[4]202    method = %s
203    path = %s
[14]204    body = (%s)
205   
[4]206    connection.putrequest(method, path)
[40]207    ''' % (repr(method), repr(path), formatted_body))
[4]208
[40]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])))
[4]215
[40]216    print('''   
[4]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
[39]225def newConnection():
226    if use_ssl:
227        return httpc.HTTPSConnection(host, port)
228    else:
229        return httpc.HTTPConnection(host, port)
[4]230
231
[71]232def fetch(data, other=None):
[62]233    ret_val = False
[39]234    connection = newConnection()
[79]235    response = sendRequest(connection, encode(data))
[39]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
[79]240
241    # These are useful for debugging, but once your response processing is working,
242    # remove them so it isn't so verbose.
[39]243    print(response.getheaders())
244    print(repr(response.read()))
245
246    connection.close()
247    return ret_val
[4]248''')
[62]249
[79]250
[62]251print('''
[79]252token = b'TODO: paste your encoded ciphertext here'
253ciphertext = decode(token)
[62]254
[79]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
[62]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())
[68]264
[79]265
[68]266# Byte-by-byte probing of ciphertext
[79]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
[71]270# result = chosenct.probe_bytes(fetch, ciphertext, [1,2,4,8,16,32,64,128], max_threads=5)
[79]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#
[68]274# print(result.toHTML())
[62]275''')
Note: See TracBrowser for help on using the repository browser.