当前位置: 首页 > news >正文

JAVA代码审计之SQL注入代码审计

前言

SQL注入漏洞是对数据库进行的一种攻击方式。其主要形成方式是在数据交互中,前端数据通过后台在对数据库进行操作时,由于没有做好安全防护,导致攻击者将恶意代码拼接到请求参数中,被当做SQL语句的一部分进行执行,最终导致数据库被攻击。可以说所有可以涉及到数据库增删改查的系统功能点都有可能存在SQL注入漏洞。虽然现在针对SQL注入的防护层出不穷,但大多情况下由于开发人员的疏忽或特定的使用场景,还是会存在SQL注入漏洞的代码。

环境搭建

首先创建相关项目,源码这儿较多,后台私信即可获取演示案例源码

首先是根据提示,创建一个名为security的数据库,并想数据库中写入相关数据。这里我用的是phpStudy的mysql数据库

接着使用Navicate创建相关数据

DROP DATABASE IF EXISTS security;
CREATE DATABASE security;
USE security;CREATE TABLE users (id INT(3) NOT NULL AUTO_INCREMENT,username VARCHAR(20) NOT NULL,password VARCHAR(20) NOT NULL,PRIMARY KEY (id)
);CREATE TABLE emails (id INT(3) NOT NULL AUTO_INCREMENT,email_id VARCHAR(30) NOT NULL,PRIMARY KEY (id)
);CREATE TABLE uagents (id INT(3) NOT NULL AUTO_INCREMENT,uagent VARCHAR(256) NOT NULL,ip_address VARCHAR(35) NOT NULL,username VARCHAR(20) NOT NULL,PRIMARY KEY (id)
);CREATE TABLE referers (id INT(3) NOT NULL AUTO_INCREMENT,referer VARCHAR(256) NOT NULL,ip_address VARCHAR(35) NOT NULL,PRIMARY KEY (id)
);INSERT INTO users (id, username, password) VALUES 
(1, 'Dumb', 'Dumb'), 
(2, 'Angelina', 'I-kill-you'), 
(3, 'Dummy', 'p@ssword'), 
(4, 'secure', 'crappy'), 
(5, 'stupid', 'stupidity'), 
(6, 'superman', 'genious'), 
(7, 'batman', 'mob!le'), 
(8, 'admin', 'admin');INSERT INTO emails (id, email_id) VALUES 
(1, 'Dumb@dhakkan.com'), 
(2, 'Angel@iloveu.com'), 
(3, 'Dummy@dhakkan.local'), 
(4, 'secure@dhakkan.local'), 
(5, 'stupid@dhakkan.local'), 
(6, 'superman@dhakkan.local'), 
(7, 'batman@dhakkan.local'), 
(8, 'admin@dhakkan.com');

接着连接数据库,这里数据库是本地的,可以直接连接

然后启动即可。访问目标地址,搭建成功

http://127.0.0.1:7089/sqli/jdbc/dynamic?id=2

漏洞分析

jdbc中的SQL注入

动态拼接

SQL语句动态拼接导致的SQL注入漏洞是先前最为常见的场景。其主要原因是后端代码将前端获取的参数动态直接拼接到SQL语句中使用java.sql.Statement执行SQL语句从而导致SQL注入漏洞的出现。

两个关键点如下:动态拼接参数、使用java.sql.Statement执行SQL语句

Statement:对象用于执行一条静态的 SQL 语句并获取它的结果。
createStatement():创建一个 Statement 对象,之后可使用executeQuery()方法执行SQL语句。
executeQuery(String sql)方法:执行指定的 SQL 语句,返回单个 ResultSet 对象。	

演示案例如下:

我们来到JdbcDynamicContriller下,相关代码已经做了详细注释,这里不一一讲解。

我们主要观察以下代码:

// 创建Statement对象用于执行SQL查询
Statement statement = conn.createStatement();
//动态拼接字符串
String sql = "select * from users where id = '" + id + "'";
// 执行SQL查询并返回结果集
ResultSet rs = statement.executeQuery(sql);

id是通过请求参数传入来获取的,这里直接进行了拼接,并且是通过创建Statement对象用于执行SQL查询,executeQuery执行查询语句,这里就存在sql注入。实际演示看看。

访问以下地址可以看到正常显示

http://127.0.0.1:7089/sqli/jdbc/dynamic?id=2

后面加一个单引号,可以看到报错了

那再加一个,输入 2'' 试试呢

可以看到正常显示了,为什么会这样子呢?回到源码来进行分析一下,这里修改源码返回的内容,将返回的result修改成sql语句。

接着访问源地址,可以看到完整的sql语句了

可以看到,造成输入2''还可以正常显示的原因是,字符串造成了拼接,我们输入的2'拼接在sql语句中,闭合了单引号,因而造成的sql注入,这里简单演示一下注入流程。我就简单查询一下数据库版本吧。

这里通过order by语句已知有三列,我们数据一下语句,查询数据库版本。

' union select 1,2,version()--+

