SQL注入漏洞從發現到修復
發現漏洞
一、環境準備
1、在Linux主機上準備一套Xampp:模擬攻防
2、在VSCode利用Remote Development進行遠程調試
3、在Lampp的htdos目錄下創建security目錄,用于編寫服務器PHP代碼
二、編寫Login.html
三、編寫Login.php
"; echo "location.href='welcome.php'";}else{ // echo "login-fail
"; echo "location.href='login.html'";}//關閉數據庫mysqli_close($conn)?>
四、編寫一個登錄后才能訪問的welcome.php
");}echo '歡迎來到安全測試平臺';?>
五、進行登錄的滲透測試
在登錄界面輸入一個單引號[']作為用戶名,Burp響應如下:
Warning: mysqli_num_rows() expects parameter 1 to be mysqli_result, boolean given in /opt/lampp/htdocs/security/login.php
以上響應出現MySQL報錯信息,上述報錯信息存在兩個漏洞:
1、單引號可以成功引起SQL語句報錯,說明后臺沒有專門對單引號進行處理
select * from user where username ='$username' and password = '$password' 正常情況:select * from user where username ='root' and password = '$password' 攻擊情況:select * from user where username =''' and password = '$password' 攻擊Payload:username:x' or userid=1#' Post正文: username=x' or+userid=1#'&password=111111&vcode=0000 select * from user where username ='x' or userid=1#'' and password = '$password'
2、在報錯信息里泄露了敏感信息
/opt/lampp/htdocs/security/login.php(當前代碼的絕對路徑)
六、總結
上述代碼一共發現了6個漏洞
1、welcome.php頁面誰都可以訪問,沒有進行登錄判斷(中)
2、在登錄界面輸入’作為用戶名,報錯信息存在login.php的絕對路徑,暴露了系統后臺的敏感信息(低)
3、保存用戶信息的數據表中,密碼字段是明文保存的,不夠安全(中)
4、登錄界面可以進行SQL注入,進而輕易實現登錄(高)
5、login.php頁面使用了萬能驗證碼(中)
6、登錄功能可以被爆破,沒有進行爆破防護(中)
SQL注入-登錄漏洞-基礎修復
一、使用Python進行注冊測試
import requests# 利用Python對PHP的登錄界面進行Fuzz測試def login_fuzz(): # 先使用單引號進行測試 url = 'http://192.168.72.148/security/login.php' data = {'username': "'", 'password': '666666', 'vcode': '0000'} resp = requests.post(url=url, data=data) if 'Warning' in resp.text: print("本登錄功能可能存在SQL注入漏洞,可以嘗試") # 如果單引號存在利用嫌疑,則繼續利用 payload_list = ["x' or id=1#", "x' or userid=1#", "x' or userid=2#"] for username in payload_list: data = {'username': username, 'password': '666666', 'vcode': '0000'} resp = requests.post(url=url, data=data) if "login-fail" not in resp.text: print(f'登錄成功,payload為:{data}') else: print('通過試探,發現后臺界面對單引號不感興趣;')if __name__ == '__main__': login_fuzz()
二、任意訪問授權界面
welcome.php頁面誰都可以訪問,沒有進行登錄判斷,該頁面是登錄后才能訪問,所以在該頁面需要進行登錄判斷,代碼修改為:
1、在common.php中添加session_start(),讓其他頁面引入,便于直接使用Session
php
session_start();
function create_connection(){
// 連接數據庫
$conn = mysqli_connect('127.0.0.1','root','123456','learn') or die("數據庫連接不成功!");
// 設置數據庫的編碼格式
mysqli_query($conn,"set names utf8;");
mysqli_set_charset($conn,'utf8');
return $conn;
}
?>
2、在welcome.php頁面中,源代碼修改為
// OWASP-失效訪問控制
// 修復方法:在顯示文本之前,先進行SESSION變量的驗證
include "common.php";
//isset() 函數用于檢測變量是否已設置并且非 NULL。
if (!isset($_SESSION['islogin']) or $_SESSION['islogin'] != 'true'){
die ("請登錄后再訪問此頁面
");
}
echo '歡迎來到安全測試平臺';
?>
3、在login.php中,登錄成功后添加以下代碼
if (mysqli_num_rows($result) == 1){
echo "login-pass
";
// 登錄成功后,記錄SESSION變量
$_SESSION['username'] = $username;
$_SESSION['islogin'] = 'true';
echo "location.href='welcome.php'";
}
三、修復login.php暴露文件路徑
當在用戶名輸入單引號時,會引起后臺報錯,一方面說明后臺沒有對單引號進行轉義處理,導致單引號可以被注入到SQL語句中,進而導致SQL語句中存在單獨的一個單引號,SQL語句無法有效閉合,發生錯誤。同時,還將該代碼的絕對路徑暴露出來,屬于敏感信息,應該將其屏蔽,修復代碼如下:
$result = mysqli_query($conn,$sql) or die("SQL語句執行錯誤!") ;
四、修改用戶表密碼為明文
1、使用md5函數
$source = 'YikJiang'; echo md5($source); //提示一:user表中password字段必須是32+位 //提示二:在用戶注冊時,必須使用md5函數將密碼加密保存
SQL注入-登錄漏洞-SQL注入防護
從代碼和SQL語句的邏輯層面進行考慮,不能輕易讓密碼對比失效
基于將用戶輸入的引號(單引號和雙引號)進行轉義處理的前提,可以使用PHP內置函數addslashes進行強制轉義
一、登錄SQL語句的邏輯問題
1、該SQL語句在實現登錄操作時,存在嚴重的邏輯問題,用戶名和密碼的對比不應該放在同一條SQL語句中。
2、應先通過用戶名查詢user表,如果確實找到一條記錄(用戶名唯一的情況下),找到記錄后再進行密碼的單獨對比。
修復后的代碼如下:
$sql = "select * from user where username ='$username'";$result = mysqli_query($conn,$sql) or die("SQL語句執行錯誤!") ; //$result稱之為結果集// 以下代碼沒用進行爆破的防護(OWASP-認證和授權失敗)//如果用戶名真實存在,剛好找到一條,則再單獨進行密碼比較,即使用戶名出現SQL注入漏洞,但是只要密碼不正確,也無法登錄if (mysqli_num_rows($result) == 1){ // $row = mysqli_fetch_all($result,MYSQLI_ASSOC); //索引數組+下標數組 // $row = mysqli_fetch_row($result); //索引數組 $row = mysqli_fetch_assoc($result); //下標數組 // var_dump($row); if ($password == $row['password']){ echo "login-pass
"; // 登錄成功后,記錄SESSION變量 $_SESSION['username'] = $username; $_SESSION['islogin'] = 'true'; echo "location.href='welcome.php'"; } else{ echo "login-fail"; }}else{ echo "login-fail
"; echo "location.href='login.html'";}
二、使用addslashes函數
addslashes函數可以將字符串中的單引號、雙引號、反斜杠、NULL值自動添加轉義符,從而防止SQL注入中對單引號和雙引號的預防。
原始SQL語句如下:
select * from user where username ='$username' and password = '$password'
如果用戶輸入x' or userid=1#',則SQL語句變成:
select * from user where username ='x' or userid=1#'' and password = '$password'
如果使用addslashes強制為用戶輸入添加轉義符,則變成:
select * from user where username ='x\' or userid=1#\'' and password = '$password'
上述SQL語句的用戶名為:x\’ or userid=1#\’
三、使用MySQL面向對象方式
1、面向過程方式
使用PHP自帶的函數
// 面向過程的方式function create_connection(){ // 連接數據庫 $conn = mysqli_connect('127.0.0.1','root','123456','learn') or die("數據庫連接不成功!"); // 設置數據庫的編碼格式 mysqli_query($conn,"set names utf8;"); mysqli_set_charset($conn,'utf8'); return $conn;}// 將數據庫查詢的結果集中的數據取出,保存到一個數組當中$row = mysqli_fetch_assoc($result);//mysqli_fetch_all默認使用索引數組,也可以設定參數強制使用關聯數組// $row = mysqli_fetch_all($result,MYSQLI_ASSOC); // $row = mysqli_fetch_row($result); //索引數組
2、面向對象方式
function create_connection_oop(){ $conn = new mysqli('127.0.0.1','root','123456','learn') or die("數據庫連接不成功!"); // 兩種方法設置字符集 // 1、$conn->query("set names utf8;") // 2、 $conn->set_charset('utf8'); return $conn;}// 執行SQL語句function test_mysqli_opp(){ $conn = create_connection_oop(); $sql = "select * from user where userid < 6"; $result = $conn->query($sql); //獲取結果集行數 echo $result->num_rows."
"; // 獲取結果集數組 // 使用關聯數組 $rows = $result->fetch_all(MYSQLI_ASSOC); // var_dump($rows); // 遍歷數組 foreach ($rows as $row){ echo "username:". $row['username'] . ",passwrod" . $row['password'] . "
"; }}
四、使用MySQL預處理功能
1、預處理功能的用法
預處理的過程,就是先交給SQL數據庫進行SQL語句的準備,準備好后再將SQL語句中的參數進行值的替換,引號會進行轉義處理,將所有參數變成普通字符串,再進行第二次正式的SQL語句執行。MySQL的預處理既支持面向過程,也支持面向對象方式,但是我們后續直接使用面向對象的方式。
$conn = create_connection_oop();$sql = "select userid,username,password,role from user where username= ?";$stmt = $conn->prepare($sql);$stmt->bind_param("s",$username); // 綁定查詢參數$stmt->bind_result($userid,$username2,$password2,$role); // 綁定結果參數$stmt->execute(); // 執行$stmt->store_result(); //調用結果
2、使用預處理來防止SQL注入
3、配置MySQL臨時日志查看SQL語句
在MySQL數據庫中運行以下語句,開啟臨時日志,將日志信息保存到表格mysql數據庫的general_log表中。
#開啟 use mysql; set global log_output = 'TABLE'; set global general_log = 'ON'; #確認 show variables like "general_log"; MySQLi的預處理功能同樣支持面向過程和面向對象 除了MySQLi用于處理數據庫外,在PHP中還有最傳統的MySQL和PDO兩種方式
SQL注入-登錄漏洞-驗證碼處理
一、驗證碼生成原理
核心目的是確保是人在使用系統,圖片驗證碼、拖動驗證碼、拼圖驗證碼、問答驗證碼、計算驗證碼等。
二、驗證碼代碼實現
添加源代碼vcode.php,基于PHP繪制基礎圖片,生成驗證碼,然后將該驗證碼保存到Session變量
三、修改HTML頁面調用驗證碼
修改login.html
input[name='vcode']{ width: 200px;}
YikJiang
登錄
四、登錄的驗證碼校驗
修改login.php
if(strtoupper($_SESSION['vcode']) != strtoupper($vcode) ){
die("vcode-error"); //向前端輸出一條消息同時結束代碼的運行
驗證碼一旦生成后,不一定必須保存在Session中,任何可以存儲數據的方式均可以。比如數據庫、文件、內存中,或者保存在Redis緩存服務器中。比如短信驗證碼,通常會有一個時間限制(5分鐘內有效),最好的解決方案就是使用Redis緩存,并設置Key的過期時間