V&N面试得到的收获

前言

前两天面了V&N,面试中遇见了很多问题,给我面试的师傅也热心的给出了解答。

先找一下自己得不足叭:

  1. 开发不够深入,对java得理解还是不够熟悉和了解。
  2. 爆肝了一段时间的ctf题,但是缺少对文章及其他重要的东西得研究,P3师傅也说不能只面向题目
  3. 知识没有很好的联系起来,理解不够透彻
  4. 面试时好紧张啊,总是打错字,想问题也不够全面,也有一部分是看到许多的大师傅,心里发慌叭。要好好研究技术了。

关于sql注入&预编译

引用师傅给我的问题

123

当时还好打开了自己的项目看了一眼,仔细看看自己写过的项目,发现拼表操作确实是这样的

(简单操作得Mapper.xml都是自己写,比较繁杂的都百度,没有好好研究吃了大亏),

原来确实有一些参数,还是必须使用${}得。

下面对原理进行解读,大佬轻喷,敬请批证。参考了网上各路大佬的解释。

什么是预编译

sql 预编译指的是数据库驱动在发送 sql 语句和参数给 DBMS 之前对 sql 语句进行编译,这样 DBMS 执行 sql 时,就不需要重新编译。

可以理解为,参数化处理得方式把我们传入的值全部团成一团,丢进去。

${}和#{}得区别

${}"是做简单的字符串替换,即将传入的值直接拼接到SQL语句中,且不会自动加单引号。

"#{}"是将传入的值先用?作为占位符替换,之后在两边加上单引号,以字符串方式处理

${ } 的变量的替换阶段是在动态 SQL 解析阶段,而 #{ }的变量的替换是在 DBMS

例如:
select * from userInfo where userName = #{name};
会被解析为
select * from userInfo where userName = ?;

${}和#{}用法提示(很重要)

  1. 能使用 #{ } 的地方就用 #{ },这样得好处有两点:

    1. 性能考虑,相同的预编译 sql 可以重复利用。

    2. 预编译这会很好的处理 sql 注入问题

  1. 表名作为变量时,必须使用 ${ }

    原理解释:

    因为,表名是字符串,使用 sql 占位符替换字符串时会带上单引号 '',会导致 sql 语法错误

    原来如此……(●ˇ∀ˇ●)。

    那么我们举个例子叭,例如:

    mapper.xml中写了

    select * from #{tableName} where name = #{name};

    预编译之后的sql 变为:

    select * from ? where name = ?;

    假设传入的参数为

    tableName = "userInfo" , name = "h3zh1",

    那么在占位符进行变量替换后,sql 语句变为

    select * from 'userInfo' where name='h3zh1';

    很明显'userInfo'是语法错误得,单引号是不可以用在表名上的,但是Tab键上面的``确实可以奥,下图可以作为参考。

  2. order by必须用${}

    order by也是同理!

    order by后一般是接字段名,而字段名是不能带引号的。

    继续举例~

    select * from 'userInfo' order by id;
    如果我们应用了#{}预编译来参数化。
    那么id就成了'id'。
    select * from 'userInfo' order by 'id';    
    语法错误,当然就game over了

    往根里说:

    like关键字也是可以预编译的,不过需要一些简单的处理。

    不只order by,凡是字符串但又不能加引号的位置都不能参数化;包括sql关键字、库名表名字段名、函数名等等。

必须使用${}时,好的防御手段

这一段参考大佬的博客。

不能参数化,不管怎么拼接,最终都是和使用“+”号拼接字符串的功效一样:拼成了sql语句缺不能防御sql注入的效果。

好在的一点是,不管是sql关键字,还是库名、表名、字段名、函数名对于后台开发者来说他的集合都是有限的,更准确点应该说也就那么几个。

这时我们应可以使用白名单的这种针对有限集合最常用的处理办法进行处理,如果传来的参数不在白名单列表中,直接返回错误即可。

我参考大佬写的if句,仿写了一个比较简单代码示例 (param in whiteList)简单表示:如果参数在白名单中则为true , 否则为false。

public void juge(String param){

        if( param in whiteList){

            System.out.println("你得参数是白名单可以通过检验");

        } else {

            System.out.println("你得参数不能通过白名单的校验");    
        }

}

最后补充几个师傅教我的知识

问题:
如果我mybatis的jdbc写了 select * from xxx where id= #{id}
并且jdbc开启了useServerPrepStmts=true
如果我查的是mysql 然后我用wireshark抓包 一次查询我能抓到几条发往mysql端口的报文?
答案:
答案是两条 第一条预编译报文 第二条查询报文 如果算关闭连接报文的话 三条

<!-- Intger类型的预编译 -->
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from t_book
    where id = #{id,jdbcType=INTEGER}
  </select>

<!-- String类型 -->
<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List" />
    from t_book
    where name = #{name,jdbcType=INTEGER}
  </select>

<!-- !!!!!!!!!!!!强调一下like关键字也是可以预编译的 -->
<select id="selectByFuzz" parameterType="java.lang.String" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List" />
    from t_book
    where name like concat('%',#{name,jdbcType=INTEGER},'%') limit 1
</select>

截图:

预编译:

image-20200507234655217

执行:

image-20200507235548183


预编译:

image-20200507234802545

执行:

image-20200507235510776

作为一个高龄又菜的离谱得ctfer压力真得很大啊,很侥幸的过了面试,希望。
希望可以融入大家伙和师傅们一起学习。

参考:
https://www.cnblogs.com/lsdb/p/12084038.html
https://www.freebuf.com/articles/web/216336.html
https://www.iteye.com/blog/zhangxugg-163-com-1835721