某比赛 CBC Padding oracle wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php
error_reporting(0);
session_start();
define("METHOD", "aes-128-cbc");
define("KEY", "secret");

function privCode(){
$code = '';
$seeds = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
for($i = 0; $i < 15; $i++){
$code .= substr($seeds, rand(1, 61), 1);
}
return $code;
}

$privcode = privCode();


function get_iv(){
$iv = '';
$seeds = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
for($i = 0; $i < 16; $i++){
$iv .= substr($seeds, rand(1, 61), 1);
}
return $iv;
}

function loginpriv(){
global $privcode;
$token = get_iv();
$c = openssl_encrypt($privcode, METHOD, KEY, OPENSSL_RAW_DATA, $token);
$_SESSION['privcode'] = base64_encode($c);
if($privcode === 'admin'){
$_SESSION['admin'] = 1;
}else{
$_SESSION['admin'] = 0;
}
setcookie("token", base64_encode($token));
}
function iflogin(){
if (isset($_SESSION['privcode'])) {
$id_decode = base64_decode($_SESSION['privcode']);
$token_decode = base64_decode($_COOKIE["token"]);
if($u = openssl_decrypt($id_decode, METHOD, KEY, OPENSSL_RAW_DATA, $token_decode)){
echo $u;
if ($u === 'admin') {
echo 'flag{test}';
}
}else{
die("Error!");
}
}
return 0;
}
if(isset($_POST['username'])&&isset($_POST['password'])){
$username = $_POST['username'];
$password = $_POST['password'];
if($username === "admin" && md5($password) === "21232f297a57a5a743894a0e4a801fc3"){
loginpriv();
header('location: ./admin.php');
}else{
die('Login failed.');
}
}else{
if(iflogin()){
header('location: ./admin.php');
}
}
?>

一开始关注点错了,总以为rand可预测…后来明白,虽然rand可以预测,但对这题来说没啥用

CBC加解密;加密时,明文未知,秘钥未知,密文未知,IV已知

解密时,密文未知,秘钥未知,IV可控;目标使解密后的值等于admin

解密时,如果解密失败会返回Error!,那么可利用这点进行padding oracle attack,拿到Middle值,再拿Middle值与目标明文admin异或,即可得到正确的IV

1
2
3
4
5
6
7
if($de = openssl_decrypt($id_decode, METHOD, KEY, OPENSSL_RAW_DATA, $token_decode)){
if ($de === 'admin') {
echo 'flag{test}'
}
}else{
die("Error!");
}

但这道题的密文、Middle值只有15位,,,IV是16位的,然后就不是很理解了;这里随便填充了一位,倒序后可以正常解密,而且解密出的第一位字符会随机,实在不理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import requests
import base64
import urllib.parse

while 1:

Middle = []
padding = ''

url = 'http://127.0.0.1:8000/login.php'

data = {
'username':'admin',
'password':'admin'
}

s = requests.session()

res = s.post(url, data=data)

html_set_cookie = requests.utils.dict_from_cookiejar(s.cookies)

for x in range(1,17): # iv位
for y in range(0,256): # iv值
# if y == 255: # 排错
# break
IV = chr(0) * (16-x) + chr(y) + padding

headers = {
'Cookie':'token='+base64.b64encode(bytes(IV, encoding='utf-8')).decode()+';'+'PHPSESSID='+html_set_cookie['PHPSESSID']
}
#print(headers)
res = requests.post(url, headers=headers)
res.encoding = res.apparent_encoding

if 'Error' not in res.text: # iv值正确
padding = '' # 清空padding
Middle.append(y^x) # 添加Middle, Middle[x] == 构造IV[x] ^ 0xN == y ^ x
#print(Middle)
for z in Middle:
padding = chr((x+1)^z) + padding # 重新计算padding生成新IV
break


des_plain = 'admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'

new_iv = bytearray(16)

Middle.append(0)

Middle = Middle[::-1]
try:
for i in range(16):
new_iv[i] = Middle[i] ^ ord(des_plain[i])
except:
print('获取出错')
continue

new_iv = base64.b64encode(new_iv).decode()


headers = {
'Cookie':'token='+new_iv+';'+'PHPSESSID='+html_set_cookie['PHPSESSID']
}

res = requests.get(url, headers=headers)
print(res.text)