Java代码审计漏洞-SQL注入
Java代码审计漏洞-SQL注入
基础知识
https://www.cnblogs.com/-meditation-/p/16031461.html
https://www.cnblogs.com/-meditation-/p/16031338.html
https://www.cnblogs.com/-meditation-/p/16112699.html
https://www.cnblogs.com/-meditation-/p/16112589.html
https://www.cnblogs.com/-meditation-/p/16031523.html
个人学习笔记,直接看代码,不写奇怪的东西,分析代码写注释。
情况一:直接用servlet写SQL语句
存在漏洞代码
/**
* Vuln Code.
* http://localhost:8080/sqli/jdbc/vuln?username=joychou
*
* @param username username
*/
//RequestMapping 这个controller表示匹配的URL
@RequestMapping("/jdbc/vuln")
//@RequestParam("username")获取从前端传过来的username参数
public String jdbc_sqli_vul(@RequestParam("username") String username) {
StringBuilder result = new StringBuilder();
try {
//获取数据库连接
Class.forName(driver);
Connection con = DriverManager.getConnection(url, user, password);
if (!con.isClosed())
System.out.println("Connect to database successfully.");
// sqli vuln code 这里没有使用预处理的方式,直接将获取的参数和SQL语句拼接,导致注入。
Statement statement = con.createStatement();
String sql = "select * from users where username = '" + username + "'";
//终端打印sql
logger.info(sql);
//执行拼接好的SQL语句
ResultSet rs = statement.executeQuery(sql);
while (rs.next()) {
String res_name = rs.getString("username");
String res_pwd = rs.getString("password");
String info = String.format("%s: %s\n", res_name, res_pwd);
result.append(info);
logger.info(info);
}
rs.close();
con.close();
} catch (ClassNotFoundException e) {
logger.error("Sorry,can`t find the Driver!");
} catch (SQLException e) {
logger.error(e.toString());
}
return result.toString();
}
意思意思放个图
安全代码
@RequestMapping("/jdbc/sec")
public String jdbc_sqli_sec(@RequestParam("username") String username) {
StringBuilder result = new StringBuilder();
try {
Class.forName(driver);
Connection con = DriverManager.getConnection(url, user, password);
if (!con.isClosed())
System.out.println("Connecting to Database successfully.");
// fix code 预处理代码
String sql = "select * from users where username = ?";
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, username);
logger.info(st.toString()); // sql after prepare statement
ResultSet rs = st.executeQuery();
while (rs.next()) {
String res_name = rs.getString("username");
String res_pwd = rs.getString("password");
String info = String.format("%s: %s\n", res_name, res_pwd);
result.append(info);
logger.info(info);
}
rs.close();
con.close();
} catch (ClassNotFoundException e) {
logger.error("Sorry, can`t find the Driver!");
e.printStackTrace();
} catch (SQLException e) {
logger.error(e.toString());
}
return result.toString();
}
/**
预处理的方式,使用 ?占位。在把值传进去
*/
情况二:使用Mybatis进行数据库操作
存在漏洞代码
/**
* vuln code
* http://localhost:8080/sqli/mybatis/vuln01?username=joychou' or '1'='1
*
* @param username username
*/
@GetMapping("/mybatis/vuln01")
public List<User> mybatisVuln01(@RequestParam("username") String username) {
return userMapper.findByUserNameVuln01(username);
}
/**
这里获取username,然后传到userMapper的findByUserNameVuln01方法里,
@Autowired
private UserMapper userMapper;
正向跟踪UserMapper findByUserNameVuln01内容如下
@Select("select * from users where username = '${username}'")
List<User> findByUserNameVuln01(@Param("username") String username);
${} 本质使用Statement进行SQL语句拼接,会造成注入
*/
/**
* vul code
* http://localhost:8080/sqli/mybatis/vuln02?username=joychou' or '1'='1' %23
*
* @param username username
*/
@GetMapping("/mybatis/vuln02")
public List<User> mybatisVuln02(@RequestParam("username") String username) {
return userMapper.findByUserNameVuln02(username);
}
/**
正向跟踪UserMapper findByUserNameVuln02内容如下,未在UserMapper.java接口中写SQL语句
List<User> findByUserNameVuln02(String username);
寻找UserMapper.xml 内容如下
<select id="findByUserNameVuln02" parameterType="String" resultMap="User">
select * from users where username like '%${_parameter}%'
</select>
${_parameter} 本质使用Statement进行SQL语句拼接,会造成注入
*/
// http://localhost:8080/sqli/mybatis/orderby/vuln03?sort=1 desc%23
@GetMapping("/mybatis/orderby/vuln03")
public List<User> mybatisVuln03(@RequestParam("sort") String sort) {
return userMapper.findByUserNameVuln03(sort);
}
/**
同理跟userMapper.findByUserNameVuln03,传递了sort,
userMapper中,定义形参为order,接收sort后传递给 order1,userMapper.xml中通过order1调用
List<User> findByUserNameVuln03(@Param("order1") String order);
userMapper.xml
<select id="findByUserNameVuln03" parameterType="String" resultMap="User">
select * from users
<if test="order1 != null">
order by ${order1} asc
</if>
</select>
*/
安全代码
/**
* security code
* http://localhost:8080/sqli/mybatis/sec01?username=joychou
*
* @param username username
*/
@GetMapping("/mybatis/sec01")
public User mybatisSec01(@RequestParam("username") String username) {
return userMapper.findByUserName(username);
}
//userMapper.jva
@Select("select * from users where username = #{username}")
User findByUserName(@Param("username") String username);
/**
* http://localhost:8080/sqli/mybatis/sec02?id=1
*
* @param id id
*/
@GetMapping("/mybatis/sec02")
public User mybatisSec02(@RequestParam("id") Integer id) {
return userMapper.findById(id);
}
//userMapper.xml
<select id="findById" resultMap="User">
select * from users where id = #{id}
</select>
/**
* http://localhost:8080/sqli/mybatis/sec03
*/
@GetMapping("/mybatis/sec03")
public User mybatisSec03() {
return userMapper.OrderByUsername();
}
//userMapper.xml
<select id="OrderByUsername" resultMap="User">
select * from users order by id asc limit 1
</select>
@GetMapping("/mybatis/orderby/sec04")
public List<User> mybatisOrderBySec04(@RequestParam("sort") String sort) {
return userMapper.findByUserNameVuln03(SecurityUtil.sqlFilter(sort));
}
}
//userMapper.xml
//过滤mybatis中order by不能用#的情况。 严格限制用户输入只能包含<code>a-zA-Z0-9_-.</code>字符。下面有介绍另一种
<select id="findByUserNameVuln03" parameterType="String" resultMap="User">
select * from users
<if test="order != null">
order by ${order} asc
</if>
</select>
//SecurityUtil.sqlFilter(sort)
public static String sqlFilter(String sql) {
if (!FILTER_PATTERN.matcher(sql).matches()) {
return null;
}
return sql;
}
知识补充
@Select注解的目的是为了取代xml中的select标签,只作用于方法上面。
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select
{
String[] value();
}
/**
源码说明了
(1)@Select注解只能修饰方法
(2)@Select注解的值是字符数组。
@Select注解的值是字符数组,但是真正生效的应该是最后那条SQL语句。
*/
//普通的字符串值,只能实现变量的替换功能
@Select("select * from t_person where id = #{id}")
Person selectPersonById(Integer id);
@Insert,@Update... 同理
<select id="findByUserNameVuln02" parameterType="String" resultMap="User">
select * from users where username like '%${_parameter}%'
</select>
//${_parameter} :当只有一个参数,可以使用_parameter,它就代表了这个参数
//!FILTER_PATTERN.matcher(sql).matches()
//方法java.time.Matcher.matches()将给定区域与指定模式进行匹配。如果区域序列与Matcher的模式匹配,则返回true,否则返回false。
Mybatis框架下易产生SQL注入漏洞场景分析
在基于Mybatis框架的Java白盒代码审计工作中,通常将着手点定位在Mybatis的配置文件中。通过查看这些与数据库交互的配置文件来确定SQL语句中是否存在拼接情况,进而确立跟踪点。通过总结,Mybatis框架下易产生SQL注入漏洞的情况主要分为以下三种:
模糊查询like
Select * from news where title like ‘%#{title}%’,
//由于这样写程序会报错,研发人员将SQL查询语句修改如下:
Select * from news where title like ‘%${title}%’,
//在这种情况下我们发现程序不再报错,但是此时产生了SQL语句拼接问题,如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。
in之后的参数
Select * from news where id in (#{id}),
//由于这样写程序会报错,研发人员将SQL查询语句修改如下:
Select * from news where id in (${id}),
//修改SQL语句之后,程序停止报错,但是却引入了SQL语句拼接的问题,如果研发人员没有对用户输入的内容做过滤,势必会产生SQL注入漏洞。
order by之后
Select * from news where title =‘京东’ order by #{time} asc,
//由于发布时间time不是用户输入的参数,无法使用预编译。研发人员将SQL查询语句修改如下:
Select * from news where title =‘京东’ order by ${time} asc,
//修改之后,程序通过预编译,但是产生了SQL语句拼接问题,极有可能引发SQL注入漏洞。
Mybatis框架下SQL注入漏洞修复建议
模糊查询like
select * from news where tile like concat(‘%’,#{title}, ‘%’),
//用concat,采用预编译机制,避免了SQL语句拼接的问题,从根源上防止了SQL注入漏洞的产生。
in之后的参数
select * from news where id in
<foreach collection="ids" item="item" open="("separator="," close=")">#{item} </foreach>
/**
一个个参数预编译拼接SQL语句查询
<foreach></foreach>标签 当传入参数为数组或者集合时进行遍历
collection:指定输入对象中集合属性
item:每次遍历生成的对象
open:开始遍历时拼接的串
separator:每次迭代的分隔符
close:结束遍历时两个对象需要拼接的串
*/
order by之后
/**前面用正则匹配的方式
针对order by这种情况研发人员还可以在java层面做映射来进行解决。如当存在发布时间time和点击量click两种排序选择时,我们可以限制用户只能输入1和2。参数化接受请求。
*/
实战中的SQL注入漏洞挖掘
1、自动化工具扫描,一般使用fortify,根据扫出来的结果去做一个跟踪。
2、使用全局搜索的功能搜索Statement 、${ 根据查找的结果进行回溯。
如:${
IDEA 的Free Mabatis Tool插件可以在UserMapper.xml中直接让你找到对应方法的引用文件
Mac下 cmd+鼠标左键
windows下 ctrl+鼠标左键
回溯到对应的controller
可以看到整个过程没有任何的过滤,直接获取参数传入,同时没有采用预编译的方式写SQL拼接,所以产生了注入。
本文来自博客园,作者:九天揽月丶,转载请注明原文链接:https://www.cnblogs.com/-meditation-/articles/16152500.html