0%

sql注入的一丁点认识

预编译是怎么防御sql注入的

SQL 注入成功需要两点:

  1. 用户输入被拼接进 SQL 字符串
  2. 数据库把用户输入当成“SQL 语法的一部分”去解析

SQL 注入的本质是“控制 Parser”,而 ? 让 Parser 根本看不到用户输入。

预编译

查询分为parser和excute两次通信,第一次通信就解析了语法树,第二次通信直接进行了参数化查询

预编译的安全性来自三件事的协同:

1️⃣ **数据库协议支持「结构与参数分离」
**2️⃣ **数据库内核支持「预解析 + 执行计划缓存」
**3️⃣ 客户端 API(JDBC)暴露了这个能力

PREPARE -> 发送 SQL 结构

EXECUTE -> 发送参数值

层级 负责什么
数据库协议 定义“结构包 / 参数包”分离
数据库 Parser **?** 识别为 PARAM 节点
执行引擎 只允许参数参与值比较
JDBC / Driver 正确使用协议
ORM 默认帮你走这条路

数据库在 prepare 阶段已经数过:

ps.setString(1, username);

ps.setInt(2, age);

就是告诉驱动:

把这个值,绑定到 AST 里的 PARAM(1)

img

img

一个实现了能防sql注入的预编译,一个没有实现,框架白写,在语法paser前就已经写进去了

sql注入就是在执行一段sql语句,本质与语言无关

宽字节注入

宽字节注入成立的前提是:
应用层用“转义”而不是“参数绑定”, (让用户输入进入 SQL Parser)
且数据库在解析 SQL 时会按多字节字符集重新解码字节流, 攻击目标是转义字符(\)本身
从而把转义字符吞进字符,导致语法边界失效。 ( 数据库会“重新按字符集解码”SQL 字节流 )

img

宽字节注入依赖“某些多字节编码允许反斜杠成为字符的一部分”

审计小技巧

GBK / Big5 符合这个条件
UTF-8 不符合

查看数据库字符集连接文件,看它是否是utf-8

HQL注入

img

Hibernate是一种orm框架,我们的输入在进入之后作为HQL的一部分,渲染成sql语句,然后再进入数据库层进行查询,但就是因为这么一手渲染,防住了很多攻击,因为我们构造的语句需要在渲染成sql语句之后还能造成注入攻击才行

将数据库tables映射为相关的类,通过此类进行数据查询,使用的是HQL自己的语言

1
2
3
String parameter = req.getParameter("name");
Query query = session.createQuery("from com.demo.bean.User where tableschema = ?1", User.class);
query.setParameter(1, parameter);

为啥会产生sql注入

开发者误用

$sql = “SELECT * FROM user WHERE name = ‘$name’”;

$stmt = $conn->prepare($sql);

在prepare之前,$name参数就已经拼接进去了,后边纯鸟用

无法预编译的输入点

like

在数据库查询中, like后边加 “%”.$username.”%” 这个百分号表示匹配,匹配包含里边的字符串

参数绑定只做一件事:

把一个“值”,安全地放到 SQL 已经确定的结构里

然而like语句 “模糊语义”不属于参数绑定能自动完成的事情。 数据库并不知道你要模糊到哪里

参数绑定是写死进去的直接参数绑定

比方说

1
2
3
like a         也就是=a
like %a 匹配a,前面随便
like %a% 包含a

正确且安全的只有这个

1
2
3
LIKE ?
"%keyword%"
LIKE CONCAT('%', ?, '%')

由于mybaits框架面对这个的话,需要开发者手动去进行预编译,手动预编译就可能存在手残没写好的情况

预编译没正确使用啊,过滤有问题啊之类的

1
2
3
4
$stmt = $conn->prepare("select * from fraction where name like '%:username%'"); 不行
select * from fraction where name like concat('%',:username,'%')
# 这种情况下就能够进行在使用预编译的情况下进行like模糊查询
预编译始终只绑定值,不绑定语法结构。所以第一个不行

不能加‘的关键字

select $username$,password from $table$ where $username2$ = ‘$dada$order by $username3$ $desc$ limit $0$,1

总的来说就是

表名、列名、limit子句、order by[desc/asc]

产生sql注入的原因:

在语法结构里,预编译只能预编译值,不能搞这些结构

SELECT ‘username’ FROM ‘users’; – ❌ 错误

SELECT username FROM users; – ✅ 正确 加引号就会报错

例子

1
2
3
$table = "users; DROP TABLE users;--"
SELECT id FROM $table
//拼接不严谨

防御

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
三、如何安全处理这些结构

白名单验证(最常用)

对表名、列名、排序字段、排序方式、LIMIT 数字,先检查是否在允许列表里

例:

$allowedCols = ['id','username','score'];
$orderCol = in_array($_GET['col'], $allowedCols) ? $_GET['col'] : 'id';

$allowedDir = ['asc','desc'];
$orderDir = in_array(strtolower($_GET['dir']), $allowedDir) ? $_GET['dir'] : 'asc';


强制类型转换

LIMIT / OFFSET 必须是整数:

$limit = (int)$_GET['limit'];
$offset = (int)$_GET['offset'];



完全避免动态表名/列名拼接

如果真的必须动态,使用 白名单 + 映射
$columnMap = [
'id' => 'id',
'name' => 'username',
'age' => 'age'
];
$userInput = $_GET['col']; // 用户传:id / name / age / 恶意payload

if (!isset($columnMap[$userInput])) {
die('invalid column');
}

$orderCol = $columnMap[$userInput];


$dirMap = [
'asc' => 'ASC',
'desc' => 'DESC'
];

$userDir = strtolower($_GET['dir']);

$orderDir = $dirMap[$userDir] ?? 'ASC';
dir = desc; union select password from users;--




绝不允许直接用用户输入拼接结构部分