SQL注入中information_schema的作用
本实验讲解了通过information_schema这个数据库来获取数据库中的数据库名、表名和字段名等信息的原理。
实验简介
实验所属系列:WEB安全应用
实验对象:WEB安全爱好者
相关课程及专业:渗透测试技术
实验时数:1小时
实验类别:实践实验类
预备知识
MySQL语法:https://dev.mysql.com/doc/refman/5.7/en/select.html
MySQL查询数据:http://www.runoob.com/mysql/mysql-select-query.html
实验目的
通过本次实验,掌握SQL注入中,通过information_schema这个数据库爆库名、表名以及字段名的原理。
实验环境
windows 7、安装wamp环境。
实验内容与步骤
在MySQL数据库的注入中,如果你有仔细看过SQL注入语句的话,你可能就会发现,在获取数据库名、表名和字段的时候,注入语句中information_schema这个数据库出现得很频繁,那么有没有想过为什么会需要用到这个数据库呢? 这个数据库又是什么?它里面保存了什么?
? information_schema数据库是MySQL自带的,MySQL 5以下没有这个数据库,它提供了访问数据库元数据的方式。什么是元数据呢?元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等。也就是说information_schema中保存着关于MySQL服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。在INFORMATION_SCHEMA中,有数个只读表。
在phpmyadmin中,在左侧点击information_schema数据库。
展开后如下图,显示了该数据库中的所有表,由于表数量太多,只截了一部分,可以拉动右边的滚动条查看所有表。
也可以执行如下SQL语句来查看该库中的所有表:
? show tables;
需要注意的是,要在information_schema这个数据库中执行该SQL语句。如何进入information_schema数据库执行SQL语句,请参考前面进入sqli数据库执行SQL语句的步骤。
这上面显示的表,它们实际上是视图,而不是基本表,所以你在数据库的数据保存目录,会看不到这个数据库的实体文件。数据库的数据保存在 C:\wamp\bin\mysql\mysql5.6.17\data 目录,在这个目录下一共有如下4个目录:
想要查看数据库的数据保存目录,可以执行select @@datadir,如下图:
每一个目录对应数据库中的一个数据库,在数据库中执行show databases;的时候,可以看到存在5个数据库,正是少了information_schema这个数据库。
在SQL注入中,我们重点关注的表有如下几个,因为主要的时候主要利用这几个表来获取数据:
SCHEMATA:提供了当前mysql数据库中所有数据库的信息,其中SCHEMA_NAME字段保存了所有的数据库名。show databases的结果取自此表。
TABLES:提供了关于数据库中的表的信息,详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息,其中table_name字段保存了所有列名信息,show tables from schemaname的结果取自此表。
? COLUMNS:提供了表中的列信息。详细表述了某张表的所有列以及每个列的信息,其中column_name保存了所有的字段信息。show columns from schemaname.tablename的结果取自此表。
为了更好地说明这些表的作用,我们进入mysql终端。
点击右下角的wampserver图标,如果没有该图标,可以双击桌面的WampServer运行。
然后在弹出来的列表中点击MySQL,再选择MySQL控制台。
会弹出一个命令行窗口,这就是mysql客户端,此时要求输入密码,由于root的密码为空,直接回车即可。
进入information_schema 数据库,命令为:use information_schema; 。一定要注意后面记得加分号,分号表示一个语句结束,如果没有检测到你输入分号,它会认为你一个语句还没结束,直到碰到分号后,才开始执行语句。
首先执行show databases;查看所有的数据库,然后再执行select schema_name from schemata;。
可以看到他们的作用是一样的,都是列出所有数据库,跟我们前面说的一样,SCHEMA_NAME字段保存了所有的数据库名。
所以,在注入中,我们可以通过注入select schema_name from schemata 来查询的当前数据库中所有的数据库名,如果你去查看一些爆数据库名的注入语句,就会发现里面包含这么一句:select schema_name from information_schema.schemata limit 0,1,其原理就是通过查询information_schema.schemata中schema_name的结果,其中limit 0,1用来获取第一条记录,通过递增第一个参数,可以每次获取一条记录,也就是一次获取一个数据库名,直到出现错误为止,说明没有更多的错误。
通常在获取了数据库名后,就会选择感兴趣的数据库,然后来获取其中的数据,首先需要获取感兴趣的数据库中的所有表名,通过查询information_schema库中的TABLES表就可以获取表名。
? 在TABLES表中,它保存了所有数据库中的所有表名以及这个表所属的库,意思是说,不管你在哪个数据库中的表,在这里都会有一条记录对应,如果你在一个数据库中创建了一个表,相应地在这个表里,也会有一条记录对应你创建的那个表。
desc 可以用来看表结构。看下tables的表结构,执行desc tables;,结果如下图:
注意上图中标记的那2条记录,每一条记录中,他们分别记录一个表名和一个这个表所属的库名。其中TABLE_NAME保存的是表名,而TABLE_SCHEMA保存的是这个表名所在的数据库。我们可以查询一条记录看看,在查询前,先看看有多少条记录,避免记录太多查看不方便,执行select count(*) from tables; 结果如下图:
说明当前所有数据库中的表数量为142。查询任意一条记录查看,我这里选择最后一条记录,SQL语句为:select * from tables limit 141,1\G由于在客户端中,默认查询结果显示不友好,所以,可以把语句后面的分号改成\G,他会让一条记录显示一行,看起来不那么乱。\G只支持在客户端中用,在其他连接数据库的软件中,使用\G会报错。
可以看到,最后一条记录的TABLE_NAME是user,TABLE_SCHEMA为sqli。查看sqli数据库中的表,SQL语句为:show tables from sqli;可以看到确实存在user表。
既然information_schema的TABLES表中的TABLE_SCHEMTA字段是保存的数据库名,而TABLE_NAME保存了表名,那么我们就可以使用TABLE_SCHEMTA字段作为查询条件,查询TABLE_NAME,即可得知所有指定数据库中的所有表名。比如,我们想要通过information_schema数据库来查询sqli数据库中所有的表,那么就可以使用如下SQL语句:
select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA = ‘sqli’;
? 如果当前库为information_schema,则可以省略不写,否则跨库查询的时候,需要带上库名。结果如下图:
通过修改TABLE_SCHEMA 的限制,可以查询任意数据库中的所有表名,网上的通过注入爆表名便是这个原理。
? 知道了表名,那么如何获取表中的字段呢?要知道我们没有表名的话,会把所有的数据查询出来,而如果注入没有回显,不能进行union查询,那么想要获取我们的标目数据,无疑效率极低。
幸运的是,在information_schema数据库中,同样存在一个表,它保存了整个数据中,所有的列名,这个表就是COLUMNS。同样先查看该表结构。
这里面,与注入相关的存在3个字段,分别是TABLE_SCHEMA、TABLE_NAME以及COLUMN_NAME,不难猜到,如果在该表中查询一条记录,TABLE_SCHEMA保存了这条记录保存的字段所属的数据库名,而TABLE_NAME保存的是该字段所属表名,COLUMN_NAME则是一个列名记录,查询一条记录验证一下,首先确定该表有多少条记录,执行select count(*) from columns;,得知一共有1662条记录,结果如下图:
我们获取最后一条记录,执行select * from columns limit 1661,1\G
其中COLUMNS_NAME为ip,TABLE_NAME为user,TABLE_SCHEMA为sqli,这说明,在sqli这个数据中,user表存在一个ip的列,也就是我们常说的ip字段。
查看sqli的user表是否存在该字段,执行SQL语句:show columns from sqli.user;
可以看到确实存在该字段。
既然在columns中,TABLE_NAME保存了字段所属的表名,TABLE_SCHEMA保存了该字段所属的库名,与通过TABLES表获取表名一样,我们就可以查询把TABLE_NAME 和TABLE_SCHEMA做为查询条件,查询符合条件的COLUMN_NAME,也就是查询指定数据库中某表中的字段。
? 比如,我们要通过information_schema数据库的columns表查询sqli数据库中user表中所有的字段,可以执行如下SQL语句:
select column_name from information_schema.columns where TABLE_SCHEMA=‘sqli’ and TABLE_NAME=‘user’;
查询结果与show columns from sqli.user; 一致。
知道了库名、表名、字段,如果有回显且支持联合查询,就可以直接通过在注入点后面注入一个联合查询语句,即可直接获取数据,如果不能回显,则可能需要通过盲注获取数据,可以参考MySQL盲注实验。
答案
分析与思考
为什么网上的SQL注入语句中,数据库名都是用的字符的16进制值? 常用于盲注时截取字符串的某一个字符,判断是什么,例如使用substr(database(),1,1)获取数据库的第一个字符进行判断,再结合length函数,最后拼接成整个数据库名。
补充
注释
#或--空格是单行注释
/**/是内联注释
SQL语句
查询语句
SELECT〈目标列组〉
FROM〈数据源〉
[WHERE〈元组选择条件〉]
[GROUP BY〈分列组〉[HAVING 〈组选择条件〉]]
[ORDER BY〈排序列1〉〈排序要求1〉 [,…n]];
UNION
用于union注入攻击
UNION [ALL | DISTINCT]
SELECT expression1, expression2, ... expression_n
FROM tables
[WHERE conditions];
参数:
expression1, expression2, ... expression_n: 表达式,sql注入时通常是database()函数等我们需要的信息。
tables: 要检索的数据表。
WHERE conditions: 可选, 检索条件。
DISTINCT: 可选,删除结果集中重复的数据。默认情况下 UNION 操作符已经删除了重复数据,所以 DISTINCT 修饰符对结果没啥影响。
ALL: 可选,返回所有结果集,包含重复数据。
LIMIT
语法:
limit m,n
从m位置开始,取n条记录
ORDER BY
order by 后面可以加列名,也可以加数字,数字应是小于等于查询结果的列数,所以可以慢慢增加,当出错时,查询结果就是出错时的数字-1。
information_schema
mysql 5.0之后有这个数据库,包含数据库的很多信息,常用表SCHEMATA、TABLES、COLUMNS。
SCHEMATA
SCHEMATA表存储数据库名,字段为SCHEMA_NAME
SCHEMATA表
sql语句
SELECT schema_name FROM information_schema.schemata
查询结果
TABLES
TABLES表存储表名及表所属数据库,字段为TABLE_NAME、TABLE_SCHEMA
TABLES表
一般是查询当前数据库或者从上面得到的数据库中指定一个。
查询当前数据库sql语句
SELECT table_name FROM information_schema.`TABLES` WHERE table_schema=database()
当前数据库的表
指定数据库sql语句,以pikachu为例
SELECT table_name FROM information_schema.`TABLES` WHERE table_schema='pikachu'
结果
COLUMNS
COLUMNS表存储列名及所属数据库名、所属表名,字段为COLUMN_NAME、TABLE_SCHEMA、TABLE_NAME
COLUMNS表
查询当前表的列,sql语句
SELECT COLUMN_NAME FROM information_schema.columns WHERE table_name=table_name
结果
查询指定表的列,通过上面,我们已经知道pikachu数据库有表users,以这个为例。
sql语句
SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema='pikachu' AND table_name='users'
结果
字段值
通过利用这个数据库我们已经知道了所有的数据库、表、列,接下来我们就可以得到想要的数据了。以数据库pikachu的users表的username和password为例。
sql语句
SELECT username,password FROM pikachu.users
结果:
结果
当然,实际的sql注入没有这么简单,本篇文章列出所有知识点,接下来的文章以本地靶机讲解几种注入技术,然后再分析dvwa的过滤代码以及绕过,都学完后差不多就可以解决中低级的sql注入问题,然后可以去一些CTF在线靶场来进一步提高了。
常用函数
在上面的Sql语句中,我们提到了databse(),这是mysql的函数,接下来我们就看看在sql注入时常用到的函数。
基本函数
1.返回当前使用数据库的用户
user()/system_user()/current_user()/session_user()
2. 返回当前数据库的版本
version()/@@version
3. 返回当前使用的数据库
database()
4. 返回当前数据库所在位置
@@datadir
5. 返回当前操作系统版本
@@version_compile_os
6.将多个字符串连接成一个字符串
concat(s1,s2...sn)
7.将多行字符串拼接成一行
group_concat()语法如下:
group_concat(
[DISTINCT] 要连接的字段
[Order BY 排序字段 ASC/DESC]
[Separator ‘分隔符’]
)
主要用来处理一对多的查询结果,通常会结合GROUP BY一起使用。
盲注(字符串处理函数)
length(s) :返回目标字符串的长度
常用于盲注时数据库等信息的判断,sql语句
select length(database());
结果
substr(),substring(),mid():用法基本相同,截取字符串的一部分,会一个就可以了
SUBSTR(s, start, length)
SUBSTRING(s, start, length)
MID(s, start, length)
从字符串s的start(包含且>=1)位置开始截取length个字符。
常用于盲注时截取字符串的某一个字符,判断是什么,例如使用substr(database(),1,1)获取数据库的第一个字符进行判断,再结合length函数,最后拼接成整个数据库名。
ascii(s)/ord(s):返回字符串s的第一个字符对应的ASCII码,记一个就可以了
常用于盲注,结合substr函数
sql语句
SELECT ascii(substr(database(),1,1))
结果
再查询ascii码表就知道105对应的是字符i。
char(num):返回ASCII码对应的字符
上面函数的逆函数
sql语句
SELECT char(97)
97对应的是字符a
结果
hex():将目标字符串装换成16进制格式的数据
select hex(“dvwa”)
返回结果:70696B61636875
unhex():将16进制格式的数据装换成原字符串
语句:unhex(64767761)
解释:返回结果:dvwa
left()/right() :从规定字符串的左边/右边开始截取字符串
LEFT(s,n) 、RIGHT(s,n)
从字符串s左/右边开始截取n位字符串
报错注入
UPDATEXML函数
UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,一般写1就可以了
第二个参数:XPath_string (Xpath格式的字符串) ,不必了解Xpath语法,我们一般是写sql语句查询表名等,通过报错信息显示出来。
第三个参数:new_value,String格式,替换查找到的符合条件的数据,一般写1就可以了
作用:改变文档中符合条件的节点的值
还有floor、extractvalue等函数也可以用于报错注入
时间盲注
sleep(N)函数
运行N秒。
IF(expr,v1,v2)函数
如果表达式expr成立,返回v1,否则返回v2。与sleep函数结合通过网页多久后返回来判断结果。
sql语句
SELECT ascii(substr(database(),1,1)) and if(length(database())>17,sleep(2),1)
等待2秒后,返回0
2秒后返回
SELECT ascii(substr(database(),1,1)) and if(length(database())>18,sleep(2),1)
立刻返回
数据库长度>17但不>18,即数据库长度为18。
|