第3章.SQL注入與防範
SQL注入與防範
經常遇到的問題:資料安全問題,尤其是sql注入導致的資料庫的安全漏洞
國內著名漏洞曝光平臺:WooYun.org
資料庫洩露的風險:使用者資訊、交易資訊的洩露等
什麼是SQL資料庫注入?
web應用下,終端使用者是無法直接訪問資料庫的,他們必須通過傳送http請求到Java伺服器,由Java伺服器訪問後端伺服器。因此,惡意使用者想要獲取資料庫中的資料,必須通過Java伺服器來訪問後端資料庫而無法繞行。他們的唯一途徑就是利用應用程式的漏洞,偽裝自己的請求,欺騙業務程式,達到訪問資料庫的目的。
之前學習的程式碼:使用者登入場景
User user = null; String sql = "select * from user where userName = '" + userName + "' and password = '" + password + "'"; rs = stmt.executeQuery(sql); while(rs.next()) { user = new User(); user.setUserName(rs.getString("userName")); user.setCardNum(rs.getString("cardNum")); } return user;
Java應用程式接收到使用者的輸入後,檢索後端資料庫,匹配資料庫記錄。
如果:
使用者輸入為“Zhangsan’;-- ” 密碼隨便輸入(因為不知道真實密碼)
也會成功登入,為什麼呢?
select * from user where userName = ‘ZhangSan’;-- ‘ and password = ‘111’;
密碼條件被註釋
總結:資料庫注入就是使用者在輸入部分輸入SQL語句,破壞原有SQL的語義,以達到欺騙伺服器併發送惡意SQL的業務程式的漏洞。
原因:SQL語句為動態拼接的,語義未知。
解決方案:除了動態拼接,還有什麼辦法能使用web傳入的引數呢?
引數化SQL的試下:1、確定SQL的含義;2、傳入引數
利用Connection物件的.preparedStatement(sql)方法;
preparedStatement ()相對Statement的最大優點:提供了引數化SQL的試下(格式化的SQL)
select * from user where userName = ? and password = ?
?為佔位符,替代了一個引數。SQL的語義確定了。
根據引數的型別:.setInt(); .setString(); .setBoolean(); 來替代引數化SQL中的佔位符
除了使用PreparedStatement,我們還應該有一些其他的注意事項:
- 執行嚴格的資料庫許可權管理
僅授予web應用訪問資料庫的最小許可權
嚴格禁止Drop table等許可權
- 封裝資料庫錯誤:不能將資料庫異常直接暴露給使用者(因為資料庫異常經常包含了大量的資料庫資訊)
禁止直接將後端資料庫異常資訊暴露給客戶
對後端異常進行必要的封裝,避免使用者直接檢視到後端異常
- 機密資訊禁止明文儲存
涉密資訊需要加密處理
針對MySQL,可使用AES_ENCRYPT/AES_DECRYPT進行加密和解密
課堂交流區:
其他的SQL注入場景:可以使用 “' or 1;#” 或者 “' or 1;-- ”
i.e. select name from student where name =” ' or 1;#”;
會返回所有。
在通過某個欄位查詢資料表時,如果SQL語句本身是拼接而成的,並且程式中沒有對欄位資料進行有效性驗證,則有暴露使用者資訊和業務資訊的可能。如下面的程式,可以查詢出資料庫中所有使用者的資料資訊。String userName = "anystring' or '1=1";
String sql = "select * from user where userName='" + userName +"'";
作業:有一張學生表
現在需要根據學生名稱獲取學生的期末考試分數。
public static void getStudent(String name) throws ClassNotFoundException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
Class.forName(JDBC_DRIVER);
conn = DriverManager.getConnection(DB_URL, USER, PASS);
stmt = conn.createStatement();
rs = stmt.executeQuery("select name,score from student where name =' " + name +"'");
while (rs.next()) {
System.out.println(rs.getString("name") + ":" + rs.getInt("score"));
}
} catch (SQLException e) {
// ignore
} finally {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
// ignore
}
}
if (stmt != null) {
try {
stmt.close();
} catch (Exception e) {
// ignore
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
}
請重新編寫應用程式,解決上述風險。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class sql {
static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study";
static final String USER = "root";
static final String PASSWORD = "xjj5211314";
public static void getStudent(String name) throws ClassNotFoundException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
Class.forName(JDBC_DRIVER);
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
stmt = conn.createStatement();
rs = stmt.executeQuery("select * from user where userName = '" + name + "';");
while (rs.next()) {
System.out.println(rs.getString("userName") + ":" + rs.getInt("number"));
}
} catch (SQLException e) {
// ignore
} finally {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
// ignore
}
}
if (stmt != null) {
try {
stmt.close();
} catch (Exception e) {
// ignore
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
}
public static void main(String[] args) {
String name = "' or 1;-- ";
try {
getStudent(name); //使用了這個變數,會吧所有使用者搜出來,所以應該用PreparedStatement替代Statement
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}