前段時間在整理一個PHP函數代碼審計的項目,所以文章也是圍繞PHP的代碼審計來寫,如果有寫的不對的地方,還請大佬們指正。文章開始前,我們先來了解一下PHP是什么。
0x01 PHP是什么
根據百度百科的描述,PHP是一種在服務器端執行的通用開源腳本語言,主要適用於Web開發。
既然是網站編程語言,自然需要一款工具輔助程序員高效編程。PhpStorm就是一款PHP集成開發工具,可以隨時幫助程序員對代碼進行調整、運行單元測試、且提供可視化debug功能。
當然也可以使用其他工具對PHP程序進行調試,比如Xdebug,一款開放源碼的PHP debug工具,用來跟蹤、調試和分析PHP程序的運行狀況。
大家可以通過學習這個實驗,掌握PhpStorm環境搭建和Xdebug工具的安裝和配置——代碼審計的前期准備
網傳這么一個段子:如何讓一個論壇的人吵起來?答:PHP是世界上最好的語言。這個梗出自PHP的官方文檔,最早出現在2001年7月的PHP文檔中。雖然有PHP是世界上最好的語言這種說法,但是也有一些因為弱類型語言的安全性問題出現,這就需要安全人員通過代碼審計來檢查PHP程序的安全性。
0x02 代碼審計又是什么
代碼審計,顧名思義就是檢查源代碼中的安全缺陷。通過自動化工具或人工審查的方式,對程序源代碼逐條進行檢查和分析,發現源碼缺陷引發的安全漏洞,有時還需要提供代碼修訂措施和修復建議。
軟件的源代碼、程序缺陷可能導致嚴重的安全漏洞,要消除代碼中的漏洞、減少不必要的補丁升級,就需要進行源代碼的安全審計。源代碼審計是對代碼庫和軟件架構的安全性、可靠性進行全面的安全檢查。人工審查已經成為軟件源碼設計、開發和應用的最佳保障,因此做好代碼審計就是從安全的角度對整個代碼質量進行評估。安全人員需要在黑客發現系統漏洞之前,找出應用的安全隱患,並提供相應的安全報告和修復方法,從而提高應用系統的安全性。
除了人工審查的方式,還可以通過一些自動化工具進行代碼審計,下面簡單介紹兩種代碼審計利器:
1、 Seay源代碼審計系統
這是一款針對PHP代碼安全審計的系統,基於C#語言開發,主要運行於Windows系統上。這款軟件能夠發現SQL注入、代碼/命令執行、文件包含、文件上傳、拒絕服務、XSS、信息泄露、任意URL跳轉等漏洞。關於Seay源代碼審計系統工具的使用,可以通過下面的實驗進行學習——代碼審計利器-Seay源代碼審計系統
2、 RIPS
RIPS通過標記和解析所有源代碼文件,自動檢測PHP應用程序中的漏洞。RIPS能夠將PHP源代碼轉換為程序模型,檢測程序流期間用戶輸入可能污染的敏感接收器,即潛在易受攻擊的函數。RIPS工具的使用參考下面的實驗——代碼審計利器-RIPS實踐
學習了代碼審計的常用工具,相信大家會對代碼審計的方法和步驟有一定的了解,那么接下來簡單總結一下代碼審計的流程:
① 通讀全文代碼:更好地了解程序的架構及業務邏輯,挖掘更多高質量的漏洞;
② 通讀敏感功能點:快速挖掘某種漏洞;
③ 正向追蹤可控變量;
④ 敏感關鍵字回溯參數傳遞過程。
下面進入文章的重點部分——PHP函數的代碼審計,通過具體實例分析PHP部分函數,包括in_array函數、filter_var函數、escapeshellarg與escapeshellcmd函數、parse_str函數及addslashes函數,學習上述函數缺陷引發的漏洞及利用方式,結合CTF練習掌握PHP函數漏洞審計流程。
0x03 PHP函數的代碼審計
項目來自https://github.com/hongriSec/PHP-Audit-Labs
這里我們根據《PHP函數漏洞審計》課程順序進行學習。
1、PHP代碼審計之in_array函數
既然是對in_array函數進行具體分析,我們先來了解一下in_array函數的相關定義:
注:后面說到的函數定義均來自PHP手冊,手冊地址:http://php.net/manual/zh/index.php
$needle變量表示待搜索的值,$haystack表示待搜索的數組。in_array函數用法為:在$haystack數組中搜索$needle變量的值,檢查值是否存在。如果第三個變量$strict的值為true,則in_array函數會進行強檢查,檢查$needle的類型是否和$haystack數組中的相同。
in_array函數為什么會存在漏洞呢,原因是程序員在使用in_array函數進行數據處理時,未使用第三個參數進行嚴格匹配,比如Piwigo軟件2.7.1版本就是因為in_array函數的不安全使用,導致SQL注入漏洞發生。
查看源碼/picture.php文件可以發現,當$_GET[‘action’]為rate時,會調用文件/include/function_rate.inc.php文件中的rate_picture方法,漏洞就存在於這個方法中:
而在rate_picture方法中,使用了in_array函數對$rate變量進行檢測,判斷$rate是否在$conf['rate_items']中:
由於functions_rate.inc.php文件中沒有將in_array函數的第三個參數設置為true,所以會進行弱比較,可以將$rate的值設置成1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));#進行繞過。
那么SQL語句就變成:
INSERT INTO piwigo_rate(user_id,anonymous_id,element_id,rate,date) VALUES (2,'192.168.2',1,1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));#,NOW())
這樣就可以進行盲注了。
直接利用sqlmap進行漏洞利用:
python sqlmap.py -u "http://10.1.1.79/piwigo/picture.php?/1/category/1&action=rate" --data "rate=1" --dbs
還可以用sqlmap獲取目標數據庫的表及敏感數據。
總結一下in_array函數的審計流程:
大家可以學習in_array函數缺陷引發的相關漏洞及其利用方式——PHP代碼審計之in_array函數
2、PHP代碼審計之filter_var函數
filter_var函數定義:
$variable變量表示待過濾的變量(變量的值在過濾前,會被轉換成字符串),$filter變量表示要應用的過濾器的ID,$options變量代表一個選項的關聯數組或按位區分的標識。filter_var函數返回過濾后的數據,過濾失敗則返回false。
這里以一個CTF練習介紹filter_var函數缺陷引發的漏洞可以怎么利用,題目考察filter_var函數的繞過與遠程命令執行。題目源碼為:
程序使用exec函數來執行curl命令,很容易讓人聯系到命令執行。可以看到用於拼接命令的$site_info變量是從用戶傳來的url參數,經過filter_var和parse_url兩個函數過濾而來。之后又規定當url參數的值以hetianlab.com結尾時,才會執行exec函數。
這就需要繞過filter_var和parse_url函數,並且需要滿足$site_info['host']的值以hetianlab.com結尾,然后通過執行系統命令獲取flag。
Payload為/ctf/index.php?url=demo://%22;ls;%23;hetianlab.com:80/
直接用cat f1agi3hEre.php命令過不了filter_var函數檢測,因為包含空格,使用如下payload獲取flag:/ctf/index.php?url=demo://%22;cat<f1agi3hEre.php;%23;hetianlab.com:80/
總結一下filter_var函數的審計流程如下入,也可以學習實驗——PHP代碼審計之filter_var函數
3、PHP代碼審計之escapeshellarg與escapeshellcmd函數
先看一下兩個函數的相關定義,escapeshellarg函數:
$arg變量表示需要被轉碼的參數,函數返回值為轉碼之后的字符串。
escapeshellcmd函數:
$command變量代表要轉義的命令,函數返回值為轉義后的字符串。
以一個由PHP內置函數mail,結合escapeshellarg與escapeshellcmd函數所引發的命令執行漏洞為例,代碼如下:
第4行代碼的作用是確保只使用有效的電子郵件地址$email,filter_var函數用於使用特定是過濾器過濾一個變量。PHP的mail函數在底層實現中,調用了escapeshellcmd函數,對用戶輸入的郵箱地址進行檢測。即使存在特殊符號,也會被escapeshellcmd函數處理轉義,無法達到命令執行的目的。第6行代碼的作用是處理$email傳入的數據,而escapeshellarg和escapeshellcmd兩個函數一起使用,會造成特殊字符逃逸,導致遠程代碼執行。
PHPMailer命令執行漏洞(CVE-2016-10033)也是利用escapeshellarg和escapeshellcmd兩個函數結合使用,導致了單引號逃逸。具體的漏洞分析和利用過程可以通過這個實驗進行學習——PHP代碼審計之escapeshellarg與escapeshellcmd函數
總結一下escapeshellarg與escapeshellcmd函數的審計流程:
4、PHP代碼審計之parse_str函數
同樣先了解parse_str函數的相關定義:
$encoded_string變量代表輸入的字符串,如果設置了第二個變量result,變量將會以數組元素的形式存入數組,作為替代。
下面簡述parse_str函數缺陷引發的變量覆蓋漏洞,代碼如下:
由於第22行parse_str()調用,其行為非常類似於注冊全局變量,通過提交類似config[dbhost]=127.0.0.1這樣的數據,因此可以控制getUser()中第6到第9行的全局變量$config。如果目標存在登錄驗證的過程,就可以通過變量覆蓋的方法,遠程連接我們自己的MySQL服務器,從而繞過登錄驗證進行下一步攻擊。一個簡單的例子
直接覆蓋了原有的變量$b。
parse_str函數還有一個有意思的CTF練習,首先利用PHP哈希比較缺陷,構造請求參數,使其經過哈希之后,值是以’OE’開頭。缺陷就是如果兩個不同的密碼經過哈希之后,其哈希值都是以'OE'開頭,PHP將會認為它們結果都為0。請求后頁面會出現‘flag is here’,點擊跳轉至flag.php。題目真正的考察點在於flag.php存在一個refer判斷,判斷refer是否存在,如果存在則展示上傳頁面,否則返回‘you can not see this page’。接下來的部分存在時間競爭問題,需要在寫入too slow之前訪問之前寫入的文件,才能獲取flag。
此題的解法是開burp的200線程不斷發包,在start attack之前寫一個腳本不斷請求寫入文件的路徑,是變量覆蓋與競爭條件漏洞的結合利用。
CTF練習地址——PHP代碼審計之parse_str函數
總結一下parse_str函數的審計流程:
5、PHP代碼審計之addslashes函數
addslashes函數相關定義:
$str表示要轉義的字符,當我們要往數據庫中輸入數據時,例如將名字O’reilly插入到數據庫中,就需要對其進行轉義。
以一個用戶登錄程序為例,考察通過SQL注入繞過登錄驗證,代碼如下:
第29行通過POST方式傳入user和passwd兩個參數,通過isValid函數判斷是否合法。isValid函數主要功能代碼在第10~20行,調用sanitizeInput方法對user和passwd進行相關處理。sanitizeInput方法主要功能代碼在第22~25行,針對輸入的數據調用addslashes函數進行處理,然后對處理后的內容進行長度判斷,長度大於20則只截取前20個字符。
這道題已經過濾了單引號,正常情況是沒有注入了,為什么還能導致注入呢?原因實際上出在第24行substr函數這里,下面簡單看一下substr函數的定義:
substr函數的參數說明:string表示輸入的字符串(至少有一個字符),如果start為非負數,返回的字符串從string的start位置開始,從0開始計算;如果start為負數,返回的字符串從string結尾處向前數,從第start個字符開始。
注:如果string的長度小於start,將返回false。
length:如果length為正數,返回的字符串將從start處開始,最多包括length個字符(取決於string的長度);如果length為負數,string末尾處的length個字符將會被省略(若start是負數則從字符串尾部算起);如果length為0、false或null,則返回一個空字符串;如果沒有提供length,返回的字符串將從start位置開始,直到字符串結尾。
關於substr函數一個簡單的例子:
substr中的參數0代表從位置為0的字符開始計算,2代表返回的字符串將從0(start)處開始最多包括2(length)個字符。
再回到題目中,我們知道反斜杠可以取消特殊字符的用法,而注入想要通過單引號閉合,必然會引入反斜杠。將官方提供的payload帶入題目中,拼接第15~17行代碼中的SQL語句:select count(p) from user u where user = ’1234567890123456789\’ AND password = ‘$pass’
這里的SQL語句由於反斜杠的原因,user=’1234567890123456789\’最后這個單引號會失去它的作用,我們讓password=or 1=1#,那么最后的SQL語句為:select count(p) from user u where user = ’1234567890123456789\’ AND password = ‘or 1=1#’
此時user值為1234567890123456789\’ AND password =,可以保證帶入數據庫執行的結果為true,就能順利地通過驗證。因此使用形如user=1234567890123456789’&passwd= or 1=1#的payload即可逃逸出\(反斜杠)注入。
蘋果CMS視頻分享程序8.0版本也曾爆出過SQL注入漏洞,程序調用addslashes函數對反斜杠進行轉義處理,但是對用戶請求的參數又會進行url解碼,因此可以使用雙url編碼繞過addslashes函數,觸發漏洞。
具體的漏洞分析過程可以學習——PHP代碼審計之addslashes函數
總結一下addslashes函數的審計流程:
0x04 總結
除了上面提到的,PHP還有很多常見危險函數,也有相關的實驗方便大家了解PHP常見的危險函數,以及使用這些函數可能導致的漏洞——PHP常見危險函數
代碼審計重在分析、重在堅持。文章看到最后,相信大家對代碼審計這個名詞不再陌生,學習完上述所有的實驗,再上GitHub找幾個代碼審計的案例源碼練練手,差不多就算入門了。安全學習貴在實踐、貴在總結。