java代码审计:SQL注入

代码审计 专栏收录该内容
4 篇文章 0 订阅

漏洞原理

所谓SQL注入,就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击.当应用程序使用输入内容来构造动态sql语句以访问数据库时,会发生sql注入攻击。如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生sql注入。

java开发中有一些框架会托管一部分的数据库的操作,我们也需要要了解一下

漏洞形式和修复:拼接和预编译

1、JDBC:直接拼接构造sql语句,未进行过滤
将用户可控的参数值直接拼接到SQL语句中,没有经过有效的过滤,导致SQL注入。

用户可控参数类型:
(1)直接可控参数:例如从用户请求中直接获取参数request.getParameter(“xxx”)获取的xxx参数值
(2)间接可控参数:从数据库中获取参数拼接到SQL语句中,但数据库中存储的值为用户提交的数据

JDBC的直接拼接方式,存在sql注入:

request.getParameter("id")

private String getNameByUserId(String id) {
    Connection conn = getConn();//获得连接
    String sql = "select name from user where id=" + id;
    PreparedStatement pstmt =  conn.prepareStatement(sql);
    ResultSet rs=pstmt.executeUpdate();
}

审计方式:全局搜索sql、select等关键字,判断是否通过拼接形式来构造sql语句,以及构造参数是否用户可控。
预防措施,修复方法

通过预编译,可有效防止sql注入:

//安全的,预编译的,防止了sql注入
Connection conn = getConn();//获得连接
String sql = "select id, username, password, role from user where id=?"; //执行sql前会预编译号该条语句
PreparedStatement pstmt = conn.prepareStatement(sql); 
pstmt.setString(1, id); 
ResultSet rs=pstmt.executeUpdate(); 

2、Mybatis和Hibernate 框架

框架主要都是使用注解或者xml将java对象与数据库sql操作对应。

下面以 Mybatis 讲解一下

  • mybatis的maven配置
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
  • config.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>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/mybatistest"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

主要注意的点!

UserMapper.xml

正确的预编译

<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id, username, password, role
from user
where username = #{username,jdbcType=VARCHAR}
and password = #{password,jdbcType=VARCHAR}
</select>

存在漏洞的预编译

<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id, username, password, role
from user
where username = ${username,jdbcType=VARCHAR}
and password = ${password,jdbcType=VARCHAR}
</select>

#和$的区别:
1、#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号,正确的预编译,输入的参数会全部变成查询的部分

2、将传入的数据直接拼接在sql语句中 。造成sql注入如 :where username = 将传入的数据直接拼接在sql中。造成sql注入 如:where username=将传入的数据直接拼接在sql中。造成sql注入如:where username = {username},如果传入的值是111,那么解析成sql时的值为where username=111;
如果传入的值是1 and 1=1 ;,则解析成的sql为:select id, username, password, role from user where username=1 and 1=1

MyBatis框架易产生SQL注入漏洞的三种情况:

1、模糊查询

Select * from news where title like ‘%#{title}%

在这种情况下使用#程序会报错,新手程序员就把#号改成了$,这样如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。

正确写法:

select * from news where tile like concat(%,#{title},%)

2、in 之后的多个参数
in之后多个id查询时使用# 同样会报错

Select * from news where id in (#{ids})

正确用法为使用foreach,而不是将#替换为$

id in<foreach collection="ids" item="item" open="("separatosr="," close=")">#{ids} </foreach>

3、order by 之后

在order by之后直接使用#{}格式传参,会导致排序无效,所以只能需要${}格式。
例如根据前端传过来的参数,使用order by #{}格式进行排序。

select * from tableName order by #{column}  #{desc};

虽然不会报错,但也不能正确排序。
因为当使用#运算符,Mybatis会将传入的对象当成一个字符串,在进行变量替换时会加上引号,所以上面的order by语句,替换后就变成了下面的样子

select * from tableName order by 'columnName' 'desc';

若要使其能正常排序需要使用${}形式构造sql语句,将SQL查询语句修改如下:

select * from tableName order by ${column}  ${desc}

修改之后,程序通过预编译,可以正常实现排序效果。

由于使用${}形式构造sql语句,Mybatis不会进行预编译,直接把值传进去,无法防止sql注入。所以当我们需要传字段的名称时,可以考虑使用$符号,但在后台需要进行白名单限制或者传入数据校验,才能在一定程度上防止sql注入。

Hibernate防止SQL注入

对参数名称进行绑定在HQL语句中定义命名参数要用”:”开头,形式如下:

Query query=session.createQuery(“from User user where user.name=:customername and user:customerage=:age ”); 
query.setString(“customername”,name); 
query.setInteger(“customerage”,age); 

按参数位置邦定:
在HQL查询语句中用”?”来定义参数位置,形式如下:

Query query=session.createQuery(“from User user where user.name=? and user.age =?); 
query.setString(0,name); 
query.setInteger(1,age); 

setParameter()方法: ,
在Hibernate的HQL查询中可以通过setParameter()方法邦定任意类型的参数,如下代码:

String hql=”from User user where user.name=:customername ”; 
Query query=session.createQuery(hql); 
query.setParameter(“customername”,name,Hibernate.STRING); 

setProperties()方法:

Entity entity=new Entity();
entity.setXx(“xx”);
entity.setYy(100);
Query query=session.createQuery(“from Entity c where c.xx=:xx and c.yy=:yy ”); 
query.setProperties(entity);

0x05 挖掘方法
使用idea 搜索$关键字

可以先筛选xml文件搜索$,逐个分析,要特别注意mybatis-generator的order by注入

Ctrl+shift+F 调出Find in Path,筛选后缀xml,搜索$关键字

找到是mybatis的数据库文件

找到调用函数后,alt+f7查看调用链,检查中间是否被过滤

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:游动-白 设计师:我叫白小胖 返回首页

打赏作者

莫语闲语

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值