jdbc注入

经典拼接

  1. @RequestMapping("/jdbc/vuln")
  2. public String jdbc_sqli_vul(@RequestParam("username") String username) {
  3. StringBuilder result = new StringBuilder();
  4. try {
  5. Class.forName(driver);
  6. Connection con = DriverManager.getConnection(url, user, password);
  7. if (!con.isClosed())
  8. System.out.println("Connect to database successfully.");
  9. // sqli vuln code
  10. Statement statement = con.createStatement();
  11. String sql = "select * from users where username = '" + username + "'";
  12. logger.info(sql);
  13. ResultSet rs = statement.executeQuery(sql);
  14. ......
  15. }

代码逻辑非常简单,直接一句:@RequestParam得到输入

RequestMapping是一个用来处理请求地址映射的注解

先测试了数据库是否正常连接,然后执行SQL语句,属于拼接,注入漏洞显而易见。而说这个的重点不在这里,而是去挖掘此类漏洞的时候。使用CodeQL去查找这种形式的注入,首先要找项目中所有以*Mapping结尾的注释,然后get其输入,也就是@RequestParam,通过数据污点追踪到最后执行方法*.executeQuery()

  1. //主要逻辑语句
  2. exists(Call call |
  3. sink.asExpr() = call.getArgument(0) and
  4. call.getCallee().hasQualifiedName("java.sql", "Statement", "executeQuery")
  5. )

预编译

  1. @RequestMapping("/jdbc/sec")
  2. public String jdbc_sqli_sec(@RequestParam("username") String username) {
  3. StringBuilder result = new StringBuilder();
  4. try {
  5. Class.forName(driver);
  6. Connection con = DriverManager.getConnection(url, user, password);
  7. if (!con.isClosed())
  8. System.out.println("Connecting to Database successfully.");
  9. // fix code
  10. String sql = "select * from users where username = ?";
  11. PreparedStatement st = con.prepareStatement(sql);
  12. st.setString(1, username);
  13. logger.info(st.toString()); // sql after prepare statement
  14. ResultSet rs = st.executeQuery();
  15. ......
  16. }

这里代码逻辑也很清晰,使用了预编译,先使用占位符然后再赋值

  1. st.setString(1, username);

关于PreparedStatement.setString():将指定的参数设置为给定的Java字符串值。驱动程序将其发送到数据库时将其转换为SQL VARCHAR或LONGVARCHAR值(取决于参数的大小相对于驱动程序对VARCHAR值的限制)。也就是说在启用预编译的情况下,在进入数据库执行时,会自动加上引号(即转换字符串)。

最后再去执行

  1. ResultSet rs = st.executeQuery();

下面调试下看看预编译都做了些什么,在executeQuery()下断点

进入跟进🍥SQL注入靶场代码实例解析 - 图1

发现什么有用的信息都没有,继续向下

🍥SQL注入靶场代码实例解析 - 图2

跟进查看,sql语句(含有占位符的)和用户输入的内容时分成两部分的,

🍥SQL注入靶场代码实例解析 - 图3

漏洞常见处

  1. 预编译无法处理order by,因为order by不能参数化。原因是 order by 子句后面需要加字段名或者字段位置,而字段名是不能带引号的,否则就会被认为是一个字符串而不是字段名,简单来说就是会报错。

使用 PreapareStatement 将会强制给参数加上',所以,在使用 order by 语句时就必须得使用拼接的 Statement,所以就会造成 SQL 注入,需要手动过滤

  1. String sql = "Select * from news where title =?" + "order by '" + time + "' asc"
  1. 使用like语句
  1. String sql = "select * from users where password like '%" + con + "%'"; //存在sql注入
  1. 没有手动过滤%

预编译无法处理这个符号,需要加入黑名单,否则会造成慢查询,进而造成DOS

  1. 以为自己使用了预编译,实则并没有

预编译必须使用?做占位符

  1. String sql = "select * from users where username = '" + username + "'";
  2. PreparedStatement st = con.prepareStatement(sql);
  3. logger.info(st.toString());
  4. ResultSet rs = st.executeQuery();
  1. in语句

删除语句中可能会存在此类语句,由于无法确定delIds含有对象个数而直接拼接sql语句

  1. String sql = "delete from users where id in("+delIds+"); //存在sql注入

MyBatis

MyBatis存在注入的主要一点就是${Parameter}

🍥SQL注入靶场代码实例解析 - 图4

跟进查看,具体为什么MyBatis不在配置文件*.xml中写着,这属于Spring自身的开发特点,也可以使用Java类作为配置文件,使用注释来开发,以实现这种效果。

  1. @Select("select * from users where username = '${username}'")
  2. List<User> findByUserNameVuln01(@Param("username") String username);

可以看到使用了${}的就是直接拼接。

在MyBatis中使用预编译则是换了一种方式#{Parameter}

  1. @Select("select * from users where username = #{username}")
  2. User findByUserName(@Param("username") String username);

Mybatis中易发生SQL注入的情况

  1. like关键字模糊查询
  1. <select id="findByUserNameVuln02" parameterType="String" resultMap="User">
  2. select * from users where username like '%${_parameter}%'
  3. </select>

原因就是在使用like进行模糊查询的时候,使用#{}会报错,那么这个时候开发会将#{}改为${}来解决报错,但是这样就会造成拼接问题。

正确的解决方法应该是

  1. <select id="findByUserNamesec" parameterType="String" resultMap="User">
  2. select * from users where username like concat('%',#{_parameter}, '%')
  3. </select>

不同数据库中

  1. mysql:
  2. select * from users where username like concat('%',#{username},'%')
  3. oracle:
  4. select * from users where username like '%'||#{username}||'%'
  5. sqlserver:
  6. select * from users where username like '%'+#{username}+'%'
  1. 使用in语句

同样的,使用in语句也会报错,改为${}则会造成拼接。

  1. <select id="findByUserNameVuln04" parameterType="String" resultMap="User">
  2. select * from users where id in (${id})
  3. </select>
  1. // http://localhost:8080/sqli/mybatis/vuln04?id=1)%20or%201=1%23
  2. @GetMapping("/mybatis/vuln04")
  3. public List<User> mybatisVuln04(@RequestParam("id") String id) {
  4. return userMapper.findByUserNameVuln04(id);
  5. }

正确的改写方法是使用 foreach

  1. <select id="findByIdSec04" parameterType="String" resultMap="User">
  2. SELECT
  3. * from users WHERE id IN <foreach collection="id" item="id" open="(" close=")" separator=",">
  4. #{id}
  5. </foreach>
  6. </select>

注意定义接口的时候,类型就要写成List了

  1. @GetMapping("/mybatis/sec04")
  2. public List<User> mybatisSec04(@RequestParam("id") List id){
  3. return userMapper.findByIdSec04(id);
  4. }

在Mapper中写一下

  1. List<User> findByIdSec04(@Param("id") List id);

🍥SQL注入靶场代码实例解析 - 图5

  1. 至于order by则需要做好过滤,写个Filter过滤器。

MyBatis-plus框架下的SQL注入

参考:https://xz.aliyun.com/t/11672#toc-8

mark一下~

Hibernate框架下的SQL注入

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 HQL语句,也是采用拼接的方式做增删查改等操作。而HQL一般采用参数绑定来有效避免注入问题。
  1. 命名参数
  1. Query<User> query = session.createQuery("from users name = ?1", User.class);
  2. String parameter = "g1ts";
  3. Query<User> query = session.createQuery("from users name = :name", User.class);
  4. query.setParameter("name", parameter);
  1. 位置参数
  1. String parameter = "g1ts";
  2. Query<User> query = session.createQuery("from users name = ?1", User.class);
  3. query.setParameter(1, parameter);
  1. 命名参数列表
  1. List<String> names = Arrays.asList("g1ts", "g2ts");
  2. Query<User> query = session.createQuery("from users where name in (:names)", User.class);
  3. query.setParameter("names", names);
  1. 类实例
  1. user1.setName("g1ts");
  2. Query<User> query = session.createQuery("from users where name =:name", User.class);
  3. query.setProperties(user1);
  1. HQL拼接
这种方式是最常用,而且容易忽视且容易被注入的,通常做法就是对参数的特殊字符进行过滤,可以使用 Spring工具包的<font style="color:rgb(51, 51, 51);">StringEscapeUtils.escapeSql()</font>对参数进行过滤:(算是黑名单吧)
  1. import org.apache.commons.lang.StringEscapeUtils;
  2. public static void main(String[] args) {
  3. String str = StringEscapeUtils.escapeSql("'");
  4. System.out.println(str);
  5. }

Hibernate也支持原生SQL语句执行,也会有使用拼接而直接造成SQL注入问题。