天天看點

【代碼審計】那些代碼審計的思路.md

前言

代碼審計工具的實作都是基于代碼審計經驗開發出來用于優化工作效率的工具,我們要學好代碼審計就必須要熟悉代碼審計的思路。而且代碼審計是基于PHP語言基礎上學習的,學習代碼審計最基本的要求就是能讀懂代碼。

常見的代碼審計思路有以下四種:

根據敏感關鍵字回溯參數傳遞過程; 查找可控變量,正向追蹤變量傳遞過程; 尋找敏感功能點,通讀功能點代碼; 直接通讀全文代碼。 敏感函數回溯參數過程 根據敏感函數來逆向追蹤參數的傳遞過程,是目前使用的最多的一種方式,因為大多數漏洞是由于函數的使用不當造成的。另外非函數使用不當的漏洞,如SQL注入,等以後學習再詳細介紹。這種方式的優缺點如下:

優點:隻需搜尋相應敏感關鍵字,即可快速挖掘想要的漏洞,可定向挖掘,高效、高品質; 缺點:由于沒有通讀代碼,對程式整體架構了解不夠深入,在挖掘漏洞時定位利用會花點時間,另外對邏輯漏洞挖掘覆寫不到。 espcms注入挖掘案例:

下載下傳網址:

http://down.chinaz.com/soft/27695.htm

下載下傳espcms源程式 (ps:一些程式源代碼可以在chinaz.com上面下載下傳) 打開seay源代碼審計系統,點選左上角建立項目,選擇下載下傳的espcms檔案夾,點選自動審計,開始審計,得到可能存在漏洞,漏洞檔案的路徑,和漏洞代碼清單。

我們挑選其中的一條代碼

輕按兩下直接定位到這行代碼,選中該變量後,可以看到變量的傳遞過程,在左側點選parentid函數,在下面詳細資訊的地方可以看到parentid函數,在下面詳細資訊的地方可以看到parentid函數,在下面詳細資訊的地方可以看到parentid變量獲得。

右鍵選中這行代碼,定位函數主體accept,點選右鍵,選擇定位函數

可以看到跳轉到了class_function.php檔案,代碼如下:

可以看到這是一個擷取GET、POST、COOKIE參數值得函數,我們傳入的變量是parentid和R,則代表在POST、GET中都可以擷取parentid參數,最後經過一個daddslashes()函數,實際上是包裝的addslashes()函數,對單引号等字元進行過濾。看前面的SQL語句是這樣的: sql=“select∗fromsql = “select * from sql=“select∗fromdb_table where parentid=$parentid”; 并不需要單引号來閉合,可以直接注入。

在citylist.php檔案看到oncitylist()函數在important類中,選中該類名右鍵點選,選擇全局搜尋 可以看到index.php檔案有執行個體化該類,代碼如下:

$archive = indexget(‘archive’, ‘R’);
$archive = empty($archive) ? ‘adminuser’ : $archive;
$action = indexget(‘action’, ‘R’);
$action = empty($action) ? ‘login’ : $action;
$soft_MOD = array(‘admin’, ‘public’, ‘product’, ‘forum’, ‘filemanage’, ‘basebook’, ‘member’, ‘order’, ‘other’, ‘news’, ‘inc’, ‘cache’, ‘bann’, ‘logs’, ‘template’);
if (in_array($point, $soft_MOD)) {
include admin_ROOT . adminfile . “/control/$archive.php”;
$control = new important();
$action = ‘on’ . $action;
if (method_exists($control, $action)) {
$control->$action();
} else {
exit(‘錯誤:系統方法錯誤!’);
} 
           

這裡可以看到一個include檔案的操作,可惜經過了addslashes()函數無法進行階段使其包含任意檔案,隻能包含本地的PHP檔案,往下是執行個體化類并且調用函數的操作,根據代碼可以構造出利用EXP:

http://127.0.0.1/espcms/upload/adminsoft/index.php?archive=citylist&action=citylist&parentid=-1 union select 1,2,user(),4,5 
           

通讀全文代碼 通讀全文代碼也有一定的技巧,否則很難讀懂Web程式的,也很難了解代碼的業務邏輯。首先我們要看程式的大體結構,如主目錄有哪些檔案,子產品目錄有哪些檔案,插件目錄有哪些檔案,另外還要注意檔案的大小,建立時間,就可以大概知道這個程式實作了那些功能,核心檔案有哪些。

如discuz的主目錄如下圖所示:

在看目錄結構的時候,特别注意以下幾個檔案:

1.函數集檔案 函數集檔案通常命名中包含functions或者common等關鍵字,這些檔案裡面是一些公共的函數,提供給其他檔案統一調用,是以大多數檔案都會在檔案頭部包含到其他檔案。尋找這些檔案的一個技巧就是打開index.php或者一些功能性檔案。2.配置檔案 配置檔案通常命名中包含config關鍵字,配置檔案包括Web程式運作必須的功能性配置選項以及資料庫等配置資訊。從這個檔案可以了解程式的小部分功能,另外看這個檔案的時候注意觀察配置檔案中參數是用單引号還是雙引号,如果是雙引号,則很可能會存在代碼執行漏洞。3.安全過濾檔案 安全過濾檔案對我們做代碼審計至關重要,通常命名中有filter、safe、check等關鍵字,這類檔案主要是對參數進行過濾,比較常見的是針對SQL注入和XSS過濾,還有檔案路徑、執行的系統指令的參數。4.index檔案 index是一個程式的入口檔案,是以我們隻要讀一遍index檔案就可以大緻了解整個程式的架構、運作的流程、包含到的檔案。騎士cms通讀審計案例 (1)檢視應用檔案結構

首先看看有哪些檔案和檔案夾,尋找名稱裡有沒有帶api、admin、manage、include一類關鍵字的檔案和檔案夾。可以看到有一個include檔案夾,一般比較核心的檔案都會放在這個檔案夾中。

(2)檢視關鍵檔案代碼 在這個檔案夾裡可以看多多個數十K的檔案,弱common.fun.php就是本程式的核心檔案,基礎函數基本就在這個檔案中實作。一打開檔案,立馬看多一大堆過濾函數,首先是一個SQL注入過濾函數:

function addslashes_deep($value) {
if (empty($value))
{
return $value;
}
else
{
if (!get_magic_quotes_gpc())
{
$value=is_array($value) ? array_map(‘addslashes_deep’, $value) : mystrip_tags(addslashes($value));
}
else
{
$value=is_array($value) ? array_map(‘addslashes_deep’, $value) : mystrip_tags($value);
}
return $value;
}
} 
           

該函數将傳入的變量使用addslashes()函數進行過濾,過濾掉了單引号、雙引号、NULL字元以及斜杠,要記住,在挖掘SQL注入漏洞時,隻要參數在拼接到SQL語句前,除非有寬位元組注入或者其他特殊情況,否則使用了這個函數就不能注入了。

再往下是一個XSS過濾的函數mystrip_tags(),代碼如下:

function mystrip_tags($string)
{
$string = new_html_special_chars($string);
$string = remove_xss($string);
return $string;
} 
           

這個函數調用了new_html_special_chars()和remove_xss()函數來過濾XSS,代碼如下:

function new_html_special_chars($string) {
$string = str_replace(array(‘&’, ‘"’, ‘<’, ‘>’), array(‘&’, ‘“‘, ‘<’, ‘>’), $string);
$string = strip_tags($string);
return $string;
}
function remove_xss($string) {
$string = preg_replace(‘/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S’, ‘’, $string);

$parm1 = Array('javascript', 'union','vbscript', 'expression', 'applet', 'xml', 'blink', 'link', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base');
 
$parm2 = 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','style','href','action','location','background','src','poster');
 
$parm3 = Array('alert','sleep','load_file','confirm','prompt','benchmark','select','update','insert','delete','alter','drop','truncate','script','eval','outfile','dumpfile');
 
$parm = array_merge($parm1, $parm2, $parm3); 
 
for ($i = 0; $i < sizeof($parm); $i++) { $pattern = '/'; for ($j = 0; $j < strlen($parm[$i]); $j++) { if ($j > 0) { $pattern .= '('; $pattern .= '(&#[x|X]0([9][a][b]);?)?'; $pattern .= '|(&#0([9][10][13]);?)?'; $pattern .= ')?'; }$pattern .= $parm[$i][$j]; }$pattern .= '/i';$string = preg_replace($pattern, '****', $string); 
}
return $string;
} 
           

在new_html_special_chars()函數中可以看到,這個函數對&符号、雙引号以及尖括号進行了html實體編碼,并且使用strip_tags()函數進行了二次過濾。而remove_xss()函數則是對一些标簽關鍵字、事件關鍵字以及敏感函數關鍵字進行了替換。

再往下有一個擷取IP位址的函數getip(),是可以僞造IP位址的:

function getip()
{
if (getenv(‘HTTP_CLIENT_IP’) and strcasecmp(getenv(‘HTTP_CLIENT_IP’),’unknown’)) {
$onlineip=getenv(‘HTTP_CLIENT_IP’);
}elseif (getenv(‘HTTP_X_FORWARDED_FOR’) and strcasecmp(getenv(‘HTTP_X_FORWARDED_FOR’),’unknown’)) {
$onlineip=getenv(‘HTTP_X_FORWARDED_FOR’);
}elseif (getenv(‘REMOTE_ADDR’) and strcasecmp(getenv(‘REMOTE_ADDR’),’unknown’)) {
$onlineip=getenv(‘REMOTE_ADDR’);
}elseif (isset($_SERVER[‘REMOTE_ADDR’]) and $_SERVER[‘REMOTE_ADDR’] and strcasecmp($_SERVER[‘REMOTE_ADDR’],’unknown’)) {
$onlineip=$_SERVER[‘REMOTE_ADDR’];
}
preg_match(“/\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}/“,$onlineip,$match);
return $onlineip = $match[0] ? $match[0] : ‘unknown’;
} 
           

很多應用都會由于在擷取IP時沒有驗證IP格式,而存在注入漏洞,不過這裡隻是可以僞造IP。

再往下可以看到一個值得關注的地方,SQL查詢統一操作函數inserttable()以及updatetable()函數,大多數SQL語句執行都會經過這裡,是以我們要關注這個地方是否還有過濾等問題。

function inserttable($tablename, $insertsqlarr, $returnid=0, $replace = false, $silent=0) {
global $db;
$insertkeysql = $insertvaluesql = $comma = ‘’;
foreach ($insertsqlarr as $insert_key => $insert_value) {
$insertkeysql .= $comma.’'.$insert_key.'‘;
$insertvaluesql .= $comma.’\’’.$insert_value.’\’’;
$comma = ‘, ‘;
}
$method = $replace?’REPLACE’:’INSERT’;
// echo $method.” INTO $tablename ($insertkeysql) VALUES ($insertvaluesql)”, $silent?’SILENT’:’’;die;
$state = $db->query($method.” INTO $tablename ($insertkeysql) VALUES ($insertvaluesql)”, $silent?’SILENT’:’’);
if($returnid && !$replace) {
return $db->insert_id();
}else {
return $state;
}
} 
           

再往下則是wheresql()函數,是SQL語句查詢的Where條件拼接的地方,我們可以看到參數都使用了單引号進行包裹,代碼如下:

function wheresql($wherearr=’’) {
$wheresql=””;
if (is_array($wherearr))
{
$where_set=’ WHERE ‘;
foreach ($wherearr as $key => $value)
{
$wheresql .=$where_set. $comma.$key.’=”‘.$value.’”‘;
$comma = ‘ AND ‘;
$where_set=’ ‘;
}
}
return $wheresql;
} 
           

還有一個通路令牌生成函數asyn_userkey(),拼接使用者名、密碼salt以及密碼進行一次md5,通路的時候隻要在GET參數key的值裡面加上生成的這個key即可驗證是否有權限,被用在注冊、找回密碼等驗證過程中,代碼如下:

function asyn_userkey($uid) {
global $db;
$sql = “select * from “.table(‘members’).” where uid = ‘“.intval($uid).”‘ LIMIT 1”;
$user=$db->getone($sql);
return md5($user[‘username’].$user[‘pwd_hash’].$user[‘password’]);
} 
           

(3)檢視配置檔案 上面我們介紹到配置檔案通常帶有“config”這樣的關鍵字,我們隻要搜尋帶有這個關鍵字的檔案名即可:

在搜尋結果中我們可以看到搜尋到多個檔案,結合經驗可以判斷config.php以及cache_config.php才是真正的配置檔案,打開config.php檢視代碼:

<?php
$dbhost = “localhost”;
$dbname =”74cms”;
$dbuser =”root”;
$dbpass =”123456”;
$pre =”qs_”;
$QS_cookiedomain = ‘’;
$QS_cookiepath = “/74cms/“;
$QS_pwdhash = “K0ciF:[email protected]”;
define(‘QISHI_CHARSET’,’gb2312’);
define(‘QISHI_DBCHARSET’,’GBK’);
?> 
           

很明顯看到,很有可能存在我們之前說過的雙引号解析代碼執行的問題,通常這個配置是在安裝系統的時候設定的,或者背景也有設定的地方。

看看資料庫連接配接時設定的編碼,找到騎士cms連接配接MySQL的代碼在include\mysql.class.php檔案的connect()函數,代碼如下:

function connect($dbhost, $dbuser, $dbpw, $dbname = ‘’, $dbcharset = ‘gbk’, $connect=1){
$func = empty($connect) ? ‘mysql_pconnect’ : ‘mysql_connect’;
if(!$this->linkid = @$func($dbhost, $dbuser, $dbpw, true)){
$this->dbshow(‘Can not connect to Mysql!’);
} else {
if($this->dbversion() > ‘4.1’){
mysql_query( “SET NAMES gbk”);
if($this->dbversion() > ‘5.0.1’){
mysql_query(“SET sql_mode = ‘’”,$this->linkid);
mysql_query(“SET character_set_connection=”.$dbcharset.”, character_set_results=”.$dbcharset.”, character_set_client=binary”, $this->linkid);
}
}
}
if($dbname){
if(mysql_select_db($dbname, $this->linkid)===false){
$this->dbshow(“Can’t select MySQL database($dbname)!”);
}
}
} 
           

這段代碼有個關鍵的地方,有安全隐患。 代碼首先判斷MySQL版本是否大于4.1,如果是則執行下面代碼:

mysql_query( “SET NAMES gbk”);

執行這個語句之後在判斷,如果版本大于5則執行下面代碼:

mysql_query(“SET character_set_connection=”.$dbcharset.”, character_set_results=”.$dbcharset.”, character_set_client=binary”, $this->linkid);

也就是說在MySQL版本小于5的情況下是不會執行這行代碼的, 但是執行了”set names gbk”,我們在之前介紹過”set names gbk”其實幹了三件事,等同于:

SET character_set_connection=’gbk’, character_set_results=’gbk’, character_set_client=’gbk’

是以在MySQL版本大于4.1小于5的情況下,基本所有跟資料庫有關的操作都存在寬位元組注入。

(4)跟讀首頁檔案 通過對系統檔案大概的了解,我們隊這套程式的整體架構已經有了一定的了解,但是還不夠,需要跟讀一下index.php檔案,看看程式運作的時候回調用哪些檔案和函數。

打開首頁檔案index.php可以看到如下代碼:

if(!file_exists(dirname(FILE).’/data/install.lock’))
header(“Location:install/index.php”);
define(‘IN_QISHI’, true);
$alias=”QS_index”;
require_once(dirname(FILE).’/include/common.inc.php’); 
           

首先判斷安裝鎖檔案是否存在,如果不存在則跳轉到install\index.php

接下來是包含\include\common.inc.php檔案,跟進檔案檢視

require_once(QISHI_ROOT_PATH.’data/config.php’);
header(“Content-Type:text/html;charset=”.QISHI_CHARSET);
require_once(QISHI_ROOT_PATH.’include/common.fun.php’);
require_once(QISHI_ROOT_PATH.’include/74cms_version.php’); 
           

\include\common.inc.php檔案在開頭包含了三個檔案,data\config.php為資料庫配置檔案,include\common.fun.php檔案為基礎函數庫檔案,include\74cms_version.php為應用版本檔案。

再看下面的代碼:

f (!empty($_GET))
{
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST))
{
$_POST = addslashes_deep($_POST);
}
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST); 
           

這段代碼調用了include\common.fun.php檔案裡面的addslashes_deep()函數對GET、POST、COOKIE參數進行了過濾。

再往下可以看到有一個包含檔案的操作:

require_once(QISHI_ROOT_PATH.’include/tpl.inc.php’);

包含了include\tpl.inc.php檔案,跟進這個檔案看看:

include_once(QISHI_ROOT_PATH.’include/template_lite/class.template.php’);
$smarty = new Template_Lite;
$smarty -> cache_dir = QISHI_ROOT_PATH.’temp/caches/‘.$_CFG[‘template_dir’];
$smarty -> compile_dir = QISHI_ROOT_PATH.’temp/templates_c/‘.$_CFG[‘template_dir’];
$smarty -> template_dir = QISHI_ROOT_PATH.’templates/‘.$_CFG[‘template_dir’];
$smarty -> reserved_template_varname = “smarty”;
$smarty -> left_delimiter = “”;
$smarty -> force_compile = false;
$smarty -> assign(‘_PLUG’, $_PLUG);
$smarty -> assign(‘QISHI’, $_CFG);
$smarty -> assign(‘page_select’,$page_select); 
           

首先看到包含了include\template_lite\class.template.php檔案,這是一個映射程式模闆的類,繼續往下看,可以看到這段代碼執行個體化了這個類對象指派給¥smarty變量。

繼續跟進則回到index.php檔案代碼:

if(!$smarty->is_cached($mypage[‘tpl’],$cached_id))
{
require_once(QISHI_ROOT_PATH.’include/mysql.class.php’);
$db = new mysql($dbhost,$dbuser,$dbpass,$dbname);
unset($dbhost,$dbuser,$dbpass,$dbname);
$smarty->display($mypage[‘tpl’],$cached_id);
}
else
{
$smarty->display($mypage[‘tpl’],$cached_id);
} 
           

判斷是否已經緩存,然後調用display()函數輸出頁面。接下來像審計index.php檔案一樣跟進其他功能入口檔案即可完成代碼通讀。

根據功能點定向審計 根據經驗我們簡單介紹幾個功能點會出現的漏洞:

1.檔案上傳功能 這裡說的檔案上傳在很多功能點都會出現,比如像文章編輯、資料編輯、頭像上傳、附件上傳,這個功能最常見的漏洞就是任意檔案上傳了,後端程式沒有嚴格地限制上傳的格式,導緻可以上傳或者存在繞過的情況,而除了檔案上傳功能外,還經常發生SQL注入漏洞。2.檔案管理功能 在檔案管理功能中,如果程式将檔案名或者檔案路徑直接在參數中傳遞,則很有可能會存在任意檔案的操作漏洞,比如任意檔案讀取等,利用的方法是在路徑中使用…/或者…\跳轉目錄。 除了任意檔案操作漏洞外,還可能會存在XSS漏洞,程式會在頁面中輸出檔案名,而通常會疏忽對檔案名進行過濾,導緻可以在資料庫中存入帶有尖括号等特殊符号的檔案名,最後在頁面顯示的時候就會被執行。3.登入認證功能 登入認證功能不是指一個過程,而是整個操作過程中的認證,目前的認證方式大多是基于Cookie和Session,不少程式會把目前登陸的使用者賬号等認證資訊放到Cookie中,或許是加密方式。進行操作的時候直接從Cookie中讀取目前使用者資訊,這裡就存在一個算法可信的問題,如果這段Cookie資訊沒有加salt一類的東西,就可以導緻任意使用者登入漏洞,隻要知道使用者的不扥資訊,即可生成認證令牌,甚至有的程式會直接把使用者名放到Cookie中,操作的時候直接讀取這個使用者名的資料,這也是常說的越權漏洞。4.找回密碼功能 找回密碼雖然看起來不像任意檔案上傳這種可以危害到伺服器安全的漏洞,但是如果可以重置管理者的密碼,也是可以間接控制業務權限甚至拿到服務權限的。找回密碼功能的漏洞有很多利用場景,最常見的是驗證碼爆破。目前特别是APP應用,請求後端驗證碼的時候大多是4位,并且沒有限制驗證碼的錯誤次數和有效時間,于是就出現了爆破的漏洞。

零基礎入門

對于從來沒有接觸過網絡安全的同學,我們幫你準備了詳細的學習成長路線圖。可以說是最科學最系統的學習路線,大家跟着這個大的方向學習準沒問題。

【代碼審計】那些代碼審計的思路.md

同時每個成長路線對應的闆塊都有配套的視訊提供:

【代碼審計】那些代碼審計的思路.md

因篇幅有限,僅展示部分資料,需要點選下方連結即可前往擷取

CSDN大禮包:《黑客&網絡安全入門&進階學習資源包》免費分享

視訊配套資料&國内外網安書籍、文檔&工具

當然除了有配套的視訊,同時也為大家整理了各種文檔和書籍資料&工具,并且已經幫大家分好類了。

【代碼審計】那些代碼審計的思路.md

因篇幅有限,僅展示部分資料,需要點選下方連結即可前往擷取

CSDN大禮包:《黑客&網絡安全入門&進階學習資源包》免費分享