1 简介

文章主要内容包括:

  • Java 持久层技术/框架简单介绍
  • 不同场景/框架下易导致 SQL 注入的写法
  • 如何避免和修复 SQL 注入

2 JDBC

介绍

JDBC:

  • 全称 Java Database Connectivity

  • 是 Java 访问数据库的 API,不依赖于特定数据库 ( database-independent )

  • 所有 Java 持久层技术都基于 JDBC

更多请参考 http://www.oracle.com/technetwork/java/javase/jdbc/index.html

说明

直接使用 JDBC 的场景,如果代码中存在拼接 SQL 语句,那么很有可能会产生注入,如

// concat sql
String sql = "SELECT * FROM users WHERE name ='"+ name + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);

安全的写法是使用 参数化查询 ( parameterized queries ),即 SQL 语句中使用参数绑定( ? 占位符 ) 和 PreparedStatement,如

// use ? to bind variables
String sql = "SELECT * FROM users WHERE name= ? ";
PreparedStatement ps = connection.prepareStatement(sql);
// 参数 index 从 1 开始
ps.setString(1, name);

还有一些情况,比如 order by、column name,不能使用参数绑定,此时需要手工过滤,如通常 order by 的字段名是有限的,因此可以使用白名单的方式来限制参数值

这里需要注意的是,使用了 PreparedStatement 并不意味着不会产生注入,如果在使用 PreparedStatement 之前,存在拼接 sql 语句,那么仍然会导致注入,如

// 拼接 sql
String sql = "SELECT * FROM users WHERE name ='"+ name + "'";
PreparedStatement ps = connection.prepareStatement(sql);

看到这里,大家肯定会好奇 PreparedStatement 是如何防止 SQL 注入的,来了解一下

正常情况下,用户的输入是作为参数值的,而在 SQL 注入中,用户的输入是作为 SQL 指令的一部分,会被数据库进行编译/解释执行。当使用了 PreparedStatement,带占位符 ( ? ) 的 sql 语句只会被编译一次,之后执行只是将占位符替换为用户输入,并不会再次编译/解释,因此从根本上防止了 SQL 注入问题。

更详细和准确的回答,请参考:

3 Mybatis

介绍

  • 首个 class persistence framework
  • 介于 JDBC (raw SQL) 和 Hibernate (ORM)
  • 简化绝大部分 JDBC 代码、手工设置参数和获取结果
  • 灵活,使用者能够完全控制 SQL,支持高级映射

更多请参考 http://www.mybatis.org/

说明

在 MyBatis 中,使用 XML 文件 或 Annotation 来进行配置和映射,将 interfaces 和 Java POJOs (Plain Old Java Objects) 映射到 database records

XML 例子

Mapper Interface

@Mapper
public interface UserMapper {
User getById(int id);
}

XML 配置文件

<select id="getById" resultType="org.example.User">
SELECT * FROM user WHERE id = #{id}
</select>

Annotation 例子

@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id= #{id}")
User getById(@Param("id") int id);
}

可以看到,使用者需要自己编写 SQL 语句,因此当使用不当时,会导致注入问题

与使用 JDBC 不同的是,MyBatis 使用 #{} 和 ${} 来进行参数值替换

使用 #{} 语法时,MyBatis 会自动生成 PreparedStatement ,使用参数绑定 (?) 的方式来设置值,上述两个例子等价的 JDBC 查询代码如下:

String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setInt(1, id);

因此 #{} 可以有效防止 SQL 注入,详细可参考 http://www.mybatis.org/mybatis-3/sqlmap-xml.html String Substitution 部分

而使用 ${} 语法时,MyBatis 会直接注入原始字符串,即相当于拼接字符串,因而会导致 SQL 注入,如

<select id="getByName" resultType="org.example.User">
SELECT * FROM user WHERE name = '${name}' limit 1
</select>

name 值为 ' or '1'='1,实际执行的语句为

SELECT * FROM user WHERE name = '' or '1'='1' limit 1 

因此建议尽量使用 #{},但有些时候,如 order by 语句,使用 #{} 会导致出错,如

ORDER BY #{sortBy}

sortBy 参数值为 name ,替换后会成为

ORDER BY "name"

即以字符串 “name” 来排序,而非按照 name 字段排序,详细可参考 https://stackoverflow.com/a/32996866/6467552。

这种情况就需要使用 ${}

ORDER BY ${sortBy}

