📜  使用准备好的语句(参数化查询)减轻SQL注入攻击

📅  最后修改于: 2021-04-17 01:32:52             🧑  作者: Mango

如本文所述,SQL注入攻击或SQLi是通过在其输入字段中插入恶意的SQL语句来执行来利用SQL语句的潜在漏洞的一种方法。它于1998年首次亮相,从那时起,它主要针对零售商和银行帐户。与其他形式的攻击(例如DDOS攻击,跨站点脚本(XSS)或DNS劫持)一起使用时,它可能导致大规模的结果。

术语:

  • 验证:验证是检查输入是否满足一组条件的过程(例如,字符串包含独立的单引号)。
  • 清理:清理是修改输入以确保输入有效的过程(例如将单引号加倍)。

为了避免SQL注入,必须正确过滤和清除要在动态SQL中串联的所有输入。

SQL攻击的剖析:
SQL攻击分为以下两个部分:

  • 研究:查看与数据库连接的用户端应用程序的易受攻击的部分。
  • 攻击:输入恶意字段,这些恶意字段可以使查询变型,从而发挥自己的优势。

否_WAF

范例1:
考虑以下用Java编写的身份验证表单的代码:

String query = "SELECT userName, balance FROM accounts" 
    + "WHERE userID=" + request.getParameter("userID") + 
  "and password='" + request.getParameter("Password") + "'";
  
try
{
    Statement statement = connection.createStatement();
    ResultSet rs = statement.executeQuery(query);
    while (rs.next()) 
    {
        page.addTableRow(rs.getString("userName"), 
                        rs.getFloat("balance"));
    }
} 
catch (SQLException e) 
    {}

在正常情况下,用户输入其用户名和密码,这将生成以下语句以供执行:

SELECT userName, balance 
FROM accounts 
WHERE userID=512 and password='thisisyoda'

可能的SQL注入攻击将利用password字段生成一个布尔表达式,该布尔表达式将使该表达式在所有情况下均评估为true。想象将userID和password字段设置为

userID = 1' or '1' = '1
password = 1' or '1' = '1

然后,SQL语句变为

SELECT userName, balance 
FROM accounts 
WHERE userID='1' OR '1'='1' and 
      password='1' OR '1'='1'

查询将返回一个值,因为条件(OR 1 = 1)始终为true。这样,系统就在不知道用户名和密码的情况下对用户进行了身份验证。

可以使用以下准备的语句来创建参数化查询来缓解此漏洞:

String query = "SELECT userName, balance "+
               "FROM accounts WHERE userID = ? 
                and password = ?";
  
try {
  PreparedStatement statement = connection.prepareStatement(query);
  statement.setInt(1, request.getParameter("userID")); 
  ResultSet rs = statement.executeQuery();
  while (rs.next()) 
  {
    page.addTableRow(rs.getString("userName"), 
                     rs.getFloat("balance"));
  }
} catch (SQLException e)
       { ... }

如果攻击者试图向userID字段提供一个非简单整数的值,则statement.setInt()将抛出SQLException错误,而不是允许查询完成。

范例2
考虑身份验证期间的另一种攻击类型:

String query = "SELECT userID, userName, passwordHash"+
               " FROM users WHERE userName = '" 
               + request.getParameter("user") + "'";
int userID = -1;
HashMap userGroups = new HashMap();
try 
{
  Satement statement = connection.createStatement();
  ResultSet rs = statement.executeQuery(query);
  rs.first();
  userID = rs.getInt("userID");
      
  if (!hashOf(request.getParameter("password")).equals(rs.getString("passwordHash"))) 
  {
    throw BadLoginException();
  }
  
  String userGroupQuery = "SELECT group FROM groupMembership"+
                         " WHERE userID = " + userID;
      
  rs = statement.executeQuery(userGroupQuery);
      
  while (rs.next()) 
  {
    userGroup.put(rs.getString("group"), true);
  }
} 
catch (SQLException e){}
catch (BadLoginException e){}

常规查询如下。

SELECT userID, userName, passwordHash 
FROM users 
WHERE userName = 'Anannya'

攻击者可能会将以下内容注入到userName字段中。

Anannya';
INSERT INTO groupMmbership (userID, group) 
VALUES (SELECT userID FROM users
WHERE userName='Anannya', 'Administrator'); --

因此,实际查询将更改为:

SELECT userID, userName, passwordHash FROM 
       users WHERE userName = 'Anannya';
INSERT INTO groupMmbership (userID, group) 
VALUES (SELECT userID FROM users 
WHERE userName='Anannya', 'Administrator'); --'

这将导致另一个SQL语句被附加到实际语句中,从而导致用户被添加到Administrator数据库中。可以通过将准备好的语句与参数化查询一起使用来缓解这种攻击,如下所示。

String query = "SELECT userID, userName, passwordHash"+
               " FROM users WHERE userName = ?";
  
try 
{
  PreparedSatement statement = 
         connection.prepareStatement(userLoginQuery);
  statement.setString(1, request.getParameter("user"));
  ResultSet rs = statement.executeQuery();
} 

例子3
考虑下面讨论的查询漏洞的另一个示例:

String query = "INSERT INTO users VALUES(" + 
        request.getParameter("userName") + ");";

一般查询将是:

INSERT INTO users VALUES("Anannya")

考虑攻击者是否在userName字段中输入以下查询:

"Anannya); DROP TABLE users;"

查询将更改为:

INSERT INTO users VALUES("Anannya"); DROP TABLE users;

该查询在执行后会完全删除用户表。同样,这里的解决方法是准备好的语句。

在Java中使用预处理语句有什么帮助?

准备好的语句“清除”了输入。这意味着它确保将用户输入的任何内容都视为SQL中的字符串字面量,而不是SQL查询的一部分。它还可能会转义某些字符并检测/删除恶意代码。在其他语言(例如PHP,可以使用filter_input或filter_input_array清理字符串。