如何保护JDBC应用程序免受SQL注入

总览

在关系数据库管理系统(RDBMS)中,有一种特定的语言(称为SQL(结构化查询语言))用于与数据库进行通信。用SQL编写的查询语句用于操纵数据库的内容和结构。创建和修改数据库结构的特定SQL语句称为DDL(数据定义语言)语句,而操作数据库内容的语句称为DML(数据操作语言)语句。与RDBMS包关联的引擎解析并解释SQL句,并相应地返回结果。这是与RDBMS进行通信的典型过程,仅执行一条SQL语句并取回结果即可。系统不会判断任何符合该语言的语法和语义结构的语句的意图。这也意味着没有验证或验证过程可以检查谁触发了该语句以及获得输出的特权。***者可以简单地以恶意意图触发SQL语句,并获取不应获取的信息。例如,***者可以使用具有无害外观的恶意负载执行SQL语句,并使用无害查询来控制Web应用程序的数据库服务器。抽丝剥茧 细说架构那些事——【优锐课】

怎么运行的?

***者可以利用此漏洞,并利用其自身的优势。例如,可以绕过应用程序的身份验证和授权机制,并从整个数据库中检索所谓的安全内容。SQL注入可用于创建,更新和删除数据库中的记录。因此,可以使用SQL来制定一个仅限于自己的想象力的查询。

通常,应用程序经常出于多种目的向数据库触发SQL查询,例如,用于获取某些记录,创建报告,验证用户身份,CRUD事务等等。***者只需要在某些应用程序输入表单中查找SQL输入查询即可。然后,可以使用表单准备的查询来缠绕恶意内容,以便在应用程序触发查询时,它也携带注入的有效负载。

理想情况之一是当应用程序要求用户输入诸如用户名或用户ID之类的内容时。该应用程序在那里打开了一个薄弱环节。SQL语句可以在不知不觉中运行。***者通过注入有效载荷来利用该有效载荷,该有效载荷将用作SQL查询的一部分并由数据库进行处理。例如,用于登录表单的POST操作的服务器端伪代码可能是:

uname = getRequestString("username");
pass = getRequestString("passwd");
 
stmtSQL = "SELECT * FROM users WHERE
   user_name = '" + uname + "' AND passwd = '" + pass + "'";
 
database.execute(stmtSQL);


前面的代码很容易受到SQL注入***的***,因为通过变量'uname''pass'SQL语句提供的输入可以通过改变语句语义的方式进行操作。

例如,我们可以像在MySQL中一样修改查询以使其针对数据库服务器运行。

stmtSQL = "SELECT * FROM users WHERE
   user_name = '" + uname + "' AND passwd = '" + pass + "' OR 1=1";


这导致将原始SQL语句修改为某种程度,以使其可以绕过身份验证。这是一个严重的漏洞,必须在代码中加以防止。

防范SQL注入***

减少SQL注入***机会的方法之一是确保在执行之前,不允许将未经过滤的文本字符串附加到SQL语句。例如,我们可以使用PreparedStatement执行所需的数据库任务。

PreparedStatement有趣的方面是,它将预编译的SQL语句发送到数据库,而不是字符串。这意味着查询和数据分别发送到数据库。这防止了SQL注入***的根本原因,因为在SQL注入中,这种想法是将代码和数据混合在一起,其中数据实际上是以数据为幌子的一部分。在PreparedStatement中,有多个setXYZ()方法,例如setString()。这些方法用于过滤特殊字符,例如SQL语句中包含的引号。

例如,我们可以通过以下方式执行SQL语句。

String sql = "SELECT * FROM employees WHERE emp_no = "+eno;


不用在输入中输入eno = 10125作为员工编号,我们可以使用输入修改查询,例如:

eno = 10125 OR 1=1


这完全改变了查询返回的结果。

举例

在以下示例代码中,我们展示了PreparedStatement如何可用于执行数据库任务。

package org.mano.example;
 
import java.sql.*;
import java.time.LocalDate;
public class App
{
   static final String JDBC_DRIVER =
      "com.mysql.cj.jdbc.Driver";
   static final String DB_URL =
      "jdbc:mysql://localhost:3306/employees";
   static final String USER = "root";
   static final String PASS = "secret";
   public static void main( String[] args )
   {
      String selectQuery = "SELECT * FROM employees
         WHERE emp_no = ?";
      String insertQuery = "INSERT INTO employees
         VALUES (?,?,?,?,?,?)";
      String deleteQuery = "DELETE FROM employees
         WHERE emp_no = ?";
      Connection connection = null;
      try {
         Class.forName(JDBC_DRIVER);
         connection = DriverManager.getConnection
            (DB_URL, USER, PASS);
      }catch(Exception ex) {
         ex.printStackTrace();
      }
      try(PreparedStatement pstmt =
            connection.prepareStatement(insertQuery);){
         pstmt.setInt(1,99);
         pstmt.setDate(2, Date.valueOf
            (LocalDate.of(1975,12,11)));
         pstmt.setString(3,"ABC");
         pstmt.setString(4,"XYZ");
         pstmt.setString(5,"M");
         pstmt.setDate(6,Date.valueOf(LocalDate.of(2011,1,1)));
         pstmt.executeUpdate();
         System.out.println("Record inserted successfully.");
      }catch(SQLException ex){
         ex.printStackTrace();
      }
      try(PreparedStatement pstmt =
            connection.prepareStatement(selectQuery);){
         pstmt.setInt(1,99);
         ResultSet rs = pstmt.executeQuery();
         while(rs.next()){
            System.out.println(rs.getString(3)+
               " "+rs.getString(4));
         }
      }catch(Exception ex){
         ex.printStackTrace();
      }
      try(PreparedStatement pstmt =
            connection.prepareStatement(deleteQuery);){
         pstmt.setInt(1,99);
         pstmt.executeUpdate();
         System.out.println("Record deleted
            successfully.");
      }catch(SQLException ex){
         ex.printStackTrace();
      }
      try{
         connection.close();
      }catch(Exception ex){
         ex.printStackTrace();
      }
   }
}


A Glimpse into PreparedStatement

这些作业也可以通过JDBC语句接口来完成,但是问题在于它有时可能会非常不安全,尤其是当执行动态SQL语句来查询将用户输入值与SQL查询连接在一起的数据库时。如我们所见,这可能是危险的情况。在大多数情况下,Statement是相当无害的,但PreparedStatement似乎是两者之间更好的选择,由于防止将恶意字符串发送到数据库的方式不同,因此它可以防止串联恶意字符串。PreparedStatement使用变量替换而不是串联。在SQL查询中放置问号(?)表示替换变量将代替其位置并在执行查询时提供值。替换变量的位置根据setXYZ()方法中分配的参数索引位置来代替。

此技术可以防止它受到SQL注入***。

此外,PreparedStatement实现了AutoCloseable。这使它可以在try-with-resources块的上下文中进行写入,并在超出范围时自动关闭。

结论

只有负责任地编写代码,才能防止SQL注入***。实际上,在任何软件解决方案中,大多数安全性都是由于不良的编码做法而被破坏的。在这里,我们描述了应避免的事项以及PreparedStatement如何帮助我们编写安全代码。有关SQL注入的完整想法,请参考适当的材料;有关SQL注入的详细信息,请参见。Internet上充满了它们,对于PreparedStatement,请查阅Java API文档以获取更详细的说明。

欢迎留言或私信探讨学习~

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章