淺談SQL注入
所謂SQL注入,就是通過把SQL指令插入到Web表單送出或輸入域名或頁面請求的查詢字元串,最終達到欺騙伺服器執行惡意的SQL指令。具體來說,它是利用現有應用程式,将(惡意的)SQL指令注入到背景資料庫引擎執行的能力,它可以通過在Web表單中輸入(惡意)SQL語句得到一個存在安全漏洞的網站上的資料庫,而不是按照設計者意圖去執行SQL語句。
上面是百度百科對SQL注入的介紹,通過上面的介紹可以知道,所謂的SQL注入就是輸入惡意SQL指令從資料庫擷取本不應該被擷取到的資料或者資訊。那麼,什麼樣的指令能夠達到這樣的目的呢?
在正式開始介紹SQL注入原理之前,需要先介紹兩個在SQL注入中被廣泛使用的指令符,那就是“ or 1 = 1” 和 “ # ”。" or 1 = 1 "使得篩選條件永真,進而使其失去意義以達到操作所有的目的,“ # ”是SQL中的注釋起始符,使得其後面的所有指令被注釋。
SQL注入原理以及效果示範
我的資料庫test中有一個student表,表中存儲的資料如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLxcDO2ADMwEjMzIDNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
如果程式中不采取防範措施,可以定義這樣一個方法:
public ResultSet select(String sno) {
String sql = "select Sno,Sname,Ssex,Sage,Sdept from student where Sno = '" + sno + "' limit 1";
System.out.println("the statement:" + sql);
try {
if(connection == null) {
connection = getConnection();
}
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
}catch(SQLException ex) {
System.out.println(ex.getMessage());
}
return resultSet;
}
這個方法的作用是通過傳入的sno參數,從資料庫表當中擷取某個指定學生的資訊。
如果使用者輸入正常,是如下效果:
public static void main(String[] args) {
DataBaseUtil util = new DataBaseUtil();
ResultSet set1 = util.select("001");
try {
while (set1.next()) {
System.out.print(set1.getString("Sno") + "\t");
System.out.print(set1.getString("Sname") + "\t");
System.out.print(set1.getString("Sage") + "\t");
System.out.print(set1.getString("Ssex") + "\t");
System.out.print(set1.getString("Sdept") + "\t");
System.out.println();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
the statement:select Sno,Sname,Ssex,Sage,Sdept from student where Sno = '001' limit 1
001 張華 23 男 軟體工程專業
但是,如果使用者輸入的資訊是經過特殊設計的,結果可能是這樣的:
public static void main(String[] args) {
DataBaseUtil util = new DataBaseUtil();
ResultSet set1 = util.select("000' or 1=1 #");
try {
while (set1.next()) {
System.out.print(set1.getString("Sno") + "\t");
System.out.print(set1.getString("Sname") + "\t");
System.out.print(set1.getString("Sage") + "\t");
System.out.print(set1.getString("Ssex") + "\t");
System.out.print(set1.getString("Sdept") + "\t");
System.out.println();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
the statement:select Sno,Sname,Ssex,Sage,Sdept from student where Sno = '000' or 1=1 #' limit 1
001 張華 23 男 軟體工程專業
002 李華 18 男 大資料科學與技術
003 耿耿 18 女 數字媒體與藝術
004 餘淮 18 男 大資料科學與技術
是的,沒錯,資料庫表中的所有資訊全部被查詢出來了,盡管表中沒有學号是000的學生,最後不僅是沒有查詢結果,反而所有資訊全部被顯示了。
原因:
使用者輸入的指令是 000' or 1=1 # ,而這條指令沒有經過任何處理,被直接拼接成為一條SQL語句,使得資料庫執行的語句成為 select Sno,Sname,Ssex,Sage,Sdept from student where Sno = '000' or 1=1 #' limit 1
原本根據學号用于篩選的where語句因為和 or 1 = 1 拼接,成為一個永真條件,在篩選中,永真條件相當于沒有,是以語句相當于 select Sno,Sname,Ssex,Sage,Sdept from student 。那麼,之前語句中用來将使用者輸入資訊包裝成字元類型的引号呢?用于限定隻能查詢一條資料的 limit 1 呢? 不要忘記文章起始時說過的 # 标記。 #' limit 1 ,這僅僅時一條注釋。
防範措施
既然有的使用者比較調皮,容易輸入一些不符合期望的内容,那麼,程式設計的時候,可以在接收使用者輸入的文本框做輸入限制。
但是,有的文本框的輸入内容并不是能夠指定的類型,或者有的使用者調皮到可以采用其他方式向背景送出一些什麼稀奇古怪的資料。這個時候可以使用PrepareStatement而不是Statement執行SQL指令。
Statement一般隻用于執行靜态SQL語句,PrepareStatement用于執行預編譯的動态SQL指令。
public ResultSet selectUsePre(String sno) {
String sql = "select Sno,Sname,Ssex,Sage,Sdept from student where Sno = ?";
try {
if(connection == null) {
connection = getConnection();
}
preStatement = connection.prepareStatement(sql);
preStatement.setString(1, sno);
System.out.println(preStatement);
resultSet = preStatement.executeQuery();
}catch(SQLException ex) {
System.out.println(ex.getMessage());
}
return resultSet;
}
public static void main(String[] args) {
DataBaseUtil util = new DataBaseUtil();
ResultSet set2 = util.selectUsePre("000' or 1=1 #");
try {
while (set2.next()) {
System.out.print(set2.getString("Sno") + "\t");
System.out.print(set2.getString("Sname") + "\t");
System.out.print(set2.getString("Sage") + "\t");
System.out.print(set2.getString("Ssex") + "\t");
System.out.print(set2.getString("Sdept") + "\t");
System.out.println();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
com.mysql.cj.jdbc.ClientPreparedStatement: select Sno,Sname,Ssex,Sage,Sdept from student where Sno = '000\' or 1=1 #'
PrepareStatement:
使用PrepareStatement,會将輸入的參數進行轉義,從一定程度上達到防止SQL注入的目的。
總結
程式設計中,對于動态SQL語句,不要使用字元串拼接的方式形成SQL指令并執行。如果條件允許,應該首先對使用者輸入進行判斷并使用預編譯。當然,上面僅僅是一個小小的案例示範,實際的SQL注入形式多種多樣而且破壞性往往很強。對SQL注入的預防也應該是全方位的,不能僅僅依靠輸入判斷或者預編譯。