使用了 ${}后,使用者需要自行过滤输入,方法有:

  • 代码层使用白名单的方式,限制 sortBy 允许的值,如只能为 nameemail 字段,异常情况则设置为默认值 name

  • 在 XML 配置文件中,使用 if 标签来进行判断

    Mapper 接口方法

    List<User> getUserListSortBy(@Param("sortBy") String sortBy);

    xml 配置文件

    <select id="getUserListSortBy" resultType="org.example.User">
    SELECT * FROM user
    <if test="sortBy == 'name' or sortBy == 'email'">
    order by ${sortBy}
    </if>
    </select>

    因为 Mybatis 不支持 else,需要默认值的情况,可以使用 choose (when, otherwise)

    <select id="getUserListSortBy" resultType="org.example.User">
    SELECT * FROM user
    <choose>
    <when test="sortBy == 'name' or sortBy == 'email'">
    order by ${sortBy}
    </when>
    <otherwise>
    order by name
    </otherwise>
    </choose>
    </select>

更多场景

除了 order by 之外,还有一些可能会使用到 ${} 情况,可以使用其他方法避免,如

like 语句

如需要使用通配符 ( wildcard characters % 和 _) ,可以

  • 在代码层,在参数值两边加上 %,然后再使用 #{}

  • 使用 bind 标签来构造新参数,然后再使用 #{}

    Mapper 接口方法

    List<User> getUserListLike(@Param("name") String name);

    xml 配置文件

    <select id="getUserListLike" resultType="org.example.User">
    <bind name="pattern" value="'%' + name + '%'" />
    SELECT * FROM user
    WHERE name LIKE #{pattern}
    </select>

    <bind> 语句内的 value 为 OGNL expression,具体可参考 http://www.mybatis.org/mybatis-3/dynamic-sql.html bind 部分

  • 使用 SQL concat() 函数

    <select id="getUserListLikeConcat" resultType="org.example.User">
    SELECT * FROM user WHERE name LIKE concat ('%', #{name}, '%')
    </select>

除了注入问题之外,这里还需要对用户的输入进行过滤,不允许有通配符,否则在表中数据量较多的时候,假设用户输入为 %%,会进行全表模糊查询,严重情况下可导致 DOS,参考 http://www.tothenew.com/blog/sql-wildcards-is-your-application-safe/

IN 条件

使用 <foreach> 和 #{}

Mapper 接口方法

List<User> getUserListIn(@Param("nameList") List<String> nameList);

xml 配置文件

<select id="selectUserIn" resultType="com.example.User">
SELECT * FROM user WHERE name in
<foreach item="name" collection="nameList"
open="(" separator="," close=")">
#{name}
</foreach>
</select>

具体可参考 http://www.mybatis.org/mybatis-3/dynamic-sql.html foreach 部分

limit 语句

直接使用 #{} 即可

Mapper 接口方法

List<User> getUserListLimit(@Param("offset") int offset, @Param("limit") int limit);

xml 配置文件

<select id="getUserListLimit" resultType="org.example.User">
SELECT * FROM user limit #{offset}, #{limit}
</select>

4 JPA & Hibernate

介绍

JPA:

  • 全称 Java Persistence API

  • ORM (object-relational mapping) 持久层 API,需要有具体的实现

更多请参考 https://en.wikipedia.org/wiki/Java_Persistence_API

Hibernate:

  • JPA ORM 实现

更多请参考 http://hibernate.org/

说明

这里有一种错误的认识,使用了 ORM 框架,就不会有 SQL 注入。而实际上,在 Hibernate 中,支持 HQL (Hibernate Query Language) 和 native sql 查询,前者存在 HQL 注入,后者和之前 JDBC 存在相同的注入问题,来具体看一下

HQL

HQL 查询例子

Query<User> query = session.createQuery("from User where name = '" + name + "'", User.class);
User user = query.getSingleResult();

这里的 User 为类名,和原生 SQL 类似,拼接会导致注入

正确的用法:

  • 位置参数 (Positional parameter)
Query<User> query = session.createQuery("from User where name = ?", User.class);
query.setParameter(0, name);
  • 命名参数 (named parameter)
Query<User> query = session.createQuery("from User where name = :name", User.class);
query.setParameter("name", name);
  • 命名参数 list (named parameter list)
Query<User> query = session.createQuery("from User where name in (:nameList)", User.class);
query.setParameterList("nameList", Arrays.asList("lisi", "zhaowu"));
  • 类实例 (JavaBean)
User user = new User();
user.setName("zhaowu");
Query<User> query = session.createQuery("from User where name = :name", User.class);
// User 类需要有 getName() 方法
query.setProperties(user);

Native 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 Persistence Query Language),同时也支持 native sql,因此和 Hibernate 存在类似的问题,这里就不再细说,感兴趣的可以参考 How to How to Fix SQL Injection using the Java Persistence API (JPA)

