详解Web安全---SQL注入漏洞
前言
什么是SQL注入?来看一下下面的案例场景,这是正常情况下的登陆场景:
而当我们使用用户名 ‘:– 的时候,密码随便输入也可以登陆成功!
这时候对比两条sql就能发现,其实用户通过在用户名写入的sql符号将内部sql提前结束,并且将后半句检索条件注释起来达到免密码登陆。
小结:SQL注入就是本来我只有我能操作数据库,本来只是让你输入内容就走,而你却输入命令,从而在我不知情下操作数据库。
手工检测
Web应用的主要注入点有:
- POST请求体中的参数;
- GET请求头URL中的参数;
- Cookie。
闭合类型
1、数字型(%后面跟的是该字符的16进制编码,为了在http中传输特殊字符和汉字等而使用,其中%23表示'#',%20表示空格)
URL: http://localhost/index.php?id=1 SQL语句:SELECT * FROM users WHERE id=1 LIMIT 0,1 注入语句:http://localhost/index.php?id=1 and 1=1 %23 SQL语句:SELECT * FROM users WHERE id=1 and 1=1 # LIMIT 0,1
2、字符型
URL: http://localhost/index.php?id=1 SQL语句:SELECT * FROM users WHERE id='1' LIMIT 0,1 注入语句:http://localhost/index.php?id=1' and 1=1 %23 SQL语句:SELECT * FROM users WHERE id='1' and 1=1 #' LIMIT 0,1
3、其他变体
URL: http://localhost/index.php?id=1 SQL语句:SELECT * FROM users WHERE id=('1') LIMIT 0,1 注入语句:http://localhost/index.php?id=1') and 1=1 %23 SQL语句:SELECT * FROM users WHERE id=('1') and 1=1 #') LIMIT 0,1
字符型注入
测试字符串 | 变体 | 预期结果 |
---|---|---|
’ | N/ A | 触发数据库返回错误 |
’ or ‘1’ = '1 | ') or (‘1’ = '1 | 永真,返回所有行 |
’ or ‘1’ = '2 | ') or (‘1’ = '2 | 空,不影响返回结果 |
’ and ‘1’ = '2 | ') and (‘1’ = '2 | 永假,返回空 |
如果系统限制了某些字符不能输入(前端限制某些字符不可输入至输入框内),我们可以对相关字符进行URL编码转换后尝试绕过,常见需要编码的字符如下:
- 加号(+)编码为:
%2B
- 等号(=)编码为:
%3D
- 单引号(’)编码为:
%27
- 注释号(#)编码为:
%23
数字型注入
测试字符串 | 变体 | 预期结果 |
---|---|---|
’ | N/ A | 触发数据库返回错误 |
or 1 = 1 | ) or (1 = 1 | 永真,返回所有行 |
or 1 = 2 | ) or (1 = 2 | 空,不影响返回结果 |
and 1 = 2 | ) and (1 = 2 | 永假,返回空 |
终止式注入
漏洞修复
SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
1、预编译语句
会产生上面的情况是因为上面的SQL是使用动态拼接的方式,所以sql传入的方式可能改变sql的语义。
动态拼接就是在java中java变量和SQL语句混合使用:
select * from user where userName=’”+userName+”’ and password = ‘”+password”’
所以要使用 preparedStatement的参数化SQL,通过先确定语义,再传入参数(通过setInt,setString,setBoolean传入参数),就不会因为传入的参数改变sql的语义。
来看看参数化SQL使用案例:
/建立数据连接 conn=ds.getConnection(); //1.设置prepareStatement带占位符的sql语句 PreparedStatement ptmt = conn.prepareStatement("select * from user where userName = ? and password = ?"); //2.设置参数 ptmt.setString(1, "张三"); ptmt.setString(2, "123456"); rs=ptmt.executeQuery(); while(rs.next()){ System.out.println("登陆成功"); return; } System.out.println("登陆失败");
2、字符串过滤
比较通用的一个方法(||之间的参数可以根据自己程序的需要添加):
public static boolean sql_inj(String str) { String inj_str = "'|and|exec|insert|select|delete|update| count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|,"; String inj_stra[] = split(inj_str,"|"); for (int i=0 ; i < inj_stra.length ; i++ ){ if (str.indexOf(inj_stra[i])>=0){ return true; } } return false; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!