溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎樣學(xué)習(xí)PHP代碼審計(jì)

發(fā)布時(shí)間:2021-10-11 10:42:25 來源:億速云 閱讀:132 作者:柒染 欄目:安全技術(shù)

怎樣學(xué)習(xí)PHP代碼審計(jì),很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

以Emlog 6.0  beta版本為引,一篇關(guān)于PHP語言CMS的代碼審計(jì)文章,詳細(xì)記錄代碼審計(jì)的完整過程,學(xué)習(xí)代碼審計(jì),不妨從這邊文章入手,認(rèn)真閱讀完,相信一定可以有所收獲!

大家需要注意的一點(diǎn)是,代碼審計(jì)是為了學(xué)習(xí)并在SDL中避免發(fā)生類似的錯(cuò)誤,同時(shí)也是幫助開源系統(tǒng)修復(fù)相關(guān)問題,并不是去為了獲得什么0day~

0x00 Emlog 6.0 beta

怎樣學(xué)習(xí)PHP代碼審計(jì)

EMLOG 6.0

官網(wǎng)地址:https://www.emlog.net/

Emlog 6.0 beta下載地址:

https://www.emlog.net/download

由于官方限制論壇會(huì)員(注冊(cè)付費(fèi))才可下載,這里提供一個(gè)原版下載地址:https://www.lanzous.com/i1l5gad

文件校驗(yàn):

文件: C:\Users\stdy\Desktop\emlog_6.0.0.zip 大小: 607725 字節(jié) 修改時(shí)間: 2018年8月6日, 20:53:50 MD5: 7844FE6FEAE7AF68052DC878B8811FAC SHA1: E06A050D2A0AA879DB9F5CFCAA4703B6AC7B8352 CRC32: 4963E489

博主的博客就是基于此套博客系統(tǒng),其實(shí)很多圈內(nèi)大佬都在使用,對(duì)于本款CMS的審計(jì)文章卻并沒有,筆者就來以此CMS作為PHP代碼審計(jì)的封筆之作。

0x01 初步測試

首先,我們得先安裝!安裝成功后的首頁界面:

怎樣學(xué)習(xí)PHP代碼審計(jì)

安裝成功

默認(rèn)后臺(tái)登陸地址:./admin/

登陸成功后:

怎樣學(xué)習(xí)PHP代碼審計(jì)

后臺(tái)界面

閑話一句,感覺6.0比5.3.1版本好看太多了~

安裝過后,我們應(yīng)該盡可能全面搜集關(guān)于此CMS的信息,這對(duì)于我們審計(jì)代碼有很大的幫助。

所以,分析得到此CMS的大致結(jié)構(gòu),Emlog是一個(gè) MVC 的設(shè)計(jì)模式,大致的結(jié)構(gòu)如圖:

怎樣學(xué)習(xí)PHP代碼審計(jì)

emlog結(jié)構(gòu)

因此我們主要會(huì)分析 admin 和 include 文件夾下的文件。

數(shù)據(jù)庫表:

怎樣學(xué)習(xí)PHP代碼審計(jì)

DATABASE

在根目錄的init.php 文件中

怎樣學(xué)習(xí)PHP代碼審計(jì)

報(bào)錯(cuò)等級(jí)

報(bào)錯(cuò)等級(jí)指定為7:

<?php //禁用錯(cuò)誤報(bào)告 error_reporting(0);  //報(bào)告運(yùn)行時(shí)錯(cuò)誤 error_reporting(E_ERROR | E_WARNING | E_PARSE);  //報(bào)告所有錯(cuò)誤 error_reporting(E_ALL);  error_reporting(7); /* 設(shè)置php錯(cuò)誤檢測級(jí)別 E_ERROR - 致命性運(yùn)行時(shí)錯(cuò) (1) E_WARNING - 運(yùn)行時(shí)警告(非致命性錯(cuò))(2) E_PARSE - 編譯時(shí)解析錯(cuò)誤 (4) 1+2+4 = 7 */ ?>

