前言
在日常开发中,数据库操作几乎是绕不开的环节。很多同学写查询语句的时候,习惯直接用字符串拼接,比如:
string sql = "select * from users where username = '" + username + "' and password = '" + password + "'";
乍一看挺正常,但一旦碰上心怀不轨的攻击者,后果就可能非常严重。这就是我们常说的 sql 注入 问题。
什么是 sql 注入
sql 注入的本质就是:
用户的输入被直接拼接到 sql 语句中,没有做任何防护,导致数据库把攻击者的输入当成真正的 sql 指令去执行。
举个最经典的例子:
如果登录接口这样写:
string username = "admin"; string password = "' or '1'='1"; // 攻击者输入的内容 string sql = "select * from users where username='" + username + "' and password='" + password + "'"; system.out.println(sql);
拼接后的 sql 就变成了:
select * from users where username='admin' and password='' or '1'='1'
这一句 or '1'='1'
永远成立,所以攻击者轻轻松松绕过了密码验证,直接登录成功。
真实场景下的危害
别以为这只是理论,现实中因为 sql 注入出问题的案例太多了。常见危害包括:
- 绕过身份认证:像上面的例子,用户不用知道密码也能登录。
- 数据泄露:攻击者可以拼接语句,把整个表的数据都查询出来。
- 数据篡改或删除:严重的情况下,甚至可以执行
drop table users;
直接把表删掉。 - 权限提升:通过复杂的注入方式,攻击者可能拿到数据库更高权限,导致系统全面失控。
所以在企业开发里,sql 注入算是基础中的基础,必须提前预防。
常见解决方案
那我们该怎么防范呢?有几种常见的方式:
1. 使用 preparedstatement
preparedstatement 是 jdbc 提供的预编译语句对象。它会先把 sql 模板交给数据库编译好,然后再传入参数。这样参数和 sql 逻辑严格分离,用户输入就不会被当成 sql 指令执行。
demo 代码:
import java.sql.*; public class safelogindemo { public static void main(string[] args) throws exception { string url = "jdbc:mysql://localhost:3306/testdb"; string user = "root"; string pass = "123456"; string inputuser = "admin"; string inputpass = "' or '1'='1"; connection conn = drivermanager.getconnection(url, user, pass); string sql = "select * from users where username = ? and password = ?"; preparedstatement stmt = conn.preparestatement(sql); stmt.setstring(1, inputuser); stmt.setstring(2, inputpass); resultset rs = stmt.executequery(); if (rs.next()) { system.out.println("登录成功: " + rs.getstring("username")); } else { system.out.println("用户名或密码错误"); } rs.close(); stmt.close(); conn.close(); } }
这里无论用户输入什么奇怪的密码,数据库都会把它当成一个普通的字符串参数处理,而不是 sql 逻辑。
2. mybatis 使用#{}占位符
在 mybatis 中,有两个写法经常被混淆:${}
和 #{}
。
${}
:直接拼接字符串,可能导致 sql 注入。#{}
:安全的参数绑定,底层会帮你用 preparedstatement。
错误写法(容易被注入):
<select id="getuserbyname" resulttype="user"> select * from users where username = '${username}' </select>
正确写法(推荐使用):
<select id="getuserbyname" resulttype="user"> select * from users where username = #{username} </select>
这样就算有人传了 ' or '1'='1
这样的输入,也只会作为字符串参数传入,不会破坏 sql 逻辑。
3. 使用 orm 框架封装查询
像 hibernate、jpa 这类 orm 框架,通常都已经封装了参数绑定逻辑,默认情况下就能避免注入风险。
比如用 spring data jpa:
public interface userrepository extends jparepository<user, long> { user findbyusernameandpassword(string username, string password); }
spring data jpa 底层会自动生成类似 preparedstatement 的语句,所以基本不需要担心 sql 注入问题。
总结
回过头来看,sql 注入的核心原因是 “把用户输入当成 sql 语句的一部分”。
解决的思路就是 “参数化查询”,让 sql 和用户输入严格分离。
落地建议:
- 在 jdbc 层,统一用
preparedstatement
。 - 在 mybatis 中,统一用
#{}
而不是${}
。 - 在使用 orm 框架时,不要为了图省事拼接 jpql/hql。
- 养成安全开发习惯,做代码审查时重点关注数据库查询部分。
到此这篇关于sql注入的风险与解决方案实战解析的文章就介绍到这了,更多相关sql注入内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论