Sql注入基础知识补全

这篇文章是 SQL 注入入门实验记录 的配套基础补全。 如果你在看那篇实验记录的时候,对"地址栏里那串东西到底是什么","+ 和 -- 到底干嘛用的"感到困惑,就从这里开始读起吧

我总结了以下几个阶段,可以结合自己自身的情况,选择性的学习

使用的示例坏境是Linux虚拟机+Docker部署本地靶场这一篇博客里,我所示例搭建的pikachu靶场,本篇用pikachu靶场介绍数字型和字符型注入,实验记录那篇用的是另一个靶场,原理是一样的

pikachu靶场帮助手册:点我下载    密码:hqne

第一阶段:数据库到底是什么?

你可以把数据库想象成一个Excel工作簿

Pikachu里的用户数据大概长这样

表名:users
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | admin    | 123456   |
|  2 | test     | abc123   |
+----+----------+----------+

一个正常的SQL查询是这样执行的:

SELECT username, password FROM users WHERE id = 1;

翻译成人话就是:"从users表里,找id等于1的那行,把username和password给我"

第二阶段:注入的根源——为什么字符串拼接很危险?

现在来看Pikachu后台PHP代码大概是怎么写的(简化版):

$id = $_POST['id'];  // 从前端表单取值,比如用户选了 "1"
$sql = "SELECT * FROM users WHERE id = " . $id;
// 拼接结果:"SELECT * FROM users WHERE id = 1"

这看起来没问题对吧?

但是往往问题就出现在这里,如果用户不老实,输入的不是 1,而是 1 or 1=1

$sql = "SELECT * FROM users WHERE id = " . "1 or 1=1";
// 拼接结果:"SELECT * FROM users WHERE id = 1 or 1=1"

这句SQL的含义变成了:"找id=1的,或者……1=1(永远为真)的数据"

结果:把整张表的数据都返回了

原理解释:

因为 1=1 永远为真,导致 WHERE 条件对每一行都成立,

而数据库会对每一行判断:

  • 不管这一行的 id 是多少
  • 1=1 都是 true
  • true OR anything = true

✔ 所以每一行都满足条件
✔ 最终返回整张表

第三阶段:注入点判断——先"试探"(确立数据库的“控制权”)

