前述:
SQL注入这一关,我们在sql-master和DVWA都详细的介绍过,尤其是DVWA里,解题的每一步都很详细,所以这里以前遇到过的就不再过多的解题了,主要是一起学习一下没见过的题还有方法和原理。
概述:
产生漏洞的原因:
SQL注入漏洞,主要是开发人员在构建代码时,没有对输入边界进行安全考虑,导致攻击者可以通过合法的输入点提交一些精心构造的语句,从而欺骗后台数据库对其进行执行,导致数据库信息泄漏的一种漏洞。比如我们期望用户输入整数的id 1,但是用户输入了1 or 1=1,这是条能被正常执行的SQL语句,导致表中的数据都会输出。
注入点类型:
1. 数字型:user_id=$id
2. 字符型:user_id='$id'
3. 搜索型:text LIKE '%{$_GET['search']}%'"
SQL注入的攻击流程:
1.注入点探测
自动方式:使用web漏洞扫描工具,自动进行注入点发现
手动方式:手工构造SQL注入测试语句进行注入点发现
2.信息获取,通过注入点取得期望得到的数据
1.环境信息:数据库类型,数据库版本,操作系统版本,用户信息等
2.数据库信息:数据库蜜罐,数据库表,表字段,字段内容等(加密内容破解)
3.获取权限
通过数据库执行shell,上传木马等方法
数字型注入(POST):
我们随便选择一个,点击查询,我选的是 1 。
正常来说,我们的数据是放在数据库里的,当我们提交了这个id的,后台会带这个参数到数据库里查询。因为是用POST语句取得我们传递的参数值,传递给一个变量,再到数据库查询。
那我们来抓包来测试一下 ,把传入的参数改成 1 or 1=1。
可以看到取出了数据库中全部数据,说明存在数字型注入漏洞。
字符型注入(GET):
这一关看到了一个框框,随便输入点什么吧,发现输入的用户名不存在。
那我们输入一个存在的用户名试试(我们上面已经知道了),发现输入了一个lili就会得到uid 和他的地址信息。
因为这里输入的查询用户名是字符串,所以在查询语句中需要有单引号。
我们需要构造闭合,闭合后台查询语句中的第一个单引号,然后注释掉第二个单引号,构造的payload为:kobe' or 1=1#
这样我们要查的这个表中全部信息就出来了。
搜索型注入:
我们一看搜索型注入我们可以猜想他后台使用了数据库搜索的逻辑,可能用到了 LIKE。
看看代码,果然如此。
看到源码就好构建payload了,构造对应的闭合,闭合前面的 单引号 和 百分号,注释后面的百分号和单引号:k%' or 1=1#
XX型注入:
我们看下源码,发现变量变成了被括号包裹的了 (),
所以我们构造的payload为:
我们看着上面几个感觉好简单啊,这是因为我们偷看了源码了。如果没有源码的话得根据报错信息和利用 and or等一个一个去试才行。
1. SQL手工注入基于联合语句查询
这里我就简单的提几句为下面的内容做一个铺垫,详细可以看看我前面DVWA里写到的关于SQL手工注入的步骤。
基于union联合查询的信息获取:
通过联合查询来查询指定的数据,比如下面这句。
select username,password from user where id=1 union select 字段1,字段2 from 表名
联合查询的字段数需要和主查询一致,上面主查询查询了 2 个字段 username 和 password,所以我们的 union 语句也要查询两个字段。
使用 union 需要知道主查询有多少个字段,我们可以用 order by 来帮助我们猜测后台查询语句查询的字段数。
select 字段1,字段2 from users order by 1
后面跟着的数字表示根据查询结果的第几列进行排序,如果后台查询 2 个字段,那我们 order by 3 时就会报错,order by 2 时会正常返回。
手工注入思路:
1.判断是否存在注入,注入是字符型还是数字型
2.猜解 SQL 查询语句中的字段数
3.确定显示的字段顺序
4.获取当前数据库
5.获取数据库中的表
6.获取表中的字段名
7.查询到账户的数据
2.基于函数报错的信息获取
原理:函数传入的参数如果不符合规则的话就会报错,当我们把一个合法的语句作为这个函数的参数时,它会先执行一遍,然后再以这个语句的结果作为报错的信息返回回来这样我们就能查询到我们需要的信息了。
所以,这个基于函数报错的信息的获取有一个重要的前提,就是该页面能显示后台返回的报错结果。
updatexml(): MySQL 对 XML 文档数据进行查询和修改的 XPATH 函数
extractvalue():MySQL 对 XML 文档数据进行查询的 XPATH 函数
floor():MySQL中用来取整的函
以函数updatexml()为例
原型:updatesml(xml_document,xpathstring,new_value)
这个函数的第二个参数(xpathsrting)一定要是有效的,否则会报错,我们就是基于此报错来获取我们想要的信息,哦,该报错还会“吃掉”前面的一些内容,所以这个我先“喂”它一点东西,就是用concat函数把两个字段连成一个字段,就是在前面加点东西,让我们想要的信息能够完整的输出出来,
例如:updatexml(1,concat(0x7e,version()),1);这里的0x7e是~符号,就按pikachu xx型注入来举例子,构造的应该是1‘) and updatexml(1,concat(0x7e,version()),1) 输出结果如下:
直接得到用户名和密码构造payload:1') and updatexml(1, concat(0x7e,(select (concat_ws('-',username,password)) from pikachu.users limit 0,1) ),1)#
同样limit 1,1或者limit 2,1都可以。
同样的,除了以上的updatexml函数,还有extractvalue()函数,floor()函数。
extractvalue()函数
语法:extractvalue(xml_document,xpath_string)
第一个参数xml——document是string格式,为xml格式文档对象名称,中文为dos
第二个参数xpath——string是xpath格式的字符串。
这里与updatexml()函数一样,xpath定位必须是有效的,如果无效则返回报错信息。
例如:1') and extractvalue(1,concat(0x7e,version()))# 得到的效果图:
floor() 函数:
向下取整。如果要用 floor() 构成报错,必须满足下面的条件
运算中有 count
运算中有 group by
运算中有 rand
还是再xx型里面试试 :1' ) and (select 2 from (select count(*), concat(version(), floor(rand(0) * 2))x from information_schema.tables group by x)a)#
insert/update注入:
所谓 insert 注入是指我们前端注册的信息,后台会通过 insert 这个操作插入到数据库中。如果后台没对我们的输入做防 SQL 注入处理,我们就能在注册时通过拼接 SQL 注入。
我们就填必填的两项,用户那里输入单引号,密码随便输入,页面会有报错信息,说明存在SQL注入漏洞。
这种情况下,我们知道后台使用的是 insert 语句,我们一般可以通过 or 进行闭合。构造语句为:1' or updatexml(1, concat(0x7e,database()), 0) or '
查询表名:1' or updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema='pikachu' limit 0,1)), 0) or '
上面返回了查询结果中的第一个表名,如果要查询第二个表名,我们可以把 limit 语句换成 limit 1,1
limit 后的第一个数据是起始位置,第二个数字是取出的数据条数
以此类推,取出所有的表名。有了表的名称后我们就去获取字段
以此类推,取出所有的列名:1' or updatexml(1, concat(0x7e, (select username from users limit 0,1)), 0) or '
然后根据得到的用户名,去查询password:1' or updatexml(1, concat(0x7e, (select password from users where username = 'admin' limit 0,1)), 0) or '
delete注入:
这里是个留言板,我们删除时候抓包试试:
我们发送到 Repeater 中继续进行实验,由于参数的值是数字型,所以后台可能存在数字型注入漏洞,构造payload如下:1 or updatexml(1, concat(0x7e,database()), 0) (要进行URL编码)
因为抓包后render里会乱码,所以就不截全部的图了。
接下来应该就是查表查列查数据了,跟上面的类似就不重复了。
http header注入:
有些时候,后台开发人员为了验证客户端头信息(比如cookie验证)或者通过http header获取客户端的一些信息,比如useragent,accept字段等。
会对客户端的http header信息进行获取并使用SQL进行处理,如果此时并没有足够的安全考虑,则可能会导致基于 http header 的 SQL 注入漏洞。
我们这里先登录一下,admin 123456
然后抓包,修改user-agent或者cookie完成注入:1' or updatexml(1, concat(0x7e,(select (concat_ws('-',username,password)) from pikachu.users limit 0,1) ),1) or '
盲注 based on boolean:
在有些情况下,后台使用了错误屏蔽方法屏蔽了报错,此时无法根据报错信息来进行注入的判断。这种情况下的注入,称为“盲注”。
基于真假的盲注主要特征:
1. 没有报错信息
2. 不管是正确的输入,还是错误的输入,都只有两种情况(可以看做 0 or 1)
3. 在正确的输入下,后面跟 and 1=1 / and 1=2 进行判断
一条正确执行,一条显示用户名不存在,说明后台存在 SQL 注入漏洞
因为这里的输出只有 用户名存在 和 用户名不存在 两种输出,所以前面基于报错的方式在这不能用。
我们只能通过 真 或者 假 来获取数据,所以手工盲注是很麻烦的。
步骤:
我们可以先用 length(database()) 判断 数据库名称的长度
再用 substr() 和 ascii() 判断数据库由哪些字母组成(可以用二分法)
不断重复,然后取得数据库名。再和 information_schema 和 length 猜测 表名 的长度,我们可以用下面的 SQL 语句替代上面的 database()
先判断表名长度,然后猜解表名
同样的方法去猜解列名、数据,就是麻烦,
(因为盲注这块在前面DVWA说的特别特别详细了,所以真的不想再重复一遍了,可以移驾看看前面DVWA里提到的盲注 https://www.cnblogs.com/qi-yuan/p/12448248.html )
盲注(based on time):
基于时间注入前面博客随笔里也详细的提过,这里也不过多的写步骤了。
基于时间的注入就什么都看不到了,我们通过特定的输入,判断后台执行的时间,从而确定注入点,比如用 sleep() 函数
在皮卡丘平台一,无论输入什么,前端都是显示“I don't care who you are!”
用 database() 取得数据库的名称,再用 substr 取字符判断数据库名称的组成,如果猜解成功就会 sleep 5秒,否则没有任何动作。
宽字节注入:
当我们输入有单引号时被转义为’,无法构造 SQL 语句的时候,可以尝试宽字节注入。GBK编码中,反斜杠的编码是 “%5c”,而 “%df%5c” 是繁体字 “連”。
我们在输入框随便输入点东西,通burp抓包一下。
当我们用通常的测试 payload时,是无法执行成功的,试一下:1' or 1=1#,没有变化
因为在后台单引号会被转义,在数据库中执行时多了个反斜杠。我们可以用下面的payload,在单引号前面加上 %df,对比一下:1%df' or 1=1#