目錄
1. 什麼是SQL注入
SQL注入是web應用程序對用戶輸入的數據沒有嚴格進行合法校驗和過濾,導致前端傳入到後端的數據被攻擊者惡意修改,欺騙服務器執行SQL語句實現對數據庫的任意操作,這就是SQL注入漏洞的原理。
2. 爲什麼會有SQL注入
- 研發人員在開發過程中代碼邏輯不嚴謹造成的,例如沒有對SQL參數進行嚴格過濾
- 未啓用框架的安全配置,例如PHP的magic_quotes_gpc
- 未啓防火牆等其他安全防護設備
3. SQL注入的種類
SQL注入的種類主要有以下幾種:
- 聯合注入
- 布爾注入
- 延時注入
- 報錯注入
- insert注入
除了以上幾種,還有很多其他的SQL注入方式,後面會對這些注入方式進行詳細介紹。
4. SQL注入原理
現在通過一個用戶登錄的萬能密碼漏洞來分析SQL注入漏洞產生的原因,測試環境如下:
在實驗的測試環境中Post data一欄就是攻擊者惡意修改的數據,Load URL一欄就是進行SQL注入測試的網址,使用POST方式提交數據,點擊Execute提交測試的數據:
萬能密碼的測試結果中看到成功登陸了,還可以使用test1用戶進行發留言和刪除,退出等操作。但是現在我們對於萬能密碼漏洞產生的原因仍然無從得知,爲什麼攻擊者惡意僞造數據就能成功登陸?其實問題的根源就是代碼邏輯不嚴謹,缺乏安全意識導致的。
爲了進一步分析漏洞產生的原因,我們需要通過分析代碼來了解前後端的數據是如何交互的,以及後端和數據庫之前如何交互的。我們知道的是,點擊Execute提交數據時,測試數據就隨着測試的網址從前端傳入到後端。
分析登錄功能對應的源代碼:
<?php
/**
* Created by TEST -- 用戶登錄
*/
require "./lib/init.php";
header("Content-type:text/html;charset=utf-8");
if(empty($_POST))
{
//header('Location: register.php');
echo "登錄信息爲空";
header("Refresh:3;url=login.php");
}else{
//是否設置用戶名和密碼
if(isset($_POST["user_name"]) && isset($_POST["user_pass"])){
//去除空格字符
$usename = trim($_POST["user_name"]);
$password = trim($_POST["user_pass"]);
//對密碼進行加密,然後拼接成SQL語句
$password = md5($password);
$sql = "select * from users where user_name='$usename' and user_pass='$password'";
//var_dump($sql);
//exit();
//查詢數據庫
$selectSQL = new MySql();
$user_data = $selectSQL->getRow($sql);
if($user_data!="")
{
session_start();
$_SESSION["user"] = $user_data["user_name"];
header("Location: user.php");
}else{
echo "用戶名或者密碼錯誤!";
header("Refresh:3;url=login.php");
}
}else{
echo "登錄信息不完整";
header("Refresh:3;url=login.php");
}
}
在以上的代碼中,對於前端傳入的用戶名和密碼等數據,後端只是簡單的去除空格字符並沒有進行嚴格的校驗和過濾,直接將用戶名和密碼拼接成SQL語句執行。
使用var_dump函數把拼接後的SQL語句打印出來:
最終拼接出來的SQL語句是這樣的:select * from users where user_name='123' or 1#' and user_pass='81dc9bdb52d04dc20036dbd8313ed055' ,重要的是這條SQL語句在MYSQL數據庫中是一定能執行成功的:
通過分析可知,前端傳入的用戶名和密碼中帶有“#”特殊字符,在MYSQL數據庫中“#”字符後面的內容會被當做註釋,但是後端代碼並沒有對“#”字符進行過濾而是直接拼接成SQL語句,也就是說,在這條SQL語句中“#”字符往後的全都當做註釋了,這就導致最終拼接的SQL語句實際上是這樣的:select * from users where user_name='123' or 1 。
MYSQL查詢到了所有的數據,卻只返回了第一條數據,在前端頁面爲啥是用的test1用戶登錄的呢?接着分析getRow函數的實現:
//查詢單行
public function getRow($sql){
$res = $this->link->query($sql);
//只截取第一條記錄
$row = $res->fetch_assoc();
return $row;
}
數據庫查詢到的所有數據都放在res對象中,但fetch_assoc函數只從res對象中截取了第一條數據並返回結果,而第一條數據就是test1用戶,前端頁面登錄時用的恰好就是test1用戶。
5. 如何防止SQL注入
以上面的用戶登錄爲例,後端在接收前端傳入的數據時,例如一些特殊字符應該使用PHP的addslashes函數和mysqli_real_escape_string函數進行過濾。
對用戶登錄功能的代碼改進:
//去除空格字符
$usename = addslashes(trim($_POST["user_name"]));
$password = addslashes(trim($_POST["user_pass"]));
這兩個函數的作用就是對 SQL語句中的特殊字符進行轉義,當後端接收到前端傳入的數據時,addslashes函數會對特殊字符進行轉義,然後再進行測試:
由於addslashes函數會對數據中的特殊字符進行過濾轉義,此時登錄就會報錯,可以看到addslashes函數對用戶名中的“ ’”特殊字符使用斜槓字符進行轉義了:
這條SQL語句在MYSQL數據庫中無法查詢到數據的:
通過一個簡單的小案例我們基本瞭解了SQL注入的原理,產生SQL注入的原因以及如何防禦的技巧。前端傳入後端的參數可以由用戶控制,且參數拼接成SQL語句會被帶入數據庫中執行。在開發過程中只要滿足上述所說就有可能產生SQL注入漏洞,因此在開發過程中應秉承“外部參數皆不可信”的原則進行開發。