文章目录
SQL注入简单原理
是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
预编译原理
将sql语句预先编译一次,后面用户输入的数据将参数化执行,不会再编译一次,更安全。
常见后端语言的预编译函数
PHP
在PHP中,PDO(PHP Data Objects)扩展提供了预编译语句的功能。PDOStatement对象表示一条预编译的SQL语句,可以通过PDO对象的prepare()方法创建。
php
<?php
try {
// 创建PDO实例
$pdo = new PDO("mysql:host=localhost;dbname=database_name", "username", "password");
// 预编译SQL语句
$stmt = $pdo->prepare("SELECT * FROM employees WHERE age > :age");
// 绑定参数值
$stmt->bindParam(':age', $age, PDO::PARAM_INT);
$age = 30;
// 执行查询并获取结果集
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 处理结果集
foreach ($results as $row) {
echo $row['name'] . "\n";
}
} catch (PDOException $e) {
echo "Error: " . $e->getMessage();
}
?>
JAVA
在Java中,JDBC(Java Database Connectivity)提供了预编译语句(PreparedStatement)的功能。PreparedStatement是Statement的一个子接口,它代表了一条预编译的SQL语句。使用PreparedStatement可以显著提高数据库操作的性能,并防止SQL注入攻击。
java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class PrecompiledExample {
public static void main(String[] args) {
try {
// 创建数据库连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/database_name", "username", "password");
// 创建预编译语句
String sql = "SELECT * FROM employees WHERE age > ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置参数值
preparedStatement.setInt(1, 30);
// 执行查询并获取结果集
ResultSet resultSet = preparedStatement.executeQuery();
// 处理结果集
while (resultSet.next()) {
// 处理每一行数据
System.out.println(resultSet.getString("name"));
}
// 关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Python
在Python中,常见的数据库操作库如MySQL Connector/Python提供了预编译功能。这通常通过prepare()方法实现,该方法会解析和优化SQL语句,并将其保存在数据库中以供后续使用。
python
import mysql.connector
# 创建数据库连接
cnx = mysql.connector.connect(user='username', password='password', host='localhost', database='database_name')
# 创建游标对象
cursor = cnx.cursor()
# 预编译SQL语句
stmt = cursor.prepare("SELECT * FROM employees WHERE age > %s")
# 执行预编译的SQL语句,并传入参数
stmt.execute((30,))
# 获取查询结果
results = stmt.fetchall()
# 输出查询结果
for row in results:
print(row)
# 关闭游标和连接
cursor.close()
cnx.close()
预编译的安全性
预编译可以有效防止sq注入,主要原理是将sql语句预先编译一次,用户无论输入什么都只会被当成参数执行,而不会将sql语句闭合,构造新的sql语句攻击。
例外
预编译不能完全防止sql注入攻击,追究其根本原因是有些地方的sql语句无法被参数化,就算预编译后,仍存在能被闭合的地方。参考一下微信这位作者的文章。
预编译真的能完美防御SQL注入吗?
看完作者的描述,我们知道了无法被参数化的地方只有几种
- 表名、列名
- order by、group by
- limit
- join
- ...
探讨一下这些地方为什么不能被参数化,这里在网上冲浪的时候读到了另外一片作者的文章,可以很好的解释一下为什么这些地方不能被参数化。
PDO预编译与sql注入
在作者的文章中可以看出,例如order by语句中,预编译会自动给你追加引号,把你的输入给参数化。而order by这条语句如果把参数自动追加引号的话就会造成语法错误,查询出错。所以这个地方无法被预编译,或者说是假预编译。
作者在文章中有一句话说的很好:没有参数绑定的预编译等于没有预编译。
(题外话)为啥预编译会出现这种例外呢?
开发之初预编译可能就不是让你用作保护数据库安全的一种手段,更多的,预编译是一种减少数据库查询时间,减少语法树构成的一种方法。顺带的只是我们认为预编译通过把sql语句参数化执行可以有效的防止sql注入,所以才把预编译拿上来说。