0x02 使用漏洞掃描器

可能有朋友就會(huì)說你為什么要使用“漏掃”吶?不是代碼審計(jì)嗎?

這里要糾正一下這個(gè)觀點(diǎn),漏掃其實(shí)就是一個(gè)自動(dòng)化黑盒測試,在本地環(huán)境下,我們不會(huì)影響任何的業(yè)務(wù)。

通過漏掃出的漏洞能夠方便我們快速定位漏洞位置,這樣是一種高效的方式,這也是在團(tuán)隊(duì)里的成員通過漏掃Get了百度的幾個(gè)高危漏洞給筆者的啟示。

這里使用了一款重型掃描器 AWVS ,得到的報(bào)告如下:

怎樣學(xué)習(xí)PHP代碼審計(jì)

結(jié)果

不過在本地掃描時(shí),使用的是 XAMPP windows10  PHP5.6的環(huán)境,所以導(dǎo)致漏洞報(bào)告中很多誤報(bào),漏掃主要掃描出了幾個(gè)XSS漏洞和CSRF漏洞

所以我們首先驗(yàn)證這兩類的漏洞

0x03 文章編輯器儲(chǔ)存性XSS

在后臺(tái)的編輯器處,編輯文章./admin/admin_log.php

怎樣學(xué)習(xí)PHP代碼審計(jì)

編輯器XSS

成功發(fā)布后,來到首頁

怎樣學(xué)習(xí)PHP代碼審計(jì)

emlogXSS

進(jìn)入文章頁后

怎樣學(xué)習(xí)PHP代碼審計(jì)

文章頁XSS

都彈窗了,這里大家可能要說沒法兒利用,但是emlog設(shè)計(jì)了 會(huì)員/作者  功能,在emlog中的某些模版中可以前臺(tái)注冊(cè)會(huì)員,會(huì)員登錄后可以編輯發(fā)表文章,評(píng)論等等功能。Emlog官方還提供了文章投稿插件,都是調(diào)用了官方默認(rèn)的Kindeditor編輯器,這個(gè)編輯器自帶HTML編輯模式,就算不帶這個(gè)模式,攻擊者也可以抓包修改達(dá)到攻擊目的。

為什么前臺(tái)沒過濾吶?為了文章有支持HTML代碼輸出,所以對(duì)于kindeditor的保存輸出內(nèi)容并沒有轉(zhuǎn)義。

怎樣學(xué)習(xí)PHP代碼審計(jì)

emlog會(huì)員/投稿

修復(fù)建議:參考其他CMS做好文章內(nèi)容關(guān)鍵詞的檢測,并做好過濾或者轉(zhuǎn)義

0x04 Uploadify SWF XSS

Emlog使用了 uploadify.swf 的方式上傳文件,文件路徑  /include/lib/js/uploadify/uploadify.swf

構(gòu)造Payload:http://www.test.com//include/lib/js/uploadify/uploadify.swf?uploadifyID=00%22%29%29;}catch%28e%29{alert%281%29;}//%28%22&movieName=%22])}catch(e){if(!window.x){window.x=1;alert(document.cookie)}}//&.swf

效果,可無視瀏覽器filter:

怎樣學(xué)習(xí)PHP代碼審計(jì)

SWF XSS

0x05 反射型XSS

此處的XSS主要發(fā)生在cookie上,因?yàn)槟承╉撁嫒? admin/admin_log,admin/sort.php,admin/link.php頁面需要在表單中添加了hidden屬性的token值,而這個(gè)token值直接從用戶的cookie中取得,導(dǎo)致了一個(gè)反射型XSS

攔截抓包修改cookie中的token值如下:

怎樣學(xué)習(xí)PHP代碼審計(jì)

payload

效果:

怎樣學(xué)習(xí)PHP代碼審計(jì)

COOKIE XSS

其次驗(yàn)證了 CSRF 漏洞,這個(gè)是前臺(tái)的搜索框的CSRF根本沒什么價(jià)值

