【SQL】sql注入风险修复方法

一、MyBaties中#{}和${}的区别
'#'相当于对数据 加上 双引号,$相当于直接显示数据。

我们经常使用的是#{},一般解说是因为这种方式可以防止SQL注入,简单的说#{}这种方式SQL语句是经过预编译的,它是把#{}中间的参数转义成字符串,举例:

select * from table_A where name = #{name} 

预编译后,会动态解析成一个参数标记符?:

select * from table_A where name = ?

而使用${}在动态解析时候,会传入参数字符串

select * from table_A where name = 'xiaoming'

总结:
#{} 这种取值是编译好SQL语句再取值
${} 这种是取值以后再去编译SQL语句
因此#{}方式能够很大程度防止sql注入

二、存在SQL注入的情况以及修改方法
如果拼接参数是SQL的查询参数都可以通过将mybatis配置文件中的 $ 改为 #,来解决

where id=${id}  -->
where id=#{id}

where id like '%${id}%' -->
where id like concat('%',#{id},'%')

where id in (${ids})  --> 
where id in 
<foreach item='id' index='index' collection='ids' open='(' seperator=',' close=')'> 
#{id}
 </foreach>

如果SQL中动态部分是攻击者可控的,那么就会形成SQL注入
SQL注入本质是数据和代码没有分离
围绕上面两点,防御的方案也就呼之欲出了。即,如果SQL中动态部分做到攻击者不可控,或者能够严格保证数据和代码是分离的。

  1. 防御思想
    2.1 打破“攻击者可控”
    这是通用的防御思路,不仅限于防御SQL注入,防御的思路和常用手段参考:《通读:WEB应用安全(二)》。

2.2 数据代码严格分离
我们在《通读:SQL注入(认识篇)》中说明SQL注入时,举了如下SQL:

SELECT xx,yy,...,FROM product WHERE name LIKE '%kindle' or 1+1=2 and '%'='%';

仔细观察,会发现用户输入的单引号跟SQL中的单引号闭合了,从而其他输入被当成了“代码”。朴素的想法是:如果我们能保证用户的输入一定是被单引号包裹,那么输入将是当作字符串(数据)对待。

但是依靠用户编写代码可能容易错漏。比如,顺着上面的朴素想法,我们可能想到策略:把用户输入的单引号转义(如使用反斜杠转义,’),那用户输入就无法跳出单引号。

然而反斜杠自身也可能被转义,导致其无法起到转义符的作用。

解决之道是将这些工作留给BD层,用户代码和BD层建立沟通。

用户代码:呐,A这个字段我告诉你是一个int,B这个字段是一个字符串…

DB层:好的,我来保证A的输入只能是int,B的输入只能是字符串。

这个机制由预编译、占位符来提供(而我们举例场景通常称之为“拼接”),不同的语言、框架实现略有差异,但是本质是一致的。

注:数据代码严格分离其实是打破“攻击者可控”的一种手段,两者不是并列关系,为了行文方便刻意区分了。

  1. 常见场景下的防御
    注意:

由于公司以Java为主,因此如下场景均为Java相关,其他语言参考2. 防御思想 寻找方案(如python,搜索“python 预编译 防止SQL注入”)
通过2. 防御思想 ,我们容易发现预编译是一个通用可靠的方案,但是有些场景下可能不支持,我们需要通过打破“攻击者可控“来防御
3.1 无法使用预编译的场景
当SQL使用动态的表名、列名,那么就无法使用预编译了,因为预编译是需要绑定具体的表名和列名。

这时候怎么防御呢?我们需要通过打破“攻击者可控“来防御。比如检查表名是否在您定义的列表中(如"aa",“bb”,“cc”),从而保证输入是可信的。

安全部提供的公共SDK,提供了更加通用的防御方案:

Java SecSdk 防御SQL注入
Node.js SecSdk 防御SQL注入
其内部检查输入(单个字符)是否是有效的标志符,通过这一个限制保证攻击者无法注入有效的SQL,即攻击者不可控。

3.2 可以使用预编译的场景
3.2.1 原生的JDBC SQL
没有使用预编译:

Statement smt = conn.createStatement();
String sql = "SELECT id, name FROM `product` WHERE `name` LIKE '%" + keyword + "%'";
ResultSet rs = stmt.executeQuery(sql); 

使用预编译:

String sql = "SELECT `id`, name FROM `product` WHERE `name` LIKE '%?%'";
PreparedStatement pSmt = conn.prepareStatement(sql);
pSmt.setString(1, keyword);
ResultSet rs = pStmt.execute(); 

使用预编译,保证用户传入的keyword只能是“数据”,而不会是“代码”。

3.2.2 mybatis
mybatis(ibatis)提供了良好的封装,#和$是区分动态参数是使用预编译还是拼接的重要特征。

因此,除了无法使用预编译的场景,其余场景使用“#”启用预编译就能免疫SQL注入了。至于无法使用预编译的场景,3.1中已经提供防御方法了。

针对能使用预编译的mybatis场景,我们举几个例子说明:

a.常规的条件

SELECT `id`, name FROM `product` WHERE `id` = #{id};

b.LIKE 条件(针对mysql,其他DBMS提供的函数不同略有差异)

SELECT `id`, name FROM `product` WHERE `name` = CONCAT('%',#{keyword},'%');

c.IN 条件

SELECT `id`, name FROM `product` WHERE `name` IN
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
 #{item}
</foreach>

d.警惕 MyBatis generator Example中的SQL注入

MyBatis generator能大大降低编写SQL中的成本,其Example允许开发者在不同的业务场景下复用一个SQL,大部分情况下您可以放心使用MyBatis generator,但是仍需小心少量场景下的SQL注入。

如下是一个Example示例,order by ${orderByClause} 中是有SQL注入风险的,如果排序条件来自不可信输入且没有检查(如需检查,可参考上方Sdk),那么攻击者借此能注入SQL。

<sql id="Example_Where_Clause" >
    <where >
      <foreach collection="oredCriteria" item="criteria" separator="or" >
        <if test="criteria.valid" >
          <trim prefix="(" suffix=")" prefixOverrides="and" >
            <foreach collection="criteria.criteria" item="criterion" >
              <choose >
                <when test="criterion.noValue" >
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue" >
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue" >
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue" >
                  and ${criterion.condition}
                  <foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," >
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
  <select id="selectByExample" resultMap="BaseResultMap" parameterType="com.doctor.mybatisdemo.domain.userExample" >
  select
  <if test="distinct" >
    distinct
  </if>
  <include refid="Base_Column_List" />
  from user
  <if test="_parameter != null" >
    <include refid="Example_Where_Clause" />
  </if>
  <if test="orderByClause != null" >
    order by ${orderByClause}
  </if>
</select>

3.2.3 JdbcTemplate

String lastName = this.jdbcTemplate.queryForObject( 
  "select last_name from t_actor where id = ?", 
  String.class, 1212L); 

上面可能没有把您使用的场景列举出来,但是只要掌握了思想,相信您能找到防御的手段。

  1. 思考
    1)预编译是如何做到防止SQL注入的?

2)MyBatis generator Example示例中,为什么order by ${orderByClause} 中是有SQL注入风险?而其他条件没有?查看源代码找答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值