0X01 JDBC#什么时候会存在sql注入?
在代码里面, 存在着那种拼接出来的SQL语句。
String sql = "SELECT * FROM users WHERE name ='"+ name + "'";Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
#如何防止SQL注入?
使用进行参数化查询
String sql = "SELECT * FROM users WHERE name = ? ";PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, name);
在把那个东西用起来之前, 必须得确认清楚, 绝对不能有那个叫sql的东西直接拼凑在一起的这种情况发生。
那么, 到底是怎么样做到对sql注入进行有效防止的呢?
首先的话, 一个sql查询的执行这个动作嘛, 它是要分成两个步骤来完成的。
编译阶段执行阶段
当SQL的服务器引擎收到查询的时候, 它必须要按照顺序经历下面的这些阶段, 每一个环节都不能少。
在解析和规范化这个部分里, 会把查问的语法还有语义都检查一遍。它会去弄明白, 那些查问里提到的表是不是存在。同时也会去弄明白, 那个查询里面用的那些列是不是存在。这里面还会做非常多别的事情。但是这些事情我们就不仔细去说了。
到了编译阶段的时候, 查询里面有像选择、从、位置之类的这些关键字会被变成一种特定的格式。这种格式是机器能够直接去读懂的。这其实是一个解释查问的阶段。在这个阶段里会决定要去做什么样的相应动作。这里面上也会有许多别的工作需要去做。但是同样的, 这些工作我们也不去详细描述了。
我们要查询一下优化方面的计划, 这个计划的目的是去建立一个决策树的内容, 然后借着这个树的帮助来找到关于执行查询的方式到底是个什么样子。它会去找能够执行这个查询的办法一共有多少种, 同时还会去看跟每一种办法相关的成本到底是执行查询所产生的什么结果。最后它会把那个用来执行查询并且最好的计划给选出来。
再说说缓存方面的事, 就是在刚才说的查询优化当中所选出来的那个最佳计划就会被放到缓存里面存起来, 这样当下一次再有相同的查询进来的时候, 就没必要再去重新走一遍第一阶段、第二阶段以及第三阶段了。当下次有人来查的时候, 它会直接在缓存里面找一找, 然后就从那个地方拿出来拿去用。
到了执行这个环节, 把那个给的查询弄明白并且做完操作, 最后把数据打包成对象交还到用户手里。
那么, 当这个SQL语句被传入之后, 服务器引擎会具体地怎么做呢。
1.一开始传进来的那个东西, 是那种带着占位符的语句, 它根本就不是一个完整的 SQL 查询语句。
2.其次, 在SQL 引擎接收到了请求以后, 这个请求都会经历下面所说的这些阶段。
1.对数据进行仔细拆解和解析, 并且完成规范化处理。
2.编译阶段
3.查询优化计划
4.缓存指的是那个地方会放着已经编译好的查询, 而这样的查询里面是带着所谓的占位符的。
3.在这个时期, 含有占位符的句子经过了一到四个步骤处理, 已经被编译成机器能看懂的格式。
4.在程序运行的时候, 当来自用户那边的数据进到系统里来之后, 系统就会去那个缓存空间里面找到事先编译好的查询语句, 然后把这些语句里面留出来的空白位置, 就用那些用户提交过来的数据给填上, 在这个时候, 负责处理SQL语言的那个引擎部分, 会把从用户那里拿到的这些数据仅仅看作是实实在在的数据材料, 而不会再把它们当成是需要再次进行拆解分析或者重新编译处理的带有语法的代码指令。
参考链接:
0X02 # 有基于注解和XML配置文件的用法
XML 例子
@Mapperpublic interface UserMapper {
User getById(int id);
}
这个XML格式的文件, 是用来做配置用的。
SELECT * FROM user WHERE id = #{id}
例子
@Mapperpublic interface UserMapper {
@Select("SELECT * FROM user WHERE id= #{id}")
User getById(@Param("id") int id);
}
使用#{} 等价使用
所以说, 通过那种叫做 #{} 的方式去操作, 就可以好好地避免出现SQL注入这种问题。
这个符号${}在意义上是与字符串的拼接这一种情况是相等的, 它里面存在着SQL注入这一安全问题。
可是那个占位符的东西, 在用于order by、like、in以及limit等语句的时候是不适合的, 因为在预编译的那个阶段, 系统会自动给它加上引号, 这样一来就会把原本的语义给破坏了。
执行修复操作, 同时实施过滤措施。
在 ${} 使用的时候, 需要使用者自己去过滤输入内容。具体的操作方式是采取以下这些办法:
除了 order by 之外,还有一些可能会使用到 ${} 情况,可以使用其他方法避免,如
like 语句
如果是在需要去使用通配符的情况下, 也就是百分号还有那个单引号里面的下划线这些符号的话, 大家可以去进行相关操作。
除了注入问题之外, 这里还需要对用户的输入进行过滤。不允许有通配符, 否则在表中数据量较多的时候, 假设用户输入为 %%, 会进行全表模糊查询, 严重情况下可导致 DOS。参考。
IN 条件
使用 和 #{}
接口方法
List getUserListIn(@Param("nameList") List nameList);xml 配置文件
SELECT * FROM user WHERE name in
open="(" separator="," close=")">
#{name}
具体的状况, 你能够去查看一部分内容。
limit 语句
直接去使用那个 #{} 符号就可以了。
接口方法
List getUserListLimit(@Param("offset") int offset, @Param("limit") int limit);xml 配置文件
SELECT * FROM user limit #{offset}, #{limit}
第三步是JPA和相关的技术点。
在这个里面, 它是支持HQL以及Query和sql这两种查询方式的。前面那个HQL, 是存在HQL注入问题的。后面那个sql, 是和之前JDBC遇到的情况一样的, 也存在那种注入问题。接下来我们来具体分析一下这些情况。
#HQL
HQL 查询例子
Query query = session.createQuery("from User where name = '" + name + "'", User.class);User user = query.getSingleResult();
这里的 User 是类名, 它的这个情况跟原生的 SQL 那种拼接方式类似, 一旦进行拼接就会容易导致注入的发生。
使用的方法要是对的。
Query query = session.createQuery("from User where name = ?", User.class);query.setParameter(0, name);
Query query = session.createQuery("from User where name = :name", User.class);query.setParameter("name", name);
Query query = session.createQuery("from User where name in (:nameList)", User.class);query.setParameterList("nameList", Arrays.asList("lisi", "zhaowu"));
User user = new User();user.setName("zhaowu");
Query query = session.createQuery("from User where name = :name", User.class);
// User 类需要有 getName() 方法
query.setProperties(user);
# SQL
存在 SQL 注入
String sql = "select * from user where name = '" + name + "'";// deprecated
// Query query = session.createSQLQuery(sql);
Query query = session.createNativeQuery(sql);
通过运用参数的绑定技术, 将具体的数值设定给对应的参数项。
String sql = "select * from user where name = :name";// deprecated
// Query query = session.createSQLQuery(sql);
Query query = session.createNativeQuery(sql);
query.setParameter("name", name);
#JPA
在利用JPA的时候, 我们会使用到JPQL也就是Java查询语言。与此同时它也支持通过SQL进行数据操作, 正因为如此, 所以它和直接使用SQL的方式存在着非常类似的麻烦之处。
关于这些烦心的事情我在这里就不继续往下啰嗦了。如果你对此真的非常感兴趣, 想要了解更多的细节, 你可以自己去参考下面提到的这个教程, 即如何利用Java API也就是JPA工具来解决关于SQL方面的各种棘手难题。