source: trunk/bin/bletchley-http2py @ 134

Last change on this file since 134 was 132, checked in by tim, 7 years ago

better place to put that

  • Property svn:executable set to *
File size: 10.7 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)
[116]9request using the Requests library, or optionally, the built-in
10http.client 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
[116]17raw HTTP request to HTTP library calls easy for pentesting automation.
[4]18
19
[39]20Copyright (C) 2011-2013 Virtual Security Research, LLC
[131]21Copyright (C) 2014-2017 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
[80]39import pprint
40import urllib.parse
[4]41
[47]42bopen = lambda f: open(f, 'rb')
43
[28]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(
[47]51    'requestfile', type=bopen, nargs='?', default=sys.stdin.buffer, 
[28]52    help='A file containing an HTTP request.  Defaults to stdin if omitted.')
[116]53group = parser.add_mutually_exclusive_group()
54group.add_argument('--requests', action='store_true',
55                   help='Generate a script that uses the Requests module'
56                        ' rather than http.client (default).')
57group.add_argument('--native', action='store_false', dest='requests',
58                   help='Generate a script that uses Pythons built-in http.client'
59                        ' rather than the Requests module.')
60parser.set_defaults(requests=True)
[40]61
[4]62args = parser.parse_args()
[40]63input_req = args.requestfile.read()
[4]64
65
[47]66if b'\r\n\r\n' in input_req:
67    raw_headers,body = input_req.split(b'\r\n\r\n', 1)
68elif b'\n\n' in input_req:
69    raw_headers,body = input_req.split(b'\n\n', 1)
[4]70else:
71    raw_headers = input_req
[47]72    body = b''
[4]73
[47]74raw_headers = raw_headers.decode('utf-8')
75
[4]76header_lines = raw_headers.split('\n')
77method,path,version = header_lines[0].split(' ', 2)
78
79host = 'TODO'
[80]80port = None
81protocol = None
[4]82
83headers = []
84for l in header_lines[1:]:
85    if len(l) < 1: 
86        break
87    # Handle header line continuations
[40]88    if l[0] in ' \t':
[4]89        if len(headers) == 0:
90            continue
91        name,values = headers[-1]
92        values.append(l.lstrip('\t'))
93        headers[-1] = (name,values)
94        continue
95
96    name,value = l.split(':',1)
97    value = value.lstrip(' ').rstrip('\r')
98
[116]99    # Skip headers that have to do with transfer encodings, connection longevity, and caching
100    # XXX: maybe add these back as commented-out headers to the output?
[4]101    if name.lower() not in ['accept','accept-language',
102                            'accept-encoding','accept-charset',
103                            'connection', 'keep-alive', 'host', 
[116]104                            'content-length', 'proxy-connection',
105                            'if-none-match']:
[4]106        headers.append((name,[value]))
107
108    if name.lower() == 'host':
109        if ':' in value:
110            host,port = value.split(':',1)
[51]111            port = int(port, 10)
[4]112            if port == 443:
[40]113                protocol = 'https'
[4]114        else:
115            host = value
116
[80]117    # Attempt to guess the port and protocol from the referer header, since
118    # often it is the same site.  Defer to the host header though, if the
119    # info is there.
120    elif name.lower() == 'referer':
121        rurl = urllib.parse.urlparse(value)
122        if rurl.netloc == host:
123            if rurl.scheme == 'https' and protocol == None:
124                protocol = 'https'
125            if rurl.port != None and port == None:
126                port = rurl.port
[39]127
[80]128if protocol == None:
[116]129    protocol = 'http'
[80]130if port == None:
131    if protocol == 'https':
132        port = 443
133    else:
134        port = 80
135
136
137# XXX: use pprint
[47]138formatted_body = '\n            '.join([repr(body[i:i+40]) for i in range(0,len(body),40)])
[51]139if formatted_body == '':
[40]140    formatted_body = "b''"
[39]141
[40]142
[79]143print('''#!/usr/bin/env python3
144# This script was generated by bletchley-http2py
145# See the "TODO" comments below for places to edit your request as needed for your situation.
[40]146
[39]147import sys
[79]148from bletchley import blobtools,buffertools
149from bletchley import chosenct
150from bletchley.CBC import *
151
[80]152# TODO: ensure the host, port, and protocol settings are correct.
[79]153host = %s
154port = %s
155protocol = %s
156
157def decode(token):
158    # TODO: Perhaps you needs something like this?
159    #       (See 'bletchley-decode -e ?' for a list of encodings)
[82]160    # return blobtools.decodeChain(['percent/mixed','base64/rfc3548'], token)
[79]161    return token
162
163
164def encode(binary):
165    # TODO: Perhaps you needs something like this?
[82]166    # return blobtools.encodeChain(['base64/rfc3548', 'percent/mixed'], binary)
[79]167    return binary
168''' % (repr(host),repr(port),repr(protocol)))
169
170if args.requests:
171    print('''
[39]172try:
[40]173    import requests
[132]174    import urllib3
175    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
[39]176except:
[40]177    sys.stderr.write('ERROR: Could not import requests module.  Ensure it is installed.\\n')
178    sys.stderr.write('       Under Debian, the package name is "python3-requests"\\n.')
[116]179    sys.stderr.write('       Alternatively, re-generate this script using the --native option.\\n.')
[40]180    sys.exit(1)
[79]181''')
[40]182
183    headers = dict(headers)
184    # XXX: We don't currently support exactly formatted header
185    #      continuations with python requests, but this should be
186    #      semantically equivalent.
187    for h in headers.keys():
188        headers[h] = ' '.join(headers[h])
189
190    print('''
191session = requests.Session()
192def sendRequest(session, data=None):
[79]193    data = data.decode('utf-8')
[116]194    # TODO: Replace the token you wish to target in this request with the "data" variable.
195    #       Then specify the starting value for that token at the end of this script.
[40]196    method = %s
197    path = %s
198    headers = %s
199    url = "%%s://%%s:%%d%%s" %% (protocol,host,port,path)
200    body = (%s)
201
[80]202    # Set verify=True if you want to validate the server cert
203    return session.request(method, url, headers=headers, data=body, allow_redirects=False, verify=False)
[116]204    ''' % (repr(method), repr(path),
205           pprint.pformat(headers, width=80-14).replace('\n','\n'+' '*14),
206           formatted_body))
[40]207
208    print('''   
209
[116]210def processResponse(data, other=None):
[40]211    global session
212    ret_val = None
[79]213    response = sendRequest(session, encode(data))
[40]214
215    # TODO: customize code here to retrieve what you need from the response(s)
216    # For information on the response object's interface, see:
217    #   http://docs.python-requests.org/en/latest/api/#requests.Response
[79]218
219    # These are useful for debugging, but once your response processing is working,
220    # remove them so it isn't so verbose.
[131]221    print(response.status_code, file=sys.stderr)
222    print(response.headers, file=sys.stderr)
223    print(repr(response.content), file=sys.stderr)
[40]224
[131]225    # You may need to return either true/false or a string/bytes object here
226    # (which is derived from the response) depending on your test
[40]227    return ret_val
[39]228''')
229
230
[40]231else:
[79]232    print('''
[80]233import http.client
[40]234
[39]235def sendRequest(connection, data=None):
[79]236    data = data.decode('utf-8')
237    # TODO: use "data" below, wherever your token normally appears
[4]238    method = %s
239    path = %s
[14]240    body = (%s)
241   
[4]242    connection.putrequest(method, path)
[40]243    ''' % (repr(method), repr(path), formatted_body))
[4]244
[40]245    for name,values in headers:
246        if len(values) > 1:
247            continuations = ','.join([repr(v) for v in values[1:]])
248            print('''    connection.putheader(%s, %s, %s)''' % (repr(name),repr(values[0]),continuations))
249        else:
250            print('''    connection.putheader(%s, %s)''' % (repr(name),repr(values[0])))
[4]251
[40]252    print('''   
[4]253    if len(body) > 0:
254        connection.putheader('Content-Length', len(body))
255    connection.endheaders()
256    connection.send(body)
257   
258    return connection.getresponse()
259
260
[39]261def newConnection():
[80]262    global protocol
263    if protocol == 'https':
264        return http.client.HTTPSConnection(host, port)
[39]265    else:
[80]266        return http.client.HTTPConnection(host, port)
[4]267
268
[116]269def processResponse(data, other=None):
[62]270    ret_val = False
[39]271    connection = newConnection()
[79]272    response = sendRequest(connection, encode(data))
[39]273
274    # TODO: customize code here to retrieve what you need from the response(s)
275    # For information on the response object's interface, see:
276    #   http://docs.python.org/library/httplib.html#httpresponse-objects
[79]277
278    # These are useful for debugging, but once your response processing is working,
279    # remove them so it isn't so verbose.
[131]280    print(response.status, file=sys.stderr)
281    print(response.getheaders(), file=sys.stderr)
282    print(repr(response.read()), file=sys.stderr)
[39]283
284    connection.close()
[131]285    # You may need to return either true/false or a string/bytes object here
286    # (which is derived from the response) depending on your test
[39]287    return ret_val
[4]288''')
[62]289
[79]290
[62]291print('''
[116]292token = b'TODO: paste your encoded ciphertext here (typically moved from the sendRequest function)'
[79]293ciphertext = decode(token)
[62]294
[79]295# TODO: Use this to verify you get the response you expect. 
296#       Once everything is working, use the commented code below to conduct specific attacks.
[131]297print(processResponse(ciphertext), file=sys.stderr)
[79]298
299
[62]300# Padding Oracle Attacks
[118]301# poa = POA(processResponse, {block size}, ciphertext, iv=None, threads=1, log_file=sys.stderr)
[62]302# print(poa.probe_padding()) # sanity check
303# print(poa.decrypt())
[68]304
[79]305
[68]306# Byte-by-byte probing of ciphertext
[79]307#   Maybe start with this as a fast but gentle probe:
[116]308# result = chosenct.probe_bytes(processResponse, ciphertext, [1,128], max_threads=2)
[79]309#   This is more in-depth (every bit of each byte) and more threads
[116]310# result = chosenct.probe_bytes(processResponse, ciphertext, [1,2,4,8,16,32,64,128], max_threads=5)
[79]311#   Yet more intensive (every byte value against every byte):
[116]312# result = chosenct.probe_bytes(processResponse, ciphertext, list(range(1,256)), max_threads=8)
[79]313#
[68]314# print(result.toHTML())
[62]315''')
Note: See TracBrowser for help on using the repository browser.