打开我们装的pikachu靶场,和burp suite工具(如果你还没有安装burp suit,那我建议去看看这个文章:点我直达burp suit安装指南

以下的讲解,我会结合burp suite来进行

打开Repeater模块(注意下图的红框部分)

现在我们来"试探"这个输入框,把这里的id=1后面的数字1,改成2,3,4,然后点击左上角的send按键,看看左侧发生了什么(*注:需要在右侧的Respone下面的几个选项中,选择Render,才能和我一样,看到预览效果)

主要观察我这里的预览效果中的这个红框,看看有啥不同之处(如上图id=2和下图id=3的区别)

很明显,id=2和id=3所输出的显示都不一样了

这就说明了一个问题,那就是这里的id=number被后端的数据库给带入进去查询了

居然知道了这一点,那我们接下来测试:1 and 1=1,带入进去看看有结果

天真的你,输入后发现,为啥我的这个显示报错?按道理来说,不因该返回正常的结果吗?

这里你就漏掉了一个很关键的点,你漏掉了“空格”!

1 and 1=1,是1“我是空格”and“我也是空格”1=1

*注:burp suite会把”空格“当成分隔符,所以为了代替正常的“空格”输入,我们需要使用“+”来进行连接,所以正确的语句是下图所示

这样左侧就有数据了

可是光输入这1 and 1=1,我们还是不能准确的分辨出后面输出的语句是否被数据库执行了,所以我们输入一个假数据,来炸一下数据库,看看会返回什么结果

这次数据库返回给了我们一个查询不到的结果,那这就更加能够说明我们刚刚的and后面的语句被数据库执行了

这时候的数据库在干啥?

当我们发送这个1+and+1=2请求时,数据库执行的指令变成了:

“请把用户 ID 等于 1,并且 1 等于 2 的数据找出来给我”

请想象以下的后端数据库语句的这个场景:

(1)先从前端那里拿到了用户的数据,数据库一看,id=1 是真的,但 1=2 永远是假的(在 AND 逻辑中,只要有一个是假的,整个条件就是假)

(2)数据库在表里搜了一圈,发现没有哪条记录能满足“1等于2”这个荒唐的条件,于是它两手一空,什么也没查到

(3)因为数据库没给后端数据,后端代码就走到了“未找到结果”的逻辑分支,给你吐出了“不存在”的提示

第四阶段:理解数据库背后的操作(核心原理部分)

这个实验的意义在于确立了我们对于数据库的“控制权”,通过对比 1=1(有数据)和 1=2(没数据),证明了一个极其重要的事实:

我们刚刚输入的逻辑指令,被数据库老老实实地执行了

如果数据库没有漏洞(即开发者做了防护),我们输入的 and 1=2 会被当成一段纯文本。数据库会去寻找一个 id 真的叫做 "1 and 1=2" 的用户。显然,由于 ID 是数字,它应该报错或者直接找不到,而不是去执行那个 1=2 的逻辑运算

(此处可能也是执行也是这个纯文本的线路,最终输出的也是这个“找不到数据的结果”,但是由于我们是初学者,所以就暂时先排除这种可能存在的情况)换句话来说,括号内的说的情况确实可能发生,但是前期看不懂不理解,可以先忽略

结论: 只要页面结果会根据你输入的 1=11=2 的真假而反复横跳,就铁证如山地证明了数据库在执行你的逻辑,而不是把它当成死板的文字

既然页面会随着你输入的 逻辑真假 而发生 显示变化,这就确认了这是一个标准的 布尔型反馈,也是注入成功的铁证

这个操作其实是在验证:

我们能不能控制数据库的“判断逻辑”

一旦成立,我们就可以:

  • 一位一位猜密码(盲注)
  • 一字符一字符爆数据

布尔盲注的本质:通过控制条件真假,观察页面变化,从而间接获取数据

那我们现在再回到数据库的视角,看看数据库在干嘛

SELECT * FROM users WHERE 条件

它只做一件事:

判断“条件是真是假”

而经过刚刚我们的操作,现在能做到:

  • 让它变真 ✅   1 and 1=1
  • 让它变假 ❌   1 and 1=2

这就叫:我们控制了判断逻辑

第五阶段:字符型注入

不过,先别急着去爆数据

你有没有注意到,我们刚才一直在测试的是 id=1?它的参数值是一个纯数字

正因为是纯数字,我们往后面直接加 and 1=1,数据库才老老实实地把它当逻辑条件执行了

但……如果这个参数值不是数字,而是一个单词名字、或者一串文字呢?

那就完全是另一回事了——这就引出了我们下一个要学的概念:字符型注入

在上一阶段,我们搞清楚了数字型注入的原理:参数值是数字,后台SQL大概长这样:

SELECT * FROM users WHERE id = 1

我们输入 多输入一个and 1=1,SQL就变成:

SELECT * FROM users WHERE id = 1 and 1=1

但是,现实里很多参数并不是数字,而是文字——比如用户名、搜索关键词、文章标题。后台SQL大概是这样写的:

SELECT * FROM users WHERE username = 'admin'

注意到区别了吗?参数值 admin单引号包裹着

Q:为什么单引号这么重要?

A:你可以把单引号理解成SQL语言里的"文字标签"

数据库看到单引号就知道:“哦,单引号里面的是文字内容,不是指令,我要原样去数据库里查找这个文字”

那如果用户输入的是 admin' and 1=1,SQL就变成:

SELECT * FROM users WHERE username = 'admin' and 1=1'

等等,最后多出来了一个落单的单引号'这句SQL直接语法错误,什么都不会执行。

所以字符型注入的核心难题就是:你得先把前面那个单引号"关掉",才能让后面的逻辑指令被执行

字符型注入的"万能公式"

我们需要做两件事:

  1. 闭合前面那个开着的单引号
  2. 注释掉SQL语句里原本在后面的内容(不然会多出来乱七八糟的东西导致报错)

所以字符型注入的基础语句长这样:

' and 1=1 --+

拆开来解释:

片段作用
'闭合前面后端代码里开着的那个单引号
and 1=1我们想注入的逻辑判断条件
--注释符,告诉数据库:我后面的内容都不算,全部忽略
+burp suite中的拼接,前面我们也提到了,仅仅用于拼接语句的作用

注释符的原理:

-- 是SQL里的行注释符,它后面的所有内容数据库都不会去读。+ 在URL里代表空格,因为 -- 后面必须接一个空格才能生效(有些地方直接写 -- - 或者 # 也可以,后面我们会遇到)

接下来我们使用Pikachu靶场来感受一下

第一步:先输入一个单引号,"撬开"一条裂缝

在用户名输入框里,输入一个单引号(%27也可以,这个是单引号的URL编码,是同一个意思):

'

可以看到,页面返回了报错的信息

这个报错翻译就是:"你的SQL语法有错误"

这正好验证了我们刚才讲的那个结论:

输入单引号 → 页面报错 → 说明单引号打破了SQL语句的结构 → 这里是字符型注入

那么此时此刻,数据库那边执行了什么?

SELECT * FROM users WHERE username = '''

 是的,这里有3个引号,数据库肯定报错了(后端的流程如下)

后台原本的SQL:  WHERE username = '  $name  '
                               ↑          ↑
                            开头引号    结尾引号

你输入了一个 ',代入进去变成:

WHERE username = '  '  '
                 ↑  ↑  ↑
              开头  你的  结尾
              引号  单引号  引号

现在引号的配对情况是:

  • 第一个 ' 和第二个 ' 配对了 → 变成了一个空字符串
  • 第三个 ' 落单了,没有人和它配对 → SQL语法直接崩掉

数据库看到这句话就懵了——这句SQL不完整,没法执行,于是直接抛出了我们看到的那个报错:

You have an error in your SQL syntax...

步骤二:把这个'换成以下的这个式子,看看会发生什么
'+or+'1

发现居然打印出了全部的数据

我们输入的这些信息被数据库执行了

为什么会输入全部的结果,那我们来看后端SQL长什么样

我们输入的 '+or+'1 代入进去之后,变成:

SELECT * FROM users WHERE username = ''+or+'1'

数据库判断每一行的时候,逻辑是这样的:

username = ''   →  假(没有用户名为空的)
or '1'          →  真(字符串 '1' 非空,在SQL里等同于 true)

假 OR 真 = 真

所以每一行都满足条件,整张表都被返回了

跟我们之前用的 and 1=1 对比一下

orand 更"危险",因为只要有一个条件为真,整个条件就成立。而 '1' 作为一个非空字符串,在MySQL里永远是真值,效果等同于 1=1

第六阶段:URL参数解析

现在你已经理解了数字型注入和字符型注入的基本原理。但你有没有发现,我们在实验记录里操作的时候,注入语句并不是直接打进输入框的,而是写在了地址栏里

那地址栏里那串 ?id=1+and+1=1 到底是什么格式?+ 真的是加号吗?? 是干嘛用的?这一阶段我们就来把这些东西说清楚

我们输入在地址栏里的那串东西,到底是什么?

在之前的实验记录里,我们在地址栏输入了这样一条 URL:

192.168.153.139/search.php?id=1+and+1=1

串东西是什么格式?? 后面是什么?+ 是加号吗?下面我把这条 URL 拆开逐段解释

URL 的基本结构

一条完整的 URL 由以下几部分组成:

192.168.153.139/search.php?id=1+and+1=1
服务器地址 (IP / 域名)
页面文件路径
分隔符 / 特殊字符
参数名
参数值 (我们注入的内容)
片段名称作用
192.168.153.139主机地址告诉浏览器去找哪台服务器
/search.php路径这里是我这个服务器IP下的某个文件地址
?查询字符串起始符分隔路径和参数,固定写法,它后面的才是传给程序的数据(可以理解成只是一个分隔符)
id=1参数键值对id 是参数名,1 是参数值,服务器会读取这个值去数据库查询

所以,在地址栏看到的:

id=1+and+1=1

实际上发给服务器、再传进 SQL 语句的是:

id=1 and 1=1

*注意:除了 +,还有另一种写法是 %20(空格的十六进制 ASCII 编码)这两种写法等价。在某些场景下 + 会失效,这时候就要改用 %20

写在末尾

到这里,Sql基础知识的部分就告一段落了

我们从"数据库是什么"开始,一路走到了字符型注入的原理、URL参数的格式——这些都是理解注入攻击的地基

如果你觉得自己已经准备好了,可以去看配套的实验记录,看看这些原理在真实靶场里是怎么一步一步被用起来的:

👉 SQL 注入入门 —— 从暴力破解到 Union 注入(实验记录)

实验记录里用的是整数型注入和数据库语句的查询,这两个我们在基础补全里已经铺垫过了,如果读的过程中哪个地方又懵了,随时可以翻回这里对照着看