Java中SQL注入审计及修复

java代码审计系统课程--代码审计视频教程-信息安全-CSDN程序员研修院少写“漏洞” 了解常见代码安全 提升代码安全能力 代码不被黑客黑-https://edu.csdn.net/course/detail/32634

sql注入

漏洞原理

在常见的web漏洞中,SQL注入漏洞较为常见,危害也较大。攻击者一旦利用系统中存在的SQL注入漏洞来发起攻击,在条件允许的情况下,不仅可以获取整站数据,还可通过进一步的渗透来获取服务器权限,从而进入内网。
注入攻击的本质,是把用户输入的数据当做代码执行。这里有两个关键条件,第一个是用户能够控制输入;第二个是原本程序要执行的代码,拼接了用户输入的数据。接下来说下SQL注入漏洞的原理。
举个栗子。

当用户发送GET请求:
http://www.xxx.com/news.jsp?id=1

这是一个新闻详情页面,会显示出新闻的title和content,程序内部会接收这个id参数传递给SQL语句,SQL如下:

SELECT title,content FROM news WHERE id = 1

这是SQL的原义,也是程序员想要得到的结果,但是如果用户改变了id的内容,修改成如下:

http://www.jd.com/news.jsp?id=1 and 1=2 UNION SELECT userna-me, password FROM admin

此时内部程序执行的SQL语句为:

SELECT title,content FROM news WHERE id = 1 and 1=2 UNION SELECT username, password FROM admin

这条SQL的原义就会被改变,导致将管理员数据表中的用户名显示在页面title位置,密码显示在页面content位置,攻击成功。

jdbc

在上古时期,人们往往这么从数据库获取数据。

public User getUserById(String id) throws SQLException {
    Connection connection = JDBCTOOLS.getConnection();
        String sql = "select id,username from user where id=" + id;
        Statement statement = connection.createStatement();
    ResultSet resultSet = statement.executeQuery(sql);
​
    resultSet.next();
    int userId = resultSet.getInt(1);
    String username = resultSet.getString(2);
    User user = new User(userId, username);
    return user;
}

通过拼接字符串来构建sql语句,其中又有用户可控的部分,很容易出现问题。

直接拼接实例代码

http://localhost:8080/sqli/jdbc/vul?username=joychou

预编译处理后的代码(修复后代码)

http://localhost:8080/sqli/jdbc/sec?username=joychou

后来,出现了预编译机制,但是预编译只能处理查询参数,很多场景下仅仅使用预编译是不够的。

  • like

like拼接实现代码:

http://localhost:8080/sqli/jdbc/like?username=joy

在使用模糊查询的场景中,

String sql = "select * from user where username like '%?%'";

这种写法是无法进行预编译的,程序会报错。
like预处理报错

http://localhost:8080/sqli/jdbc/likesec?username=joy

like预处理

http://localhost:8080/sqli/jdbc/likesec?username=joy

  • order by

需要按照时间、id等信息进行排序的时候,也是无法使用预编译的。 sort=123

String sort = req.getParameter("sort");

String sql = "select * from user order by ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql); //预编译
preparedStatement.setString(1, sort); //绑定参数
ResultSet resultSet = preparedStatement.executeQuery();

如果像上面这样强行使用预编译,数据库会将字段名解析为字符串,即实际执行的sql为

select * from user order by 'sort';

无法达到实际需求。

总结:jdbc方式进行拼接的,可以直接使用预处理来规避sql注入,但是如果有like、order by 进行参数拼接不能直接使用预处理来解决,必须在set处把%拼接上。

Mybatis

前置知识:了解Mybatis框架映射关系
mapper定位java代码

parm定位方法

List<User> findByUserNameVuln02(String username);
​
定位到UserMapper.xml中的id
​
  <select id="findByUserNameVuln02" parameterType="String" resultMap="User">
        select * from users where username like '%${_parameter}%'
    </select>

Mybatis中两种数据库拼接方法
${xx}是直接拼接
#{xxx}预处理后拼接 //string int float。。。
mybatis有两种写法,一种是使用@Param 注解:

@Mapper
public interface CategoryMapper {
    @Select("select * from category_ where name= '${name}' ")
    public CategoryM getByName(@Param("name") String name);
}

1、${}直接拼接,类似jdbc中的直接拼接审计方法

http://localhost:8080/sqli/mybatis/vuln01?username=joychou
​
  / * http://localhost:8080/sqli/mybatis/vuln01?username=joychou' or '1'='1
     */
    @GetMapping("/mybatis/vuln01")
public List<User> mybatisVuln01(@RequestParam("username") String username) {
        return userMapper.findByUserNameVuln01(username);
    }
​
这里使用的是mybatis来进行SQL查询,获取参数username后使用userMapper.findByUserNameVuln01(username)来进行查询。
​
来看一下findByUserNameVuln01。MyBatis支持两种参数符号,一种是#,另一种是$。这里的参数获取使用的是${username},而不是#{username},而${username}是直接将参数拼接到了SQL查询语句中,就会造成SQL注入。
​
@Select("select * from users where username = '${username}'")
List<User> findByUserNameVuln01(@Param("username") String username);

注入语句:

http://localhost:8080/sqli/mybatis/vuln01?username=joychou' or '1'='1

${}修复方法

采用#{},也就是预处理的形式

http://localhost:8080/sqli/mybatis/sec01?username=joychou
@Select("select * from users where username = #{username}")
User findByUserName(@Param("username") String username);

2、Mybatis中like审计方法

访问路径:

http://localhost:8080/sqli/mybatis/vuln02?username=admin

这里使用的是findByUserNameVuln02(String username);来进行查询,来看一下UserMapper.xml,也就是他的XML映射文件。

List<User> findByUserNameVuln02(String username);
​
UserMapper.xml:
<select id="findByUserNameVuln02" parameterType="String" resultMap="User">
    select * from users where username like '%${_parameter}%'
</select>
    
     _parameter是Mybatis的内置参数,代表整个参数
​

这里传入未过滤的username后,插入到SQL语句中,就成了:select * from users where username like '%username%',${}直接拼接字符串,而且这里在like的后面不能使用#{}预编译,不然就会产生报错。
注入语句:

http://localhost:8080/sqli/mybatis/vuln02?username=admin' or '1'='1' %23
url编码,%23 #
​
<select id="findByUserNameVuln02" parameterType="String" resultMap="User">
    
    select * from users where username like '%admin' or '1'='1' 
</select>

修复建议:

可以使用like concat('%',#{username}, '%')就可以避免注入了。

http://localhost:8080/sqli/mybatis/vsec02?username=joyc
​
java代码:
List<User> findByUserNameVsec02(String username);
​
Mybatis配置:
    <select id="findByUserNameVsec02" parameterType="String" resultMap="User">
        select * from users where username like concat('%',#{_parameter}, '%')
    </select>

验证:
同理不同数据库的修复代码快:
Mysql数据库:

SELECT  *  FROM  user  WHERE  name like CONCAT('%',#{name},'%')

Oracle数据库:

SELECT  *  FROM  user  WHERE  name like CONCAT('%',#{name},'%') 
或
SELECT  *  FROM  user  WHERE  name like '%'||#{name}||'%'

Sqlserver数据库:

SELECT  *  FROM  user  WHERE  name like '%'+#{name}+'%'

DB2数据库:

SELECT  *  FROM  user  WHERE  name like '%'+#{name}+'%'  
或
SELECT  *  FROM  user  WHERE  name like '%'||#{name}||'%'

3、Mybatis中order by审计方法

访问链接:

http://localhost:8080/sqli/mybatis/orderby/vuln03?sort=1

这里使用的是findByUserNameVuln03(@Param("order") String order)来进行查询,同样也是去看UserMapper.xml。

<select id="findByUserNameVuln03" parameterType="String" resultMap="User">
    select * from users
    <if test="order != null">
        order by ${order} asc
    </if>
</select>

可以看到我们输入的参数在order by之后,也是${order}直接拼接起来了,与like相同,在这也是无法使用预编译,只能使用${}。由于没有过滤就直接拼接,很显然存在注入。
注入语句:

http://localhost:8080/sqli/mybatis/orderby/vuln03?sort=1 and (updatexml(1,concat(0x7e,(select version()),0x7e),1))

修复代码:

1、当order排序能不让用户输入就不让用户输入,后台直接写死,不传递参数到后端:

http://localhost:8080/sqli/mybatis/sec03
Java代码块:
User OrderByUsername();
​
Mysql配置:
 <select id="OrderByUsername" resultMap="User">
        select * from users order by id asc limit 1
    </select>

2、当order必须要从外界获取参数到后端代码进行拼接时候,那就先对参数进行过滤,过滤后再使用:

http://localhost:8080/sqli/mybatis/orderby/sec04?sort=1
Java代码块:
@GetMapping("/mybatis/orderby/sec04")
public List<User> mybatisOrderBySec04(@RequestParam("sort") String sort) {
    String filter_order = SecurityUtil.sqlFilter(sort);
    return userMapper.findByUserNameVuln03(filter_order);
}
​
//sqlFilter
private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\.-]+$");
 
public static String sqlFilter(String sql) {
    if (!FILTER_PATTERN.matcher(sql).matches()) {  //严格限制用户输入只能包含a-zA-Z0-9_-.
        return null;
    }
    return sql;
}
​
Mysql配置:
    
<select id="findByUserNameVuln03" parameterType="String" resultMap="User">
    select * from users
    <if test="order != null">
        order by ${order} asc
    </if>
</select>

Hibernate

@Autowired CategoryDAO categoryDAO; //依赖注入
​
@RequestMapping("/hibernate")
public String hibernate(@RequestParam(name = "id") int id) {
  Category category = categoryDAO.getOne(id);
  return category.getName();
}

hibernate即我们经常使用的orm的一种实现,如果使用已封装好的方法,那么默认是使用预编译的。需要注意的有这么几种情况:

  1. 对于一些复杂的sql语句,需要开发手写sql,此时要严格过滤用户输入。
  2. 上面提到的预编译不生效的几种场景。

关键词:

存在问题的关键词:
JDBC连接方式下:  +、like、order by
hibernate: +、like、order by
Mybatis: $

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mingzhi61

你的打赏,是我创造最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值