跳到内容

查询参数化速查表

简介

SQL 注入是最危险的网络漏洞之一。它曾是 OWASP Top 10 2013 年版本2017 年版本中的头号项目。截至 2021 年,它位列 OWASP Top 10 的第三位。

它构成严重威胁,因为 SQL 注入允许恶意攻击者代码改变 Web 应用程序 SQL 语句的结构,从而窃取数据、修改数据,或可能促成对底层操作系统的命令注入。

本速查表是 SQL 注入防御速查表的衍生作品。

参数化查询示例

SQL 注入的最佳防御方式是使用 参数化查询。下表通过实际代码示例,演示了如何在大多数常见的 Web 语言中构建参数化查询。这些代码示例的目的是向 Web 开发人员展示在 Web 应用程序中构建数据库查询时如何避免 SQL 注入。

请注意,许多客户端框架和库提供客户端查询参数化。这些库通常只是在将原始查询发送到服务器之前,通过字符串拼接构建查询。请务必确保查询参数化是在服务器端完成的!

预处理语句示例

使用 Java 内置功能

String custname = request.getParameter("customerName");
String query = "SELECT account_balance FROM user_data WHERE user_name = ? ";  
PreparedStatement pstmt = connection.prepareStatement( query );
pstmt.setString( 1, custname);
ResultSet results = pstmt.executeQuery( );

在 Java 中使用 Hibernate

// HQL
@Entity // declare as entity;
@NamedQuery(
 name="findByDescription",
 query="FROM Inventory i WHERE i.productDescription = :productDescription"
)
public class Inventory implements Serializable {
 @Id
 private long id;
 private String productDescription;
}

// Use case
// This should REALLY be validated too
String userSuppliedParameter = request.getParameter("Product-Description");
// Perform input validation to detect attacks
List<Inventory> list =
 session.getNamedQuery("findByDescription")
 .setParameter("productDescription", userSuppliedParameter).list();

// Criteria API
// This should REALLY be validated too
String userSuppliedParameter = request.getParameter("Product-Description");
// Perform input validation to detect attacks
Inventory inv = (Inventory) session.createCriteria(Inventory.class).add
(Restrictions.eq("productDescription", userSuppliedParameter)).uniqueResult();

使用 .NET 内置功能

String query = "SELECT account_balance FROM user_data WHERE user_name = ?";
try {
   OleDbCommand command = new OleDbCommand(query, connection);
   command.Parameters.Add(new OleDbParameter("customerName", CustomerName Name.Text));
   OleDbDataReader reader = command.ExecuteReader();
   // …
} catch (OleDbException se) {
   // error handling
}

使用 ASP .NET 内置功能

string sql = "SELECT * FROM Customers WHERE CustomerId = @CustomerId";
SqlCommand command = new SqlCommand(sql);
command.Parameters.Add(new SqlParameter("@CustomerId", System.Data.SqlDbType.Int));
command.Parameters["@CustomerId"].Value = 1;

在 Ruby 中使用 ActiveRecord

## Create
Project.create!(:name => 'owasp')
## Read
Project.all(:conditions => "name = ?", name)
Project.all(:conditions => { :name => name })
Project.where("name = :name", :name => name)
## Update
project.update_attributes(:name => 'owasp')
## Delete
Project.delete(:name => 'name')

使用 Ruby 内置功能

insert_new_user = db.prepare "INSERT INTO users (name, age, gender) VALUES (?, ? ,?)"
insert_new_user.execute 'aizatto', '20', 'male'

在 PHP 中使用 PHP Data Objects

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);

使用 Cold Fusion 内置功能

<cfquery name = "getFirst" dataSource = "cfsnippets">
    SELECT * FROM #strDatabasePrefix#_courses WHERE intCourseID =
    <cfqueryparam value = #intCourseID# CFSQLType = "CF_SQL_INTEGER">
</cfquery>

在 PERL 中使用数据库独立接口

my $sql = "INSERT INTO foo (bar, baz) VALUES ( ?, ? )";
my $sth = $dbh->prepare( $sql );
$sth->execute( $bar, $baz );

在 Rust 中使用 SQLx

// Input from CLI args but could be anything
let username = std::env::args().last().unwrap();

// Using build-in macros (compile time checks)
let users = sqlx::query_as!(
        User,
        "SELECT * FROM users WHERE name = ?",
        username
    )
    .fetch_all(&pool)
    .await 
    .unwrap();

// Using built-in functions
let users: Vec<User> = sqlx::query_as::<_, User>(
        "SELECT * FROM users WHERE name = ?"
    )
    .bind(&username)
    .fetch_all(&pool)
    .await
    .unwrap();

存储过程示例

您在 Web 应用程序中编写的 SQL 并不是唯一可能引入 SQL 注入漏洞的地方。如果您使用存储过程并在其中动态构造 SQL,同样会引入 SQL 注入漏洞。

动态 SQL 可以使用绑定变量进行参数化,以确保动态构造的 SQL 是安全的。

以下是在不同数据库的存储过程中使用绑定变量的一些示例。

Oracle 使用 PL/SQL

普通存储过程

没有动态 SQL 被创建。传递给存储过程的参数自然会绑定到查询中的相应位置,无需任何特殊操作。

PROCEDURE SafeGetBalanceQuery(UserID varchar, Dept varchar) AS BEGIN
   SELECT balance FROM accounts_table WHERE user_ID = UserID AND department = Dept;
END;
使用绑定变量在 EXECUTE 中运行的 SQL 存储过程

绑定变量用于告诉数据库,传递给此动态 SQL 的输入是“数据”,而非可能的代码。

PROCEDURE AnotherSafeGetBalanceQuery(UserID varchar, Dept varchar)
          AS stmt VARCHAR(400); result NUMBER;
BEGIN
   stmt := 'SELECT balance FROM accounts_table WHERE user_ID = :1
            AND department = :2';
   EXECUTE IMMEDIATE stmt INTO result USING UserID, Dept;
   RETURN result;
END;

SQL Server 使用 Transact-SQL

普通存储过程

没有动态 SQL 被创建。传递给存储过程的参数自然会绑定到查询中的相应位置,无需任何特殊操作。

PROCEDURE SafeGetBalanceQuery(@UserID varchar(20), @Dept varchar(10)) AS BEGIN
   SELECT balance FROM accounts_table WHERE user_ID = @UserID AND department = @Dept
END
使用绑定变量在 EXEC 中运行的 SQL 存储过程

绑定变量用于告诉数据库,传递给此动态 SQL 的输入是“数据”,而非可能的代码。

PROCEDURE SafeGetBalanceQuery(@UserID varchar(20), @Dept varchar(10)) AS BEGIN
   DECLARE @sql VARCHAR(200)
   SELECT @sql = 'SELECT balance FROM accounts_table WHERE '
                 + 'user_ID = @UID AND department = @DPT'
   EXEC sp_executesql @sql,
                      '@UID VARCHAR(20), @DPT VARCHAR(10)',
                      @UID=@UserID, @DPT=@Dept
END

参考资料