然后是管理員添加友情鏈接的XSS,經(jīng)過驗(yàn)證并不存在,后臺(tái)函數(shù)會(huì)限制字?jǐn)?shù)

然后就是我們開始進(jìn)行原始的代碼審計(jì)工作了,主要借用了Seay代碼審計(jì)工具和Rips,這種審計(jì)工具主要依靠正則匹配可能導(dǎo)致危險(xiǎn)的php函數(shù)來作為可能存在漏洞的判斷,半自動(dòng)化的方式,在一定程度上緩解了代碼審計(jì)的壓力。

0x06 基本函數(shù)

首先看了一下文件操作相關(guān)的函數(shù),發(fā)現(xiàn)經(jīng)常用到 View::getView 這一方法,

在include/lib/view.php 文件中,源碼如下:

<?php /**  * 視圖控制  * @copyright (c) Emlog All Rights Reserved  */  class View {  public static function getView($template, $ext = '.php') {   if (!is_dir(TEMPLATE_PATH)) {    emMsg('當(dāng)前使用的模板已被刪除或損壞,請(qǐng)登錄后臺(tái)更換其他模板。', BLOG_URL . 'admin/template.php');   }   return TEMPLATE_PATH . $template . $ext;  }   public static function output() {   $content = ob_get_clean();         ob_start();   echo $content;   ob_end_flush();   exit;  } }

同時(shí)作為權(quán)限控制的 LoginAuth::checkToken(),在 \include\lib\loginauth.php下約209行開始

/** * 生成token,防御CSRF攻擊 */ public static function genToken() {  $token_cookie_name = 'EM_TOKENCOOKIE_' . md5(substr(AUTH_KEY, 16, 32) . UID);  if (isset($_COOKIE[$token_cookie_name])) {   return $_COOKIE[$token_cookie_name];  } else {   $token = md5(getRandStr(16));   setcookie($token_cookie_name, $token, 0, '/');   return $token;  } }  /** * 檢查token,防御CSRF攻擊 */ public static function checkToken(){  $token = isset($_REQUEST['token']) ? addslashes($_REQUEST['token']) : '';  if ($token != self::genToken()) {   emMsg('權(quán)限不足,token error');  } }

驗(yàn)證了Rips掃描出的文件包含問題(第一次使用Rips),發(fā)現(xiàn)無法復(fù)現(xiàn),因?yàn)镽ips掃描的時(shí)候是以文件形式,并沒有參照程序的嚴(yán)格邏輯,導(dǎo)致的誤報(bào)!

來到 \admin\admin_log.php 文件,從第78行開始:

//操作文章 if ($action == 'operate_log') {     $operate = isset($_REQUEST['operate']) ? $_REQUEST['operate'] : '';     $pid = isset($_POST['pid']) ? $_POST['pid'] : '';     $logs = isset($_POST['blog']) ? array_map('intval', $_POST['blog']) : array();     $sort = isset($_POST['sort']) ? intval($_POST['sort']) : '';     $author = isset($_POST['author']) ? intval($_POST['author']) : '';     $gid = isset($_GET['gid']) ? intval($_GET['gid']) : '';      LoginAuth::checkToken();      if ($operate == '') {         emDirect("./admin_log.php?pid=$pid&error_b=1");     }     if (empty($logs) && empty($gid)) {         emDirect("./admin_log.php?pid=$pid&error_a=1");     }      switch ($operate) {         case 'del':             foreach ($logs as $val)             {                 doAction('before_del_log', $val);                 $Log_Model->deleteLog($val);                 doAction('del_log', $val);             }             $CACHE->updateCache();             if ($pid == 'draft')             {                 emDirect("./admin_log.php?pid=draft&active_del=1");             } else{                 emDirect("./admin_log.php?active_del=1");             }             break;         case 'top':             foreach ($logs as $val)             {                 $Log_Model->updateLog(array('top'=>'y'), $val);             }             emDirect("./admin_log.php?active_up=1");             break;         case 'sortop':             foreach ($logs as $val)             {                 $Log_Model->updateLog(array('sortop'=>'y'), $val);             }             emDirect("./admin_log.php?active_up=1");             break;         case 'notop':             foreach ($logs as $val)             {                 $Log_Model->updateLog(array('top'=>'n', 'sortop'=>'n'), $val);             }             emDirect("./admin_log.php?active_down=1");             break;         case 'hide':             foreach ($logs as $val)             {                 $Log_Model->hideSwitch($val, 'y');             }             $CACHE->updateCache();             emDirect("./admin_log.php?active_hide=1");             break;          ...//中間的代碼要驗(yàn)證管理身份,故省略          case 'uncheck':             if (ROLE != ROLE_ADMIN)             {                 emMsg('權(quán)限不足!','./');             }             $Log_Model->checkSwitch($gid, 'n');             $CACHE->updateCache();             emDirect("./admin_log.php?active_unck=1");             break;     } }

