前言 最近在疯狂打游戏哈,有点懈怠了,所以赶紧去来补一下java的代码审计,上一篇的xss是找了个视频,随便写了点,这个就认真一点,依旧找个博客跟着大佬学习。 项目地址:JoyChou93/java-sec-code: Java web common vulnerabilities and security code which is base on springboot and spring security
jdbc的拼接不当导致的sql注入 在jdbc中存在两个sql语句来执行sql,一个是Statement,另一个是PreparedStatement。 那么他们的写法分别是
Statement 1 2 3 4 Connection con = DriverManager.getConnection(url, user, password);Statement statement = con.createStatement();String sql = "select * from users where username = '" + username + "'" ;ResultSet rs = statement.executeQuery(sql);
这里直接复制项目里的了。漏洞点就是username这里进行了拼接。 这里的绕过就很简单了 万能密码就可以了1' or '1' = '1
然后为了为了修复这个漏洞,官方这边又给出了PreparedStatement,进行预编译。
PreparedStatement 1 2 3 4 5 Connection con = DriverManager.getConnection(url, user, password);String sql = "select * from users where username = ?" ;PreparedStatement st = con.prepareStatement(sql);st.setString(1 , username); ResultSet rs = st.executeQuery();
至于为什么能够进行防御,这里口述一下:主要是因为在Statement中,数据库接受到的是一个完整的sql语句,但是这个语句已经是我们修改过的了,所以会实现恶意攻击。而在PreparedStatement中参数和sql语句是分开的,这里会用一个?(占位符),先将sql语句进行发送给数据库,数据库看到明白要执行什么操作,然后在去接收参数,这样就可以避免sql注入的产生。总的来说就是预编译的过程就是参数和sql语句进行分开。
漏洞点总结 未使用占位符 PreparedStatement只有在使用?占位符的时候才能够预防SQL注入,否则还是会产生sql注入的(现在php好像也是用占位符进行预防的)。
使用in语句 在删除语句中,如果使用的是in语句,并且并且无法确定in内的对象个数的话,就有可能照成sql注入。String sql= "delete from users where id in("id1,id2...")" 解决方法:对对象进行一个遍历,每一个都加上?作为占位符就好了。
使用like语句 在使用like语句时,如果直接做拼接的话,就会导致sql注入漏洞 例如:
1 2 3 4 5 String userInput = "admin" ; String sql = "SELECT id, username, email FROM users WHERE username LIKE '%" + userInput + "%'" ;
%和_ %和_作为通配符,一个是匹配多个字符,一个匹配一个字符,在预编译中,它们依旧是作为通配符进行使用的。 这就会导致慢查询,形成dos 解决办法就是手动过滤掉就好
order by和from等关键词无法进行预编译 师傅讲解的很明白,就是主要原因就是,order by后是需要加字段名的,而字段名是不能加引号的,如果使用了引号就是一个字符串,而不是一个字段名了,然而在使用预编译的时候会自动添加‘,所以导致我们在使用order by的时候是不能使用预编译技术的,只能使用statement,这就会导致sql注入,当然解决问题的方法也很简单,直接进行过滤就好。
Mybatis下的SQL注入 Mybatis算是jbdc的一个代替,在mybatis中也是有两个可以执行sql语句的方法,一个是${},另一个是#{}。
${} 在项目中,sql语句的执行代码是写在了Mapper中的,所以我们一般看sql注入的话是可以直接去Mapper里看的,这样节省时间。 还是那这个项目举例
1 2 3 4 5 6 7 8 @GetMapping("/mybatis/vuln01") public List<User> mybatisVuln01 (@RequestParam("username") String username) { return userMapper.findByUserNameVuln01(username); } @Select("select * from users where username = '${username}'") List<User> findByUserNameVuln01 (@Param("username") String username) ;
这里的${}是直接进行拼接的。
因为是直接拼接,所以payload就是上述那样
#{} 这种就是做了一个防护,也就是预编译
1 2 3 4 5 6 7 8 @Select("select * from users where username = #{username}") User findByUserName (@Param("username") String username) ; List<User> findByUserNameVuln02 (String username) ; @GetMapping("/mybatis/vuln02") public List<User> mybatisVuln02 (@RequestParam("username") String username) { return userMapper.findByUserNameVuln02(username); }
具体的话就和上述是一样的
MyBatis产生的三种sql注入 like关键字 因为在使用like进行模糊查询的时候,使用#{}会报错,所以一些程序员,会将#{}换成${},最终导致拼接的注入,在项目中体现为:
1 2 3 <select id="findByUserNameVuln02" parameterType="String" resultMap="User" > select * from users where username like '%${_parameter}%' </select>
修复方法
1 2 3 <select id ="findByUserNameVuln02" parameterType ="String" resultMap ="User" > select * from users where username like concat('%',#{_parameter},'%') </select >
正确写法:
1 2 3 4 5 6 mysql: select * from users where username like concat ('%' ,#{username},'%' ) oracle: select * from users where username like '%' ||#{username}||'%' sqlserver: select * from users where username like '%' +#{username}+'%'
使用in语句 这个漏洞会出现的原因呢就是当你在使用in的时候如果传入id=1,2,3这种,预编译的判断就会将参数1,2,3直接赋值为一个占位符?,这样的话查询就变成了去查1,2,3,(这个显然会报错)而不是分别查1,2,3,返回结果。所以程序员为了避免这个报错,就将#{}换成${},这样的话就会导致拼接的sql注入。 具体是 controller中添加
1 2 3 4 @GetMapping("/mybatis/vuln04") public List<User> mybatisVuln04 (@RequestParam("id") String id) { return userMapper.findByUserNameVuln04(id); }
接口类添加
1 List<User> findByUserNameVuln04 (@Param("id") String id) ;
UserMapper.xml中添加
1 2 3 <select id ="findByUserNameVuln04" parameterType ="String" resultMap ="User" > select * from users where id in (${id}) </select >
解决方法: 就是使用foreach标签对其遍历,动态的生成占位符。 也就是说使用foreach之后对于in(1,2,3)预编译的结果是in(?,?,?),而不是in(?) 具体的使用方法是:
1 2 3 4 5 <select id ="findByUserNameVuln04" parameterType ="String" resultMap ="User" > select * from users where id in <foreach collection ="id" item ="id" open ="(" close =")" separator ="," > #{id} </foreach > </select >
也就是会将我们的输入按照,分隔开,然后进行预编译。
使用order by语句 这里和jdbc的一样
Mybatis-Plus的sql注入 我是让ai写了个小项目来辅助学习一下(这里ai写的项目就不放了哈) 没有涉及到的是手动补充的 这里是可以正常查询的
apply拼接产生的sql注入 apply直接进行拼接产生的漏洞
1 2 3 4 5 @GetMapping("/vuln1") public List<User> getVuln1 (String name) { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.apply("name = '" + name + "'" ); return userMapper.selectList(wrapper);
这里是对name直接进行了拼接,然后调用selectlist直接执行sql语句,然后可以返回多条结果。 这里可以使用万能密码返回所有数据,但是使用selectlist的场景是很少见的。 在看一个场景:
1 2 3 4 5 6 @GetMapping("/vuln3") public User getVuln3 (String name, Long id) { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.eq("name" , name).apply("id=" + id); return userMapper.selectOne(wrapper); }
这算是一个常见的场景,也是使用apply进行的直接拼接,但是selectone只能返回单个结果(这里就不能使用万能密码了,如果使用万能密码的话,会返回多个结果最终就会报错)。 那么我们sql注入就可以使用报错注入:
1 1 %20and%20extractvalue(1 ,concat(0x7e ,(select%20database()),0x7e ))
前提还得是使用的mysql数据库版本还得在5,因为得能用extractvalue 这边也是能成功的注入出来 updatexml也可以
1 2 and updatexml(1,concat(0x7e,(select database()),0x7e),1)
floor我就不试了。 对于怎么进行防护的,直接进行预编译就好了
1 2 3 4 5 6 7 @GetMapping("/sec3") public User getSec3 (String id) { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.apply("id={0}" , id); return userMapper.selectOne(wrapper); }
last产生的sql注入 先说明一下last的作用:直接向sql语句后面拼接语句(有拼接这里就会出现sql注入) 在由上图可知last是可以直接传入sql语句的。 所以可见:
1 2 3 4 5 6 @GetMapping("/vuln2") public List<User> getVuln2 (String id) { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.last("where id = " + id); return userMapper.selectList(wrapper); }
使用万能密码
exists和notexists拼接导致的sql注入 关于这两个其实是在sql语句中拼接一个子查询,只返回true和flase
1 2 3 4 5 6 @GetMapping("vuln4") public List<User> getVuln4 (String id) { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.exists("select * from users where id = " + id); return userMapper.selectList(wrapper); }
1 2 3 4 5 6 @GetMapping("vuln4") public List<User> getVuln4 (String id) { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.notExists("select * from users where id = " + id); return userMapper.selectList(wrapper); }
having语句 having主要是对分组后的数据进行过滤 接口:
1 2 3 4 5 6 @GetMapping("vuln5") public List<User> getVuln5 (String id) { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.select().groupBy(id).having("id > " +id); return userMapper.selectList(wrapper); }
order by和group by语句 这个的话还是和上述,以及jdbc的情况一致,因为无法进行预编译所以容易造成sql注入漏洞。
insql和notinsql 主要作用是向sql语句中拼接 字段 in (sql语句),notinsql同理 接口:
1 2 3 4 5 6 @GetMapping("vuln6") public List<User> getVuln6 (String id) { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.select().inSql("id" ,"select * from users where id = " + id); return userMapper.selectList(wrapper); }
写法是这样一种写法,notinsql的接口是:
1 2 3 4 5 6 @GetMapping("vuln6") public List<User> getVuln6 (String id) { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.select().notinSql("id" ,"select * from users where id = " + id); return userMapper.selectList(wrapper); }
有报错应该是我的数据库字段和逻辑矛盾产生的,这到无所谓
wrapper自定义sql 和上述一样
有关mp中分页插件的的sql注入 分页插件的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.example.demo.config; import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor (); interceptor.addInnerInterceptor(new PaginationInnerInterceptor (DbType.MYSQL)); return interceptor; } }
1 2 3 4 5 6 7 8 9 10 11 12 @GetMapping("/vulnpage") public IPage<User> getVulnPage ( @RequestParam(defaultValue = "1") Integer current, @RequestParam(defaultValue = "10") Integer size, @RequestParam String sortColumn) { Page<User> page = new Page <>(current, size); page.addOrder(OrderItem.desc(sortColumn)); return userMapper.selectPage(page, null ); }
这里是addorder导致的sql注入漏洞 正常的请求如果是current=1&size=10&sortColumn=age 它执行的sql命令类似于这种
1 SELECT id, name, age, email FROM users ORDER BY age DESC LIMIT ?
age位置是我们能够控制的,这时候就可以使用报错注入,或者是时间盲注 例如
也可以触发延迟导致盲注 最后就是pagehelper的几种形式,1.setorderby 2.startpage 其实也都是和order by有关的注入。 修复的话就加filter就好。
Hibernate下的sql注入 Hibernate是一种ORM(对象,关系数据库,映射)框架,在java对象与数据库之间建立某种映射,以实现直接存取java对象。 在这个框架下,用的较多的还是hql进行查询,但是,hql语句是和php很像的,所以也就是拼接产生sql注入漏洞 例如:
1 String hql = "from users where id ='" +id+ "'"
至于防护方法,这里直接copy了
命名参数 1 2 3 4 Query<User> query = session.createQuery("from users name = ?1" , User.class); String parameter = "g1ts" ;Query<User> query = session.createQuery("from users name = :name" , User.class); query.setParameter("name" , parameter);
位置参数 1 2 3 String parameter = "g1ts" ;Query<User> query = session.createQuery("from users name = ?1" , User.class); query.setParameter(1 , parameter);
命名参数列表 1 2 3 List<String> names = Arrays.asList("g1ts" , "g2ts" ); Query<User> query = session.createQuery("from users where name in (:names)" , User.class); query.setParameter("names" , names);
类实例 1 2 3 user1.setName("g1ts" ); Query<User> query = session.createQuery("from users where name =:name" , User.class); query.setProperties(user1);
HQL拼接方法 1 2 3 4 5 import org.apache.commons.lang.StringEscapeUtils;public static void main (String[] args) { String str = StringEscapeUtils.escapeSql("'" ); System.out.println(str); }
参考 Java OWASP 中的 SQL 注入代码审计 | Drunkbaby’s Blog xz.aliyun.com/news/11118