SQL注入(SQL Injection)是一种代码注入技术,攻击者通过在用户可控的输入位置插入SQL代码,使应用程序执行非预期的SQL语句,从而破坏原有的SQL语句语义。当应用程序将这些带有恶意SQL代码的输入直接拼接到SQL查询语句中执行时,就会导致SQL注入漏洞。
简单来说就是传入的恶意参数被当做sql语句执行了
SQL注入的根本原因是应用程序没有正确区分代码和数据。当用户输入被直接拼接到SQL语句中时,输入中的特殊字符可能会改变SQL语句的结构和语义,导致非预期的执行结果。
示例
// 不安全的代码
$query = "SELECT * FROM users WHERE username = '" . $_POST['username'] . "' AND password = '" . $_POST['password'] . "'";
如果用户输入:'OR '1'='1
,最终SQL会变成:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '...'
这就可以实现一个任意密码登录
讲完的原理再来讲下类型
通过union拼接查询语句获取大量的信息
利用:模糊查询猜测列名、表名---->union查询构造select语句进行查询返回大量数据
利用系统返回的错误信息来获取有用的信息
需要用到的函数:updataxml
、floor
向下取整、group by
分组排列、count
统计数量、concat
连接字符串updatexml
, 0x7e
等价于 ~
,将查询语句和特殊符号拼接在一起,就可以将查询结果显示在报错信息中
kobe%27%20and%20 ascii ( substr(database(),§1§,1))=§115§--+
构造语句,通过页面响应时长来判断信息。
例如:id=1 and if(length((select database()))>1,sleep(3),1)--+
sleep被禁用 使用 get_lock
,heavy_query
,benchmark
使用gbk编码, %df
与 /
组成了一个汉字 `綅,使得转义字符不起作用,单引号逃脱
特殊字符进行了转义处理,在从数据库中取脏数据,发生二次注入,比如同时注册两个账admin,admin’,然后修改admin’密码成功把admin密码修改了
Dns在域名解析时会留下解析记录,利用 load_file()
函数发起请求,使用Dnslog接受请求,可以获取数据。
如
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
黑名单:对特殊的字符例如括号斜杠进行转义过滤删除;
白名单:对用户的输入进行正则表达式匹配限制
主要是防止写webshell
漏洞产生的根本原因都是人的原因。
只有提高人员的安全意识才能从根本上杜绝漏洞
预编译语句(Prepared Statements)是一种数据库功能,它将SQL语句的编译与执行分为两个独立阶段:
1.准备阶段:
应用程序:发送带占位符的SQL模板(不含实际数据)
数据库:分析SQL结构并生成执行计划
2.执行阶段:
应用程序:只发送参数值
数据库:将值填入预先准备好的执行计划并执行
预编译能防SQL注入的核心原理在于SQL语句结构与用户输入数据的分离:
除安全性外,预编译还具有性能优势:
PDO::ATTR_EMULATE_PREPARES
配置是否启用模拟预编译
真预编译是指由数据库服务器真正执行SQL语句的预处理:
模拟预编译 没有参数绑定 预编译的过程,只是对符号做了过滤 转义
特点:
准备阶段
客户端 ──[SQL模板]──> 数据库服务器
↓
解析SQL、生成语法树、优化、生成执行计划
↓
客户端 <──[statement_id]── 数据库服务器
执行阶段
客户端 ──[statement_id + 参数值]──> 数据库服务器
↓
将参数值填入预编译语句的执行计划
↓
客户端 <──[查询结果]── 数据库服务器
模拟预编译是在客户端(应用程序)模拟预编译过程:
特点:
区别与选择
特性 | 真预编译 | 模拟预编译 |
---|---|---|
防SQL注入效果 | 更可靠 | 依赖转义正确性 |
处理多语句攻击 | 有效防御 | 可能有漏洞 |
性能(大量查询) | 更优 | 较差 |
性能(单次查询) | 可能略差 | 可能略好 |
服务器负载 | 较高 | 较低 |
兼容性 | 依赖数据库支持 | 更广泛 |
PDO(PHP Data Objects)
// 默认为模拟预编译
$pdo = new PDO("mysql:host=localhost;dbname=test", "user", "password");
// 启用真预编译
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
// 使用预编译
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$userId]);
PDO::ATTR_EMULATE_PREPARES
为false启用真预编译MySQLi
$mysqli = new mysqli("localhost", "user", "password", "database");
// 使用预编译
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
JDBC
// 使用PreparedStatement
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE)
等参数调整行为Hibernate/JPA
// 使用参数化查询
String hql = "FROM User WHERE username = :username AND password = :password";
Query query = session.createQuery(hql);
query.setParameter("username", username);
query.setParameter("password", password);
List<User> result = query.list();
MySQL Connector
import mysql.connector
conn = mysql.connector.connect(user='user', password='password', database='testdb')
cursor = conn.cursor(prepared=True) # 启用预编译支持
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
psycopg2 (PostgreSQL)
import psycopg2
conn = psycopg2.connect("dbname=test user=user password=password")
cur = conn.cursor()
cur.execute("SELECT * FROM users WHERE username = %s AND password = %s",
(username, password))
SQLAlchemy
from sqlalchemy import text
# 使用参数化查询
stmt = text("SELECT * FROM users WHERE username = :username AND password = :password")
result = conn.execute(stmt, username=username, password=password)
Node.js (mysql2)
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'user',
password: 'password',
database: 'test'
});
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
connection.execute(query, [username, password], (err, results) => {
// 处理结果
});
C# (ADO.NET)
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand("SELECT * FROM users WHERE username = @Username", connection);
command.Parameters.AddWithValue("@Username", username);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
// 处理结果
}