那么我們嘗試越權(quán)刪除文章http://www.test.com/admin/admin_log.php?action=operate_log&operate=del&blog=29&token=994132a26661c8c244a91063c4701a7e  失敗了提示權(quán)限不足,來到\include\model\log_model.php 發(fā)現(xiàn)

/**  * 刪除文章  *  * @param int $blogId  */ function deleteLog($blogId) {  $author = ROLE == ROLE_ADMIN ? '' : 'and author=' . UID;  $this->db->query("DELETE FROM " . DB_PREFIX . "blog where gid=$blogId $author");  //這里和上一句限制了作者只能刪除自己的文章  if ($this->db->affected_rows() < 1) {   emMsg('權(quán)限不足!', './');  }  // 評(píng)論  $this->db->query("DELETE FROM " . DB_PREFIX . "comment where gid=$blogId");  // 標(biāo)簽  $this->db->query("UPDATE " . DB_PREFIX . "tag SET gid= REPLACE(gid,',$blogId,',',') WHERE gid LIKE '%" . $blogId . "%' ");  $this->db->query("DELETE FROM " . DB_PREFIX . "tag WHERE gid=',' ");  // 附件  $query = $this->db->query("select filepath from " . DB_PREFIX . "attachment where blogid=$blogId ");  while ($attach = $this->db->fetch_array($query)) {   if (file_exists($attach['filepath'])) {    $fpath = str_replace('thum-', '', $attach['filepath']);    if ($fpath != $attach['filepath']) {     @unlink($fpath);    }    @unlink($attach['filepath']);   }  }  $this->db->query("DELETE FROM " . DB_PREFIX . "attachment where blogid=$blogId"); }

這個(gè)越權(quán)漏洞不存在,同時(shí)看了下面的函數(shù)判斷也是做了類似的處理

到這里其實(shí)我們對(duì)于整個(gè) CMS 的架構(gòu)已經(jīng)較為熟悉了,基本能根據(jù)對(duì)應(yīng)函數(shù)功能,直接手動(dòng)找到對(duì)應(yīng)的函數(shù)位置。

令人傷心的是,通過 Rips 代碼審計(jì)工具得到的結(jié)果,一個(gè)都沒復(fù)現(xiàn)成功...

0x07 Seay輔助審計(jì)

相信很多人都知道法師的這款工具,主要還是因?yàn)橹形?,用著方便,但是完全依靠正則的方式去匹配函數(shù),只能發(fā)現(xiàn)那些函數(shù)直接的控制漏洞,邏輯漏洞有時(shí)候可以根據(jù)逆推可以發(fā)現(xiàn),但這種情況很少。

使用這款工具掃描出來共120個(gè)可能的情況(根據(jù)經(jīng)驗(yàn)98%以上都是沒法復(fù)現(xiàn)的),然后一個(gè)個(gè)排查,有的例如SQL語句反單引號(hào)這樣的,很容易就可以判斷給忽略,就不需要考慮。

在 /admin/store.php 看到這樣一串代碼:

怎樣學(xué)習(xí)PHP代碼審計(jì)

store.php