但是存在一种情况,即使是拼接,也不会造成sql注入(目前还未发现绕过方法),那就是限制输入的类型为int,我们输入其他的就会报错。

正常输入,可以看到显示正常

但是一旦输入其他的,非数字的情况,就会报错。这种情况就基本不存在sql注入了。

错误的预编译

在动态拼接中是使用Statement执行SQL语句。如果使用PreparedStatement预编译参数化查询是能够有效防止SQL注入的。但如果没有正确的使用PreparedStatement预编译还是会存在SQL注入风险的。

PreparedStatement是继承Statement的子接口。

PreparedStatement会对SQL语句进行预编译,不论输入什么,经过预编译后全都以字符串来执行SQL语句。

PreparedStatement会先使用?作为占位符将SQL语句进行预编译,确定语句结构,再传入参数进行执行查询。如下述代码:

PreparedStatement是继承Statement的子接口。
PreparedStatement`会对SQL语句进行预编译,不论输入什么,经过预编译后全都以字符串来执行SQL语句。
PreparedStatement会先使用`?`作为占位符将SQL语句进行预编译,确定语句结构,再传入参数进行执行查询。如下述代码:

首先讲解正确的预编译,示例代码如下:

这里就不存在sql注入了(还未有绕过方法)

访问目标地址,发现数据无法进行注入

但是存在一种情况,就是虽然使用的是预编译,但是还是进行的拼接进行查询。由于开发人员疏忽或经验不足等原因,虽然使用了预编译PreparedStatement,但没有根据标准流程对参数进行标记,依旧使用了动态拼接SQL语句的方式,进而造成SQL注入漏洞。

示例代码如下

重点关注下述代码:

String sql = "select * from users where username = '" + username + "'";
PreparedStatement preparestatement = conn.prepareStatement(sql);
ResultSet rs = preparestatement.executeQuery();

可以看到这里用的拼接,所以存在漏洞。

' union select 1,2,version()'

Order by注入

在SQL语句中,order by语句用于对结果集进行排序。order by语句后面需要是字段名或者字段位置。在使用PreparedStatement预编译时,会将传递任意参数使用单引号包裹进而变为了字符串。如果使用预编译方式执行order by语句,设置的字段名会被数据库认为是字符串,而不在是字段名。因此,在使用order by时,就不能使用PreparedStatement预编译了。

示例代码如下:

这里我们使用时延注入,发现有明显的时间延迟。

Mybatis中的SQL注入

在Mybatis中拼接SQL语句有两种方式:一种是占位符#{},另一种是拼接符${}。

占位符#{}:对传入的参数进行预编译转义处理。类似 JDBC 中的PreparedStatement。

比如:select * from user where id = #{number},如果传入数值为1,最终会被解析成select * from user where id = "1"。

拼接符${}:对传入的参数不做处理,直接拼接,进而会造成SQL注入漏洞。

比如:比如:select * from user where id = ${number},如果传入数值为1,最终会被解析成select * from user where id = 1。

#{}可以有效防止SQL注入漏洞。${}则无法防止SQL注入漏洞。

因此在我们对JavaWeb整合Mybatis系统进行代码审计时,应着重审计SQL语句拼接的地方。除非开发人员的粗心对拼接语句使用了${}方式造成的SQL注入漏洞。在Mybatis中有几种场景是不能使用预编译方式的,比如:order by、in,like。

漏洞案例

示例代码如下:

该网站SQL是Mabatis类型的,sql语句一般写在Mapper中

通过关键词进行全局搜索,找到Dao层,以及Servers层

跟进代码

找到相关接口以及参数

直接拿到sqlmap中跑

python sqlmap.py -u http://127.0.0.1:7089/sqli/mybatis/orderby?sort=id --batch

漏洞出发点找到

修复方案

in注入

正确的做法是需要使用foreach配合占位符#{}实现IN查询。比如:

<!-- where in 查询场景 -->
<select id="select" parameterType="java.util.List" resultMap="BaseResultMap">SELECT *FROM userWHERE name IN<foreach collection="names" item="name" open="(" close=")" separator=",">#{name}</foreach>
</select>
like注入

下面代码是正确的做法,可以防止SQL注入漏洞,如下。

SELECT * FROM users WHERE name like CONCAT("%", #{name}, "%")
表,字段名称

(Select, Order by, Group by 等)

// 插入数据用户可控时,应使用白名单处理
// example for order byString orderBy = "{user input}";
String orderByField;
switch (orderBy) {case "name":orderByField = "name";break;case "age":orderByField = "age"; break;default:orderByField = "id";
}
JDBC
String name = "foo";// 一般查询场景
String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement pre = conn.prepareStatement(sql);
pre.setString(1, name);
ResultSet rs = pre.executeQuery();// like 模糊查询场景
String sql = "SELECT * FROM users WHERE name like ?";
PreparedStatement pre = conn.prepareStatement(sql);
pre.setString(1, "%"+name+"%");
ResultSet rs = pre.executeQuery();// where in 查询场景
String sql = "select * from user where id in (";
Integer[] ids = new Integer[]{1,2,3};StringBuilder placeholderSql = new StringBuilder(sql);
for(int i=0,size=ids.length;i<size;i++) {placeholderSql.append("?");if (i != size-1) {placeholderSql.append(",");}
}
placeholderSql.append(")");PreparedStatement pre = conn.prepareStatement(placeholderSql.toString());
for(int i=0,size=ids.length;i<size;i++) {pre.setInt(i+1, ids[i]);
}
ResultSet rs = pre.executeQuery();
Spring-JDBC
JdbcTemplate jdbcTemplate = new JdbcTemplate(app.dataSource());// 一般查询场景
String sql = "select * from user where id = ?";
Integer id = 1;
UserDO user = jdbcTemplate.queryForObject(sql, BeanPropertyRowMapper.newInstance(UserDO.class), id);// like 模糊查询场景
String sql = "select * from user where name like ?";
String like_name = "%" + "foo" + "%";
UserDO user = jdbcTemplate.queryForObject(sql, BeanPropertyRowMapper.newInstance(UserDO.class), like_name);// where in 查询场景
NamedParameterJdbcTemplate namedJdbcTemplate = new NamedParameterJdbcTemplate(app.dataSource());MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("names", Arrays.asList("foo", "bar"));String sql = "select * from user where name in (:names)";
List<UserDO> users = namedJdbcTemplate.query(sql, parameters, BeanPropertyRowMapper.newInstance(UserDO.class));
Mybatis XML Mapper
<!-- 一般查询场景 -->
<select id="select" parameterType="java.lang.String" resultMap="BaseResultMap">SELECT *FROM userWHERE name = #{name}
</select><!-- like 查询场景 -->
<select id="select" parameterType="java.lang.String" resultMap="BaseResultMap">SELECT *FROM userWHERE name like CONCAT("%", #{name}, "%")
</select><!-- where in 查询场景 -->
<select id="select" parameterType="java.util.List" resultMap="BaseResultMap">SELECT *FROM userWHERE name IN<foreach collection="names" item="name" open="(" close=")" separator=",">#{name}</foreach>
</select>
Mybatis Criteria
public class UserDO {private Integer id;private String name;private Integer age;
}public class UserDOExample {// auto generate by Mybatis
}UserDOMapper userMapper = session.getMapper(UserDOMapper.class);
UserDOExample userExample = new UserDOExample();
UserDOExample.Criteria criteria = userExample.createCriteria();// 一般查询场景
criteria.andNameEqualTo("foo");// like 模糊查询场景
criteria.andNameLike("%foo%");// where in 查询场景
criteria.andIdIn(Arrays.asList(1,2));List<UserDO> users = userMapper.selectByExample(userExample);

相关文章:

  • 【面试宝藏】Redis 常见面试题解析其二
  • CAD2022下载与安装
  • 医学领域科技查新点提炼方法!---附案例分析
  • 合并两个排序链表
  • 【Python】已完美解决:(Python键盘中断报错问题) KeyboardInterrupt
  • uniapp自定义的下面导航
  • 基于Python的AI动物识别技术研究
  • 酷开科技丨酷开系统重塑家庭娱乐生态,开启家庭生活新体验
  • RAG系统进阶(五)文本分割优化技巧及代码
  • Spring学习笔记
  • Linux基础IO【II】
  • RuoyiAdmin项目搭建及Docker 部署备忘
  • 【FreeRTOS】创建第一个多任务程序
  • Polar Web【简单】upload
  • 苹果WWDC重磅发布的IOS 18、Apple Intelligence背后的技术分析!
  • 【391天】每日项目总结系列128(2018.03.03)
  • 【Leetcode】104. 二叉树的最大深度
  • Angular数据绑定机制
  • Consul Config 使用Git做版本控制的实现
  • ECMAScript入门(七)--Module语法
  • Javascript设计模式学习之Observer(观察者)模式
  • js写一个简单的选项卡
  • k8s 面向应用开发者的基础命令
  • Linux快速复制或删除大量小文件
  • PaddlePaddle-GitHub的正确打开姿势
  • python大佬养成计划----difflib模块
  • Redis的resp协议
  • Vue2.0 实现互斥
  • 从零搭建Koa2 Server
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 机器学习中为什么要做归一化normalization
  • 区块链分支循环
  • 深入浅出webpack学习(1)--核心概念
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 【运维趟坑回忆录 开篇】初入初创, 一脸懵
  • 2017年360最后一道编程题
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • %check_box% in rails :coditions={:has_many , :through}
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第5节(封闭类和Final方法)
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (四)Controller接口控制器详解(三)
  • (一)Thymeleaf用法——Thymeleaf简介
  • (转)平衡树
  • (转)原始图像数据和PDF中的图像数据
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .NET MVC第三章、三种传值方式
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • .NET 简介:跨平台、开源、高性能的开发平台