Java SQL注入学习笔记的更多相关文章

  1. sql注入学习笔记,什么是sql注入,如何预防sql注入,如何寻找sql注入漏洞,如何注入sql攻击 (原)

    (整篇文章废话很多,但其实是为了新手能更好的了解这个sql注入是什么,需要学习的是文章最后关于如何预防sql注入) (整篇文章废话很多,但其实是为了新手能更好的了解这个sql注入是什么,需要学习的是文 ...

  2. sql注入学习笔记 详解篇

    sql注入的原理以及怎么预防sql注入(请参考上一篇文章) https://www.cnblogs.com/KHZ521/p/12128364.html (本章主要针对MySQL数据库进行注入) sq ...

  3. Sql 注入----学习笔记2

    转载自:http://blog.51cto.com/quiterr/1699964 sql注入 sql注入98年第一次出现在<phrack>54期上. 注入攻击有两个关键条件,第一是用户能 ...

  4. sql注入学习笔记

    1.什么是SQL注入 SQL注入就是指web应用程序对用户输入的数据的合法性没有判断,前端传入后端的参数带有数据库查询的语句,攻击者可以构造不同的SQL语句来实现对数据库的操作. 2.SQL注入原理 ...

  5. Sql 注入----学习笔记

    先了解下CRLF,CRLF常用在分隔符之间,CR是carriage retum(ASCII 13,\r) LF是Line Feed (ASCII 10,\n), \r\n这两个字符类似于回车是用于换行 ...

  6. Java框架spring 学习笔记(十八):事务管理(xml配置文件管理)

    在Java框架spring 学习笔记(十八):事务操作中,有一个问题: package cn.service; import cn.dao.OrderDao; public class OrderSe ...

  7. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  8. Java多线程技术学习笔记(二)

    目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和w ...

  9. Java安全防御学习笔记V1.0

    Java安全防御学习笔记V1.0http://www.docin.com/p-766808938.html

随机推荐

  1. [转]关于oracle with as用法

    原文地址:https://www.cnblogs.com/linjiqin/archive/2013/06/24/3152667.html with as语法–针对一个别名with tmp as (s ...

  2. [Linux基础环境/软件]Linux下安装mysql

    我是使用免安装的包mysql-5.5.28-linux2.6-x86_64.tar.gz(在http://dev.mysql.com/downloads/可以下载到最新的包)的.直接解压,然后配置,初 ...

  3. 【Linux/CentOS】Boolean ftp_home_dir is not defined

    安装完vsftpd软件后,因为CentOS系统的SELinux安全策略默认是没有开启FTP服务,直接访问会报错500 OOPS,所以需要修改为允许使用FTP服务. 目标:希望ftp用户可以访问自己的家 ...

  4. ubuntu 14.04 gitlab 的搭建

    经过两天的尝试,gitlab 终于在 ubuntu14.04 上面搭建起来. 一.安装相关软件. // 执行如下命令: sudo apt-get install vim git curl openss ...

  5. drupal

    一 devel 模块 配置->development->generate users 添加测试账号 php区块 结构->区块->switch user 使用者切换 结构-> ...

  6. Spring Cloud Config 自动刷新所有节点

    全局刷新 详细参考:<Sprin Cloud 与 Docker 微服务架构实战>p160-9.9.2节 1.使用Spring Cloud Config 客户端时,可以使用 /refresh ...

  7. 解决华为手机图片选择无效及产生的open failed: EACCES (Permission denied)错误

    在华为手机上调起图片选择时原来的效果如下 原来的代码是 Intent intent = new Intent(); intent.setAction(Intent.ACTION_GET_CONTENT ...

  8. 路由策略和策略路由 & route-map

    今天,这个专题应用下route-map,在这个之前,有很多内容需要掌握,不是简单的制定一个路由图就可以了. -------- 本次专题理论的东西居多,但是不是复制黏贴,是加上自己的理解思想. 第一个要 ...

  9. Linux进程间通信之管道(pipe)、命名管道(FIFO)与信号(Signal)

    整理自网络 Unix IPC包括:管道(pipe).命名管道(FIFO)与信号(Signal) 管道(pipe) 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道 ...

  10. js dom 创建table标签和子属性, 以及创建多选框

    代码: <div class="Category"> <span id="Edit_headerTitle">Edit Categori ...