這里我的思考是,如果在emlog官網(wǎng)有URL跳轉(zhuǎn)鏈接的話,那么就可以構(gòu)造下載遠(yuǎn)程任意的文件到網(wǎng)站,但是測試了官網(wǎng)沒有跳轉(zhuǎn)鏈接,那么我們嘗試下載別的插件(鏈接跳轉(zhuǎn)等),或者有黑客精心構(gòu)造了一個(gè)插件或者模版,然后再利用,這也算是一個(gè)可行的方案。

此處需要管理員權(quán)限,作為代碼審計(jì)的一個(gè)參考思路,不是要發(fā)現(xiàn)什么0day,而是希望大家能夠在代碼審計(jì)方面有所收獲。

(1). SQL注入

對(duì)于SQL注入,Seay工具一直都沒準(zhǔn)過,這里筆者推薦方式,使用全局搜索 $_GET[ 或  $_PSOT[,然后看看是否代入了SQL查詢,然后一一驗(yàn)證。

然后我發(fā)現(xiàn)了這樣一個(gè)沒有過濾IP參數(shù)

怎樣學(xué)習(xí)PHP代碼審計(jì)

IP參數(shù)

然后到 admin/comment.php 中查看

怎樣學(xué)習(xí)PHP代碼審計(jì)

comment.php

再看 delCommentByIp($ip) 函數(shù)

怎樣學(xué)習(xí)PHP代碼審計(jì)

IP參數(shù)sql

由此我們可以確定了SQL注入的存在

驗(yàn)證如下:

怎樣學(xué)習(xí)PHP代碼審計(jì)

SQL注入

(2).一個(gè)CSRF+任意文件刪除

$_GET[]型分析完以后,就尋找$_POST[]的,然后在admin/data.php文件中找到了如下代碼

怎樣學(xué)習(xí)PHP代碼審計(jì)

data.php

這里我們發(fā)現(xiàn),并沒有驗(yàn)證toknen,那么可以構(gòu)造csrf頁面,這里筆者就不演示了,直接BURP驗(yàn)證一下任意文件刪除吧,關(guān)于CSRF,只要沒有調(diào)用上面基礎(chǔ)函數(shù)部分說到的  LoginAuth::checkToken() 方法的,都存在CSRF

怎樣學(xué)習(xí)PHP代碼審計(jì)

CSRF+任意刪除

這里就成功刪除了文件

(3).TAG SQL注入

在POST參數(shù)中發(fā)現(xiàn)此處并沒有過濾,同時(shí)在 deleteTag() 函數(shù)中,代入了SQL查詢,因此又是一個(gè)SQL注入

怎樣學(xué)習(xí)PHP代碼審計(jì)

tag sql

來看deleteTag()函數(shù):

怎樣學(xué)習(xí)PHP代碼審計(jì)

deletetag

又調(diào)用了getBlogIdsFromTagId()函數(shù),同樣沒有過濾

怎樣學(xué)習(xí)PHP代碼審計(jì)

getBlogIdsFromTagId

因此使用抓包驗(yàn)證一下:

怎樣學(xué)習(xí)PHP代碼審計(jì)

驗(yàn)證

但是其他語句利用時(shí)候并沒有回顯,筆者不知道什么原因,沒仔細(xì)探究,但是可以采用時(shí)間盲注的方式。

至此,利用工具的半自動(dòng)化審計(jì)已經(jīng)結(jié)束,下面準(zhǔn)備手工測試

0x08 手工測試

手工測試也不是單純的翻文件,應(yīng)當(dāng)以灰盒測試為主導(dǎo),從邏輯、權(quán)限、敏感信息等方面入手

(1).后臺(tái)登陸存在暴力破解風(fēng)險(xiǎn)

在這里,我之前提到過的驗(yàn)證碼未及時(shí)銷毀的歷史問題還存在,此處不再詳細(xì)敘述,請(qǐng)參考https://blog.csdn.net/dyboy2017/article/details/78433748

(2).報(bào)錯(cuò)信息導(dǎo)致物理路徑泄漏

大家不要以為這是小事情,當(dāng)sql注入存在的時(shí)候,我們有機(jī)會(huì)是可以直接寫shell文件,安全無小事

一個(gè)低權(quán)限的方式,在游客的條件下測試一下

怎樣學(xué)習(xí)PHP代碼審計(jì)

物理路徑

payload:http://www.test.com/admin/attachment.php?action[]=

原因是:addslashes() expects parameter 1

(3).Cookie可計(jì)算

在include/lib/loginauth.php中134行開始

/**  * 寫用于登錄驗(yàn)證cookie  *  * @param int $user_id User ID  * @param bool $remember Whether to remember the user or not  */ public static function setAuthCookie($user_login, $ispersis = false) {  if ($ispersis) {   $expiration  = time() + 3600 * 24 * 30 * 12;  } else {   $expiration = null;  }  $auth_cookie_name = AUTH_COOKIE_NAME;  $auth_cookie = self::generateAuthCookie($user_login, $expiration);  setcookie($auth_cookie_name, $auth_cookie, $expiration,'/'); }  /**  * 生成登錄驗(yàn)證cookie  *  * @param int $user_id user login  * @param int $expiration Cookie expiration in seconds  * @return string Authentication cookie contents  */ private static function generateAuthCookie($user_login, $expiration) {  $key = self::emHash($user_login . '|' . $expiration);  $hash = hash_hmac('md5', $user_login . '|' . $expiration, $key);  $cookie = $user_login . '|' . $expiration . '|' . $hash;  return $cookie; }

可以看到此處的cookie都可以直接計(jì)算得到,只需要知道根目錄下config.php中的

//auth key define('AUTH_KEY','dx1&CH^En86GZnxd9CLO7GwC0Q5eYHKM450f598bbd148b6a62f7d263623e31c3'); //cookie name define('AUTH_COOKIE_NAME','EM_AUTHCOOKIE_VzfVniPWDqd1LM3BFocnrcjpAGH4lUbz');

即可。

(4).側(cè)邊欄存儲(chǔ)性XSS

為了同樣是為了支持HTML代碼的輸出,沒有轉(zhuǎn)義對(duì)應(yīng)的腳本代碼標(biāo)簽,導(dǎo)致了存儲(chǔ)性的XSS存在

怎樣學(xué)習(xí)PHP代碼審計(jì)

側(cè)邊欄XSS

0x09 Getshell

(1).SQL注入拿到shell

如上所講有SQL注入的存在,同時(shí)可以獲取到物理路徑,那么就可以直接寫Shell

(2).后臺(tái)插件上傳zip

因?yàn)楹笈_(tái)可以直接上傳本地zip文件,這里我們?nèi)ス倬W(wǎng)下載一個(gè)插件,同時(shí)把我們的shell文件(比如dyboy.php)加入zip,上傳安裝這個(gè)插件就可以了,然后shell地址為:http://www.test.com/content/plugins/插件名/dyboy.php

(3).后臺(tái)模版上傳zip

和插件同樣的原理,這里的shell地址為:http://www.test.com/content/templates/模版名/dyboy.php

(4).備份文件拿shell

后臺(tái)的數(shù)據(jù)功能處,先備份一個(gè),然后下載到本地,加入SELECT "" into outfile  'D:\\Server\\htdocs\\safe\\dyboy.php';

然后導(dǎo)入備份恢復(fù)本地?cái)?shù)據(jù)即可

這樣就在網(wǎng)站個(gè)目錄生成了一個(gè)dyboy.php的shell

EMLOG是一個(gè)非常小巧輕快的博客系統(tǒng),運(yùn)行占用資源非常低,所以非常適合博主用作博客用途,其實(shí)只要不開啟會(huì)員功能,沒有弱口令就沒有什么大的威脅。以此文章作為PHP代碼審計(jì)拋磚引玉,文章所述方法同樣適用于其他的CMS代碼審計(jì)和分析。

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI