当前位置: 首页 > news >正文

QL注入漏洞详解:产生原因、攻击演示及解决方案(附实战代码)

在Java Web开发中,SQL注入是最常见、最危险的安全漏洞之一。它利用开发者编写SQL语句时的疏忽,通过恶意输入篡改SQL逻辑,从而实现越权登录、数据泄露、甚至数据库被篡改等严重后果。本文将结合实战代码,详细拆解SQL注入漏洞的产生前提、攻击效果、底层原因,以及最有效的解决方案,全程贴合实际开发场景,新手也能轻松理解。

一. 什么是SQL注入?核心定义

SQL注入(SQL Injection),简单来说,就是攻击者通过向应用程序输入恶意的SQL语句片段,利用程序对用户输入的未过滤、未验证,将恶意片段拼接进后台执行的SQL语句中,篡改原有SQL的执行逻辑,达到非法操作数据库的目的。

结合我们的实战场景:在已知用户名的情况下,攻击者无需输入正确密码,只需输入特定的恶意字符串,就能绕过登录验证,成功登录系统——这就是最典型的SQL注入攻击。

二. SQL注入漏洞的产生前提(必看)

SQL注入并非随时随地都能发生,它需要满足两个核心前提,缺一不可,这也是我们后续防范漏洞的关键切入点:

  1. 已知关键信息:攻击者需要知道系统中的某个有效用户名(比如本文案例中的“aaa”),这是攻击的基础(如果连用户名都未知,注入攻击的难度会大幅提升)。

  2. 后台存在SQL语句拼接:这是漏洞产生的核心前提。后台程序没有对用户输入的用户名、密码进行过滤,直接将用户输入的内容拼接进SQL语句中,导致恶意输入被当作SQL代码执行。

三. SQL注入的攻击效果(实战演示)

我们以登录功能为例,结合提供的代码,演示两种最常见的SQL注入攻击方式,看看攻击者如何在“密码任意输入”的情况下,成功登录系统。

先看后台存在漏洞的登录代码(对应代码中login方法):

/*** 存在SQL注入漏洞的登录方法* @param username  用户输入的用户名* @param password  用户输入的密码* @return  登录结果*/
public String login(String username,String password){Connection conn = null;Statement stmt = null;ResultSet rs = null;try {// 获取数据库连接conn = JDBCUtils.getConnection();// 核心问题:直接拼接用户输入和SQL语句String sql = "select * from t_user where username = '"+username+"' and password = '"+password+"'";// 创建Statement对象执行SQLstmt = conn.createStatement();rs = stmt.executeQuery(sql);// 有查询结果则登录成功if(rs.next()){return "登录成功...";}else{return "登录失败了...";}} catch (SQLException e) {e.printStackTrace();}finally {JDBCUtils.close(conn,stmt,rs);}return null;
}

攻击方式1:输入恶意字符串 “aaa'or'1=1”(用户名)+ 任意密码

攻击者已知用户名是“aaa”,在登录界面输入:

  • 用户名:aaa'or'1=1

  • 密码:任意字符(比如123456、sfsdfsds等,无需正确)

我们来分析拼接后的SQL语句,就能明白为什么能登录成功:

原SQL模板:select * from t_user where username = '"+username+"' and password = '"+password+"'

拼接后实际执行的SQL:

select * from t_user where username = 'aaa'or'1=1' and password = 'sfsdfsds'

核心分析:

  • SQL中 '1=1' 是一个永远为“真”的条件(恒成立);

  • 原SQL的逻辑是“用户名等于输入值 并且密码等于输入值”,拼接后变成“用户名等于aaa 或者 1=1(恒真) 并且 密码等于任意值”;

  • 根据SQL的逻辑优先级,“and”优先级高于“or”,1=1(恒真) 并且 密码等于任意值这两个条件结合结果为“假”,'aaa'或者假的结果为真(因为用户名正确),这样就会导致整个WHERE条件最终为“真”;

  • SQL会查询出t_user表中的所有数据,后台判断有查询结果,就返回“登录成功”——攻击者无需正确密码,成功登录。

攻击方式2:输入恶意字符串 “aaa'-- ”(用户名)+ 任意密码

这是另一种更简洁的注入方式,攻击者输入:

  • 用户名:aaa'-- (注意:-- 后面有一个空格,这是SQL的注释符号)

  • 密码:任意字符

拼接后实际执行的SQL:


select * from t_user where username = 'aaa'-- '' and password = 'sfsdfsdfs'

核心分析:

  • SQL中 -- 是注释符号,注释符号后面的所有内容都会被数据库忽略;

  • 拼接后,-- 后面的 “'' and password = 'sfsdfsdfs'” 被全部注释,相当于SQL变成:select * from t_user where username = 'aaa'

  • 只要用户名“aaa”存在,SQL就会查询到对应数据,后台判断登录成功——同样无需正确密码,实现越权登录。

四. SQL注入漏洞的产生原因(底层剖析)

通过上面的演示,我们能明确:SQL注入漏洞的根本原因,就是后台程序对用户输入的内容未做任何过滤和验证,直接将用户输入拼接进SQL语句中

具体拆解为两点,结合代码更易理解:

  1. SQL语句拼接的弊端:代码中使用 String sql = "select ... where username = '"+username+"' and password = '"+password+"'" 这种拼接方式,用户输入的内容会直接成为SQL语句的一部分。如果用户输入的是恶意SQL片段(比如'or'1=1、-- 等),就会篡改原有SQL的逻辑。

  2. 未过滤特殊字符:对于SQL中的特殊字符(比如单引号'、注释符号--、逻辑运算符or/and等),后台程序没有进行转义或过滤,导致这些特殊字符被当作SQL代码的一部分执行,而不是当作普通的用户输入数据。

简单来说:程序把“用户输入的数据”当成了“SQL代码”来执行,这就是SQL注入的本质。

五. SQL注入漏洞的解决方案(终极方案)

解决SQL注入的核心思路是:禁止SQL语句拼接,将用户输入的内容与SQL语句的逻辑分离,让用户输入的内容永远只作为“数据”,而不是“SQL代码”执行

在Java中,最标准、最有效的解决方案是:使用PreparedStatement接口,它是Statement接口的子接口,核心优势是“预编译SQL语句”,能从根源上杜绝SQL注入。

1. PreparedStatement的核心原理

  • 预编译功能:先将SQL语句的“骨架”(固定逻辑)发送到MySQL服务器端进行编译,编译后的SQL语句格式被固定,无法再被篡改。

  • 占位符替代参数:SQL语句中需要传入用户输入的部分,用 ? 作为占位符(比如 select * from t_user where username = ? and password = ?),而不是直接拼接用户输入。

  • 参数单独传入:编译完成后,再将用户输入的内容作为“参数”,通过专门的方法传入占位符,数据库会自动将参数当作普通数据处理,不会解析成SQL代码。

2. 核心方法(必掌握)

  1. 创建PreparedStatement对象:通过Connection接口的 prepareStatement(String sql) 方法创建,参数是带占位符的SQL语句(预编译SQL)。

  2. 给占位符赋值:通过PreparedStatement的setXxx()方法给 ? 赋值,核心方法如下:

    1. setString(int parameterIndex, String x):给指定位置的占位符赋值(字符串类型),parameterIndex是占位符的位置(从1开始,不是从0开始)。

    2. setInt(int parameterIndex, int x):给占位符赋值(int类型)。

    3. setObject(int parameterIndex, Object x):通用赋值方法,可适配任意数据类型。

  3. 执行SQL语句

    1. executeQuery():执行查询类SQL(select),无需传入SQL语句(因为已经预编译完成)。

    2. executeUpdate():执行增删改类SQL(insert、update、delete),同样无需传入SQL语句。

实战代码(解决SQL注入,对应login2方法)

/*** 采用预编译方式,解决SQL注入漏洞的登录方法* @param username  用户输入的用户名* @param password  用户输入的密码* @return  登录结果*/
public String login2(String username,String password){Connection conn = null;PreparedStatement stmt = null; // 预编译SQL执行对象ResultSet rs = null;try {// 1. 获取数据库连接conn = JDBCUtils.getConnection();// 2. 编写带占位符的SQL语句(骨架固定)String sql = "select * from t_user where username = ? and password = ?";// 3. 预编译SQL语句,创建PreparedStatement对象stmt = conn.prepareStatement(sql);// 4. 给占位符赋值(用户输入的内容仅作为数据传入)stmt.setString(1, username); // 第1个? 赋值为usernamestmt.setString(2, password); // 第2个? 赋值为password// 5. 执行SQL(无需传入SQL语句,已预编译)rs = stmt.executeQuery();// 6. 判断登录结果if(rs.next()){return "登录成功...";}else{return "登录失败了...";}} catch (SQLException e) {e.printStackTrace();}finally {// 关闭资源JDBCUtils.close(conn,stmt,rs);}return null;
}

4. 效果验证(彻底杜绝注入)

当我们使用login2方法,再输入之前的恶意字符串时,注入攻击会彻底失效:

  • 输入用户名:aaa'or'1=1,密码:任意字符;

  • 预编译后,SQL的骨架 select * from t_user where username = ? and password = ? 被固定;

  • 用户输入的“aaa'or'1=1”会被当作普通字符串,赋值给第一个占位符,不会被解析成SQL逻辑;

  • 数据库会查询“username等于'aaa'or'1=1'”的用户,而这种用户名并不存在,因此返回“登录失败”,注入攻击失效。

六、核心总结(必背)

  1. SQL注入是什么:攻击者通过恶意输入,篡改后台拼接的SQL语句逻辑,实现越权操作(如无密码登录)。

  2. 产生原因:后台程序直接拼接用户输入和SQL语句,未过滤特殊字符,导致用户输入被当作SQL代码执行。

  3. 攻击前提:已知有效用户名 + 后台存在SQL拼接。

  4. 解决方案:使用PreparedStatement预编译SQL,用?占位符替代参数拼接,将用户输入作为数据传入,从根源杜绝注入。

  5. 关键区别:Statement(拼接SQL,有注入漏洞) vs PreparedStatement(预编译+占位符,无注入漏洞),实际开发中必须使用PreparedStatement。

完整代码

import cn.qcby.utils.JdbcUtils;import java.sql.*;/*** 演示SQL注入的问题,漏洞* 在已知用户名的情况下,通过sql语言关键字,登录系统。* SQL注入产生原因是SQL语句的拼接,利用SQL关键字(or和and的优先级问题)产生效果。* 需要解决SQL注入的问题** 解决SQL注入问题,采用SQL语句预编译的方式,把SQL语句中的参数使用 ? 占位符来表示,先把SQL语句编译,格式固定的。* 再给 ? 传入值,传入任何内容都表示值。数据库会判断SQL执行的结果。*/
public class JdbcTest4 {public static void main(String[] args) {/*// 模拟登录的功能,有SQL注入的问题*/
//        String result = new JdbcTest4().login("aaa'or'1=1", "123");
//        System.out.println(result);String result = new JdbcTest4().login2("aaa'or'1=1", "12346546464");System.out.println(result);}/*** 采用预编译的方式,解决SQL注入的问题* @param username* @param password* @return*/public String login2(String username,String password){Connection conn = null;// 预编译执行SQL语句对象PreparedStatement stmt = null;ResultSet rs = null;try {// 获取到连接conn = JDBCUtils.getConnection();// 使用?占位符String sql = "select * from t_user where username = ? and password = ?";// 预编译SQL语句,把SQL语句固定//Statement statement = conn.createStatement();//statement不能防止sql注入问题    prepareStatement (有占位符)能够防止sql注入问题stmt = conn.prepareStatement(sql);// 需要给 ? 设置值stmt.setString(1,username);stmt.setString(2,password);// 执行SQL语句rs = stmt.executeQuery();// 遍历数据if(rs.next()){// 表示存在数据,如果存在,说明用户名和密码编写正确return "登录成功...";}else{return "登录失败了...";}} catch (SQLException e) {e.printStackTrace();}finally {JDBCUtils.close(conn,stmt,rs);}return null;}/*** 模拟登录的功能,通过用户名和密码从数据库中查询* @param username* @param password* @return*/public String login(String username,String password){Connection conn = null;Statement stmt = null;ResultSet rs = null;try {// 获取到连接conn = JDBCUtils.getConnection();// 编写SQL语句的拼接  '1=1' true  password = '1234sdfsce' false  true and false  整体上false,前面的用户名'aaa'or false,用户名正确,所以即使密码错误也能登录// String sql = "select * from t_user where username = 'aaa' or '1=1' and password = '1234sdfsce'";// String sql = "select * from t_user where username = 'aaa' or false";String sql = "select * from t_user where username = '"+username+"' and password = '"+password+"'";// 执行sqlstmt = conn.createStatement();// 执行rs = stmt.executeQuery(sql);// 遍历数据if(rs.next()){// 表示存在数据,如果存在,说明用户名和密码编写正确return "登录成功...";}else{return "登录失败了...";}} catch (SQLException e) {e.printStackTrace();}finally {JDBCUtils.close(conn,stmt,rs);}return null;}}
http://www.jsqmd.com/news/660306/

相关文章:

  • DeepFaceLab模型训练避坑指南:从‘鬼脸’到‘以假乱真’,关键就这3个参数开关
  • 从文本到图表:Draw.io Mermaid插件如何重塑技术文档工作流
  • Umi-OCR终极指南:5分钟掌握免费离线OCR的完整解决方案
  • 告别在线学习:用SiamFC和PyTorch从零搭建一个实时目标跟踪器(附完整代码)
  • 别再只用默认主题了!手把手教你给Obsidian换上10款高颜值皮肤(附GitHub链接)
  • 2026年星型卸料器制造厂家口碑精选,这五家值得一看!有名的星型卸料器口碑推荐京蓝环保显著提升服务 - 品牌推荐师
  • 从‘体素粗糙’到检测SOTA:手把手图解Voxel R-CNN中的Voxel RoI Pooling核心模块
  • 2026年3月比较好的摺景机源头厂家推荐,ZJ-217D 电脑压褶机/摺景机,摺景机公司口碑推荐 - 品牌推荐师
  • 别再只谈概念了!知识图谱在推荐系统里的实战:基于CKE的电影推荐项目搭建
  • Cadence Virtuoso实战:手把手教你搞定Bandgap电路版图的DRC与LVS(附完整流程)
  • DeepSeek总结的致力于在一分钟内将十亿行数据插入 SQLite
  • 滑动T检验实战:用MATLAB分析股票价格突变点(从数据清洗到可视化)
  • 用74LS181芯片搭建一个简易4位CPU运算器:从真值表到电路实现的保姆级教程
  • 从控制器到光伏:用TRNSYS搭建一个完整太阳能供热系统的模块选择实战
  • 2026年侧压窗公司口碑推荐榜:高性价比的侧压窗定制厂家/不错的侧压窗定制厂家/值得信赖的侧压窗生产厂家 - 品牌策略师
  • STM32F103C8T6 + MPU9250 + MPL库实战:从CubeMX配置到姿态解算(附完整代码)
  • DFT - 从Scan Chain到故障覆盖率的实战解析
  • OWL ADVENTURE小白友好测评:告别枯燥界面,这款AI工具真的不一样
  • SAP SD CMD_EI_API=>MAINTAIN 客户主数据创建实战:从零到一的完整流程解析
  • 解放桌游设计师的双手:用CardEditor实现300%效率提升的卡牌批量生成神器
  • julia小循环清新写法
  • MPU9250磁力计校准实战:从椭圆拟合到mpl库自动校准
  • 深度实战指南:OpenCore Configurator系统化配置黑苹果引导
  • ImageJ细胞计数翻车?荧光信号太散点被误删?试试这个Dilate操作(附避坑提醒)
  • 告别Keil和CubeIDE:用CLion 2025.2 + OpenOCD打造丝滑的STM32开发环境(附完整工具链下载)
  • 别再让NextCloud拖慢你的内网!保姆级Nginx配置+缓存优化,上传轻松跑满千兆
  • SAP ALV表格F4搜索帮助配置全攻略:从标准引用到自定义事件(附完整代码)
  • 别再乱用findAny了!Java Stream并行流性能优化,用对这个方法效率翻倍
  • 保姆级教程:用ADAMS 2021和MATLAB R2022a搞定六轴机器人联合仿真(附完整模型文件)
  • 最全面的山东一卡通回收指南:常见问题与误区解析 - 团团收购物卡回收