如何審計一個冷門的cms?
前言
剛開始代碼審計多多少少會有種無從下手的感覺,想要通過自己的代碼審計能力直接審計出漏洞未免有些困難。但是如果直接拿到一個cms開始審,剛開始可能富有激情,可是越審到后面就會越懷疑自己,然后放棄代碼審計。造成這樣的現象的原因便是:不知道自己到底能不能審到漏洞。
那如何審出一個很少人知道,并且肯定存在的洞呢?
本文介紹的辦法就是:上cnvd看看以前師傅提交的洞,雖然大多數的洞都是不公開的,但是我們還是可以根據cnvd給的一點點信息來嘗試復現。
因此,本文涉及到的漏洞都是比較早被發現,危害性較低,并且已經被廠商修復的漏洞,本文僅對代碼審計的過程做一個分析。
確定目標
剛開始審計,可以選擇一些比較冷門的cms(內容管理系統),挑選一個自己擅長的cms語言審計,之所以選擇冷門的cms是因為這些cms一般存在的安全問題會比熱門cms多。
本文選用的cms是:emlog(V5.3.1),這個cms是使用PHP語言開發的個人博客管理系統
信息收集
確定完目標之后,就需要上cnvd搜索該cms歷史存在的一些漏洞,本文演示的是emlog

可以看到在cnvd上存在這么一些漏洞,雖然在漏洞詳情頁面并沒有直接寫漏洞是怎么形成的,但是在這里或多或少會存在一些信息。比如在哪個地方會存在什么類型的洞。
接下里挑選一個簡單的漏洞進行演示

復現漏洞
由上圖可知,在后臺頁面,一般是admin目錄下,有一個ta打頭的文件,而我們查看一下emlog目錄,唯一一個ta打頭的文件就是tags.php

而在tag.php僅有50余行代碼,因此想在50行左右的代碼地方找到一個SQL注入還是比較簡單的

首先是大概了解一下這50余行代碼的作用,對于一個博客內容管理系統來說,tag.php應該是一個處理文章標簽管理的文件
因此我們這個cms可能會用到這個文件的頁面

在這個頁面中存在幾種操作,一個是全選,一個是刪除,其中點進標簽后還存在標簽修改的功能

這幾個功能對應在tag.php中就是這么幾個if語句

而執行哪一種操作是根據傳入action的值來決定的
因此,接來下審計在這些if語句中可能存在的SQL注入的點
首先是第一個if判斷
if ($action == '') {
$tags = $Tag_Model->getTag();
include View::getView('header');
require_once View::getView('tag');
include View::getView('footer');
View::output();
}
這一個if判斷只有一個action是可控的,其余沒有參數可控,因此這個函數不存在SQL注入的點
然后再看一下第二個if判斷
if ($action== "mod_tag") {
$tagId = isset($_GET['tid']) ? intval($_GET['tid']) : '';
$tag = $Tag_Model->getOneTag($tagId);
extract($tag);
include View::getView('header');
require_once View::getView('tagedit');
include View::getView('footer');View::output();
}
這一個if判斷相比上一個多一個tid為可控參數,不過cms對傳入的tid進行了intval的轉換
不過我們還是跟進getOneTag方法看一下
function getOneTag($tagId) {
$tag = array();
$row = $this->db->once_fetch_array("SELECT tagname,tid FROM ".DB_PREFIX."tag WHERE tid=$tagId");
$tag['tagname'] = htmlspecialchars(trim($row['tagname']));
$tag['tagid'] = intval($row['tid']);
return $tag;
}
getOneTag方法具有一個參數tagId,這個tagId就是前面我們自定義傳入的tid,不過這個地方沒法傳入字符串進行閉合注入,因為在傳入這個方法以前就已經intval轉換了,所以這個點不存在sql注入
接下來看第三個if判斷
//標簽修改
if ($action=='update_tag') {
$tagName = isset($_POST['tagname']) ? addslashes($_POST['tagname']) : '';
$tagId = isset($_POST['tid']) ? intval($_POST['tid']) : '';
if (empty($tagName)) {
emDirect("tag.php?action=mod_tag&tid=$tagId&error_a=1");
}
$Tag_Model->updateTagName($tagId, $tagName);
$CACHE->updateCache(array('tags', 'logtags'));
emDirect("./tag.php?active_edit=1");
}
這里相比上面多了一個tagname,這個tagname可以傳入字符串,也就是有可能會存在SQL注入,不過在傳入的時候,cms會對這個tagname進行一個addslashes函數轉換,也就是傳入的單引號以及一些特殊符號會被轉譯。
我們這邊抓包嘗試一下
首先查看一下現在的標簽,此時有兩個標簽tag112以及tags2123

上面提到的tagname其實就是標簽的名字,我們抓包將tag112修改為tag112',嘗試能否閉合

這時候我們返回主頁面

發現此時標簽變為了tag112',所以單引號是被轉義了,沒法直接進行SQL注入。
當然,如果數據庫的編碼為GBK,那么可以嘗試寬字節注入,不過默認使用的數據庫為utf8,因此無法使用寬字節注入。
接下來看第4個if判斷
//批量刪除標簽
if ($action== 'dell_all_tag') {
$tags = isset($_POST['tag']) ? $_POST['tag'] : '';
LoginAuth::checkToken();
if (!$tags) {
emDirect("./tag.php?error_a=1");
}
foreach ($tags as $key=>$value) {
$Tag_Model->deleteTag($key);
}
$CACHE->updateCache(array('tags', 'logtags'));
emDirect("./tag.php?active_del=1");
}
這個地方POST傳入了一個tag,是我們的可控參數,并且這里沒有對tag進行轉義,所以我們再往下看是否存在SQL注入
其中我們傳入的tag最終會成為tags數組中的key,仔細看一下foreach循環中,傳入的deleteTag方法的參數是數組中的key,而在PHP中,數組的key并不一定需要是數字,也可以是一個字符串,只要key和value對應即可。
我們這個時候先不著急抓包,先看看deleteTag方法是如何實現的。
跟進deleteTag方法
function deleteTag($tagId) {
$this->db->query("DELETE FROM ".DB_PREFIX."tag where tid=$tagId");
}
可以發現,SQL語句執行的時候也是沒有進行任何過濾的。
這里我們開始抓包

可以看到post傳入了tag[1],這里我們對方括號中的1開始注入,這里也不需要引號閉合,直接注即可。
我使用的是報錯注入
tag%5B1%20and%20updatexml(0,concat(0x7e,version()),1)%20%23%5D=1&token=c16eaff79a17d690f5c0caae66276085

看下方的結果,已經把數據庫的版本爆出來了。

總結
本文所復現的是在后臺的SQL注入,利用難度較大,危害性較低。并且審計這個cms并不是直接拿一個cms從頭開始審,而是根據在cnvd中存在的一些模糊信息進行審計,來提高自己復現這個漏洞的成功率,不至于在一開始就碰壁,而能在相對短的時間里獲得較大的成就感,提高學習效率。
大家也可以多在練習中,積累,復制下方鏈接實操起來吧