本文使用世界上最好的語言(PHP)針對某h站系統而開發的自動化注入工具。
首先來明確工具開發中的幾個點:
1、正常頁面和錯誤頁面的區別
2、該工具自動化判斷表名以及表中欄位的資料長度
3、使用快取機制(讀取和寫入)
4、使用php cli(命令列)模式
這裡可能會有人疑問了“為啥使用php?不使用python”,因為這個注入點比較特殊,是一個302頁面跳轉處的注入,從資料庫中查詢到某個域名後會輸出到頁面,然後跳轉,否則查詢不到某個域名的情況下造成死迴圈,輸出頁面的程式碼如下:
<script language="javascript" type="text/javascript">
document.write('<a href="http://www.52six.xyz"></a>');
go.click();
</script>
Href引數有值的情況下為正常,否則異常
<script language="javascript" type="text/javascript">
document.write('<a href=""></a>');
go.click();
</script>
異常之後就會存在死迴圈,因為href都為空了。
這裡因為是302頁面,python獲取不到頁面內容,而php則可以獲取到內容,或許是我不擅長使用python,對php情有獨鍾的原因吧!
對於工具開發,必不可少的是http請求函式,php現成的有file_get_contents,相對來說,這個系統函式並不好用,因為請求不到的使用就會產生異常,當然我們不希望程式執行中突然出現異常就被中斷了,然而我們要用的curl函式,下面將curl封裝了一個函式:
我們先來測試一下,主要我們還是要測試是否能獲取到302頁面的內容,程式碼如下:
獲取到的內容如下:
我使用-----------------------------------------------將正確頁面和錯誤頁面分割開來,href引數中有值的是正常頁面,否則為異常頁面
我們接著往下,因為現在大多數自動化工具都是採用接收命令列引數的方式,比如SQLMAP:
Python sqlmap.py -u “” --batch --dbms “”
也就是這樣的,我們也採用這樣的方式吧!下面我們來具體的實現:
Php cli模式接收命令列引數的超全域性陣列為$argv
首先我們要想到如何實現-u啊這些的,所以這裡我們還是將獲取的東西封裝並且命名為GetHostId,在這個函式中我們不需要引數,我們需要獲取全域性變數可以使用兩種形式$GLOBALS和global $argv
這裡我們還是使用$GLOBALS的方式獲取,因為我們最後會將這些函式封裝在一個類裡面,這樣更加靈活,好了,我們繼續吧!
根據上圖我們可以知道,陣列下標為0的數值是無用的,所以我們需要刪掉$GLOBALS[“argv”][0]
這裡第一行就將下標為0的值刪掉了,因為第1個就是執行的檔名,所以直接刪掉,然後進行賦值,我們接著往下來,我們命令列中需要兩個引數一個為--host或者-h,另一個為--id,函式已封裝完畢
這裡我們當前檔案接收命令列引數就有了兩種形式:
程式這樣做了以後就顯得不靈活了,因為這裡的位置不能隨意調換就比較懊惱,那麼我們再封裝一個函式
這樣獲取命令列引數就可以不分先後順序了!!這裡已經獲取到了域名以及id,我們繼續往下,獲取到域名以及id過後,我們還需要另一個引數--batch,如果這個引數存在那麼我們預設使用http,否則則在命令列中獲取,已將獲取這部分封裝成了函式:
GetInput函式是獲取命令列中的值,而GetHttp是呼叫GetInput獲取使用者輸入的是https還是http協議
這裡獲取到http或者https協議拼接到host後,我們將拼接的值進行存活的判斷,我們此時封裝一個is_survival函式,存活返回true,否則false,判斷存活本應判斷狀態碼,但是判斷狀態碼又不是很嚴格,這裡我們使用正規表示式來進行匹配,返回值如下:
我們正則所匹配的東西應該是href中的值,那麼我們的正規表示式如下:
/href=”(.+)”/i
以上就是判斷存活的必要條件,我們繼續:
這裡存活條件為href中有值的情況下存活,否則是死亡狀態,死亡狀態有兩種情況,一種是無法訪問或者輸入的id值不正確,我們接著往下走
查詢不到內容的時候!
這個函式中同時會自動測試五次,若五次過後依然獲取不到那麼自動退出,這裡我們驗證了是否存活,接著我們從字典中獲取表名,然後測試取出的表是否存在,那麼我們在本地建立一個dictionary目錄,並且寫入一個名為tables.txt的字典檔案:
我們接著往下走,這裡我們封裝兩個函式,一個為GetContents,另一個為GetTables
這裡我們透過GetTables函式中呼叫GetContents函式獲取字典內容,然後返回,返回到GetTables函式中,將內容以回車分割為陣列並且返回陣列,那麼接下來我們還要再接著封裝一個名為TablesSurvival的函式,然後返回值為陣列,這裡我們將查表的SQL拼接上去
+and+exists(select%20*%20from%20admin)
這裡我們成功測試表名存在之後再封裝一個名為GetColumns的函式,此函式獲取dictionary目錄下的columns.txt檔案中的內容,並以回車分割,具體實現:
這裡欄位名的格式和表名的格式略有不同,格式如下:
到這裡為止,我們的程式已經可以自動化測試表名以及欄位名,因為前面相同功能的函式有些重複,我們這裡來進行合併,比如GetColumns和GetTables,將這兩個函式合併後名稱改為GetTablesOrColumns:
接著我們再將TablesSurvival和ColumnsSurvival合併為TablesOrColumnsSurvival:
執行一下
程式碼精簡了以後,還是一樣的執行結果,我們接著來,這個時候我們知道了表名以及欄位名稱,既然是自動化工具,自然少不了測試欄位中內容的長度,也將獲取字元長度的程式碼封裝成一個名為GetStringLength,返回值為陣列:
這裡可以看到我們返回的格式為[表名,欄位長度=>欄位名,欄位長度=>欄位名],然後我們已經得知了表名,欄位名以及欄位內容長度,接著我們定義一個名為SqlInjection,我們這裡就不需要返回值了。
到這裡呢!基礎的已經寫完成了,但是我們還需要一個機制,那就是快取,我們將獲取到的內容寫入到資料夾下,我們的快取目錄結構如下
Data目錄->域名目錄->表名目錄->欄位文字,那麼我們現在封裝一個名為TextPut函式:
隨後我們在GetStringLength中呼叫textput函式,將欄位中字元長度以及欄位名寫入到以域名的名稱命名的文字中
寫入文字格式如下:
接著往下走,然後在SqlInjection函式中呼叫並將注入到的資料寫入檔案中
這樣我們就成功將獲取到的資料寫入到檔案中,但是還要進行一次判斷,封裝一個名為is_cache函式:
並且將SqlInjection函式增加一個cache形參,方便快取機制
最終程式呼叫如上,我們還應該寫一個名為DumpContent的函式,將注入到的資料列印出來
程式呼叫最終如下:
這裡我們來測試一下快取機制
這裡我們的username欄位測試到第四個字元,我們退出程式!
程式果斷退出了,接著我們再來執行一下:
這裡說一下為什麼還是在獲取第四個字元呢?因為我們們設定的是,只有讀取到了寫入檔案之後才會跳到第五個字元,然而我們這裡還沒讀取到第四個字元,所以還是從第四個字元開始。。。靜靜的等待程式執行完畢。。。
本次分享就到這裡了,勿嫌棄,其實工具開發旨在思路,用什麼語言都是一樣的,只要可以實現就行。
自動化測試工具地址:https://github.com/only-wait/tools/tree/master/h%E7%AB%99%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7