1. 程式人生 > >第3章.SQL注入與防範

第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,我們還應該有一些其他的注意事項:

  1. 執行嚴格的資料庫許可權管理

僅授予web應用訪問資料庫的最小許可權

嚴格禁止Drop table等許可權

  1. 封裝資料庫錯誤:不能將資料庫異常直接暴露給使用者(因為資料庫異常經常包含了大量的資料庫資訊)

禁止直接將後端資料庫異常資訊暴露給客戶

對後端異常進行必要的封裝,避免使用者直接檢視到後端異常

  1. 機密資訊禁止明文儲存

涉密資訊需要加密處理

針對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();
		}

	}

}