Java代码审计漏洞-SQL注入

Java代码审计漏洞-SQL注入

基础知识
https://www.cnblogs.com/-meditation-/p/16031461.html
https://www.cnblogs.com/-meditation-/p/16031338.html
https://www.cnblogs.com/-meditation-/p/16112699.html
https://www.cnblogs.com/-meditation-/p/16112589.html
https://www.cnblogs.com/-meditation-/p/16031523.html

个人学习笔记,直接看代码,不写奇怪的东西,分析代码写注释。

情况一:直接用servlet写SQL语句

存在漏洞代码

    /**
     * Vuln Code.
     * http://localhost:8080/sqli/jdbc/vuln?username=joychou
     *
     * @param username username
     */
		//RequestMapping 这个controller表示匹配的URL
    @RequestMapping("/jdbc/vuln")
		//@RequestParam("username")获取从前端传过来的username参数
    public String jdbc_sqli_vul(@RequestParam("username") String username) {

        StringBuilder result = new StringBuilder();

        try {
           //获取数据库连接
            Class.forName(driver);
            Connection con = DriverManager.getConnection(url, user, password);

            if (!con.isClosed())
                System.out.println("Connect to database successfully.");
						
           // sqli vuln code  这里没有使用预处理的方式,直接将获取的参数和SQL语句拼接,导致注入。
            Statement statement = con.createStatement();
            String sql = "select * from users where username = '" + username + "'";
           //终端打印sql
            logger.info(sql);
           //执行拼接好的SQL语句
            ResultSet rs = statement.executeQuery(sql);

            while (rs.next()) {
                String res_name = rs.getString("username");
                String res_pwd = rs.getString("password");
                String info = String.format("%s: %s\n", res_name, res_pwd);
                result.append(info);
                logger.info(info);
            }
            rs.close();
            con.close();


        } catch (ClassNotFoundException e) {
            logger.error("Sorry,can`t find the Driver!");
        } catch (SQLException e) {
            logger.error(e.toString());
        }
        return result.toString();
    }

意思意思放个图

安全代码

@RequestMapping("/jdbc/sec")
    public String jdbc_sqli_sec(@RequestParam("username") String username) {

        StringBuilder result = new StringBuilder();
        try {
            Class.forName(driver);
            Connection con = DriverManager.getConnection(url, user, password);

            if (!con.isClosed())
                System.out.println("Connecting to Database successfully.");

            // fix code 预处理代码
            String sql = "select * from users where username = ?";
            PreparedStatement st = con.prepareStatement(sql);
            st.setString(1, username);

            logger.info(st.toString());  // sql after prepare statement
            ResultSet rs = st.executeQuery();

            while (rs.next()) {
                String res_name = rs.getString("username");
                String res_pwd = rs.getString("password");
                String info = String.format("%s: %s\n", res_name, res_pwd);
                result.append(info);
                logger.info(info);
            }

            rs.close();
            con.close();

        } catch (ClassNotFoundException e) {
            logger.error("Sorry, can`t find the Driver!");
            e.printStackTrace();
        } catch (SQLException e) {
            logger.error(e.toString());
        }
        return result.toString();
    }

/**
预处理的方式,使用 ?占位。在把值传进去
*/

情况二:使用Mybatis进行数据库操作

存在漏洞代码

 /**
     * vuln code
     * http://localhost:8080/sqli/mybatis/vuln01?username=joychou' or '1'='1
     *
     * @param username username
     */

    @GetMapping("/mybatis/vuln01")
    public List<User> mybatisVuln01(@RequestParam("username") String username) {
        return userMapper.findByUserNameVuln01(username);
    }

/**
这里获取username,然后传到userMapper的findByUserNameVuln01方法里,   
	@Autowired
	private UserMapper userMapper;
	
正向跟踪UserMapper  findByUserNameVuln01内容如下  
	@Select("select * from users where username = '${username}'")
  List<User> findByUserNameVuln01(@Param("username") String username);
  
${} 本质使用Statement进行SQL语句拼接,会造成注入
*/



    /**
     * vul code
     * http://localhost:8080/sqli/mybatis/vuln02?username=joychou' or '1'='1' %23
     *
     * @param username username
     */
    @GetMapping("/mybatis/vuln02")
    public List<User> mybatisVuln02(@RequestParam("username") String username) {
        return userMapper.findByUserNameVuln02(username);
    }

/**
正向跟踪UserMapper  findByUserNameVuln02内容如下,未在UserMapper.java接口中写SQL语句
 List<User> findByUserNameVuln02(String username); 

寻找UserMapper.xml 内容如下
    <select id="findByUserNameVuln02" parameterType="String" resultMap="User">
        select * from users where username like '%${_parameter}%'
    </select>
${_parameter} 本质使用Statement进行SQL语句拼接,会造成注入
*/



    // http://localhost:8080/sqli/mybatis/orderby/vuln03?sort=1 desc%23
    @GetMapping("/mybatis/orderby/vuln03")
    public List<User> mybatisVuln03(@RequestParam("sort") String sort) {
        return userMapper.findByUserNameVuln03(sort);
    }
/**
同理跟userMapper.findByUserNameVuln03,传递了sort,
userMapper中,定义形参为order,接收sort后传递给 order1,userMapper.xml中通过order1调用
	List<User> findByUserNameVuln03(@Param("order1") String order);
	userMapper.xml
    <select id="findByUserNameVuln03" parameterType="String" resultMap="User">
        select * from users
        <if test="order1 != null">
            order by ${order1} asc
        </if>
    </select>
*/

安全代码

/**
     * security code
     * http://localhost:8080/sqli/mybatis/sec01?username=joychou
     *
     * @param username username
     */
    @GetMapping("/mybatis/sec01")
    public User mybatisSec01(@RequestParam("username") String username) {
        return userMapper.findByUserName(username);
    }

//userMapper.jva
    @Select("select * from users where username = #{username}")
    User findByUserName(@Param("username") String username);

    /**
     * http://localhost:8080/sqli/mybatis/sec02?id=1
     *
     * @param id id
     */
    @GetMapping("/mybatis/sec02")
    public User mybatisSec02(@RequestParam("id") Integer id) {
        return userMapper.findById(id);
    }

//userMapper.xml
    <select id="findById" resultMap="User">
        select * from users where id = #{id}
    </select>
      

    /**
     * http://localhost:8080/sqli/mybatis/sec03
     */
    @GetMapping("/mybatis/sec03")
    public User mybatisSec03() {
        return userMapper.OrderByUsername();
    }

//userMapper.xml
    <select id="OrderByUsername" resultMap="User">
        select * from users order by id asc limit 1
    </select>
      
      
    @GetMapping("/mybatis/orderby/sec04")
    public List<User> mybatisOrderBySec04(@RequestParam("sort") String sort) {
        return userMapper.findByUserNameVuln03(SecurityUtil.sqlFilter(sort));
    }

}

//userMapper.xml     
//过滤mybatis中order by不能用#的情况。 严格限制用户输入只能包含<code>a-zA-Z0-9_-.</code>字符。下面有介绍另一种
    <select id="findByUserNameVuln03" parameterType="String" resultMap="User">
        select * from users
        <if test="order != null">
            order by ${order} asc
        </if>
    </select>

//SecurityUtil.sqlFilter(sort)
   public static String sqlFilter(String sql) {
        if (!FILTER_PATTERN.matcher(sql).matches()) {
            return null;
        }
        return sql;
    }       

知识补充

@Select注解的目的是为了取代xml中的select标签,只作用于方法上面。
源码:
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public @interface Select
	{
    	String[] value();
	}

/**
源码说明了
(1)@Select注解只能修饰方法
(2)@Select注解的值是字符数组。
@Select注解的值是字符数组,但是真正生效的应该是最后那条SQL语句。
*/

//普通的字符串值,只能实现变量的替换功能
@Select("select * from t_person where id = #{id}")
Person selectPersonById(Integer id);

@Insert,@Update... 同理
  
    
<select id="findByUserNameVuln02" parameterType="String" resultMap="User">
		select * from users where username like '%${_parameter}%'
</select>  
//${_parameter} :当只有一个参数,可以使用_parameter,它就代表了这个参数
  
  
//!FILTER_PATTERN.matcher(sql).matches()   
//方法java.time.Matcher.matches()将给定区域与指定模式进行匹配。如果区域序列与Matcher的模式匹配,则返回true,否则返回false。

Mybatis框架下易产生SQL注入漏洞场景分析

在基于Mybatis框架的Java白盒代码审计工作中,通常将着手点定位在Mybatis的配置文件中。通过查看这些与数据库交互的配置文件来确定SQL语句中是否存在拼接情况,进而确立跟踪点。通过总结,Mybatis框架下易产生SQL注入漏洞的情况主要分为以下三种:

模糊查询like

Select * from news where title like ‘%#{title}%’,
//由于这样写程序会报错,研发人员将SQL查询语句修改如下:
Select * from news where title like ‘%${title}%’,
//在这种情况下我们发现程序不再报错,但是此时产生了SQL语句拼接问题,如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。

in之后的参数

Select * from news where id in (#{id}),
//由于这样写程序会报错,研发人员将SQL查询语句修改如下:
Select * from news where id in (${id}),
//修改SQL语句之后,程序停止报错,但是却引入了SQL语句拼接的问题,如果研发人员没有对用户输入的内容做过滤,势必会产生SQL注入漏洞。

order by之后

Select * from news where title =‘京东’ order by #{time} asc,
//由于发布时间time不是用户输入的参数,无法使用预编译。研发人员将SQL查询语句修改如下:
Select * from news where title =‘京东’ order by ${time} asc,
//修改之后,程序通过预编译,但是产生了SQL语句拼接问题,极有可能引发SQL注入漏洞。

Mybatis框架下SQL注入漏洞修复建议

模糊查询like

select * from news where tile like concat(‘%’,#{title}, ‘%’),
//用concat,采用预编译机制,避免了SQL语句拼接的问题,从根源上防止了SQL注入漏洞的产生。

in之后的参数

select * from news where id in
<foreach collection="ids" item="item" open="("separator="," close=")">#{item} </foreach>

 
/**
一个个参数预编译拼接SQL语句查询
<foreach></foreach>标签 当传入参数为数组或者集合时进行遍历
 collection:指定输入对象中集合属性
 item:每次遍历生成的对象
 open:开始遍历时拼接的串
 separator:每次迭代的分隔符
 close:结束遍历时两个对象需要拼接的串
 */

order by之后

/**前面用正则匹配的方式
	 针对order by这种情况研发人员还可以在java层面做映射来进行解决。如当存在发布时间time和点击量click两种排序选择时,我们可以限制用户只能输入1和2。参数化接受请求。
*/

参考文章

实战中的SQL注入漏洞挖掘

1、自动化工具扫描,一般使用fortify,根据扫出来的结果去做一个跟踪。

2、使用全局搜索的功能搜索Statement${ 根据查找的结果进行回溯。

如:${

IDEA 的Free Mabatis Tool插件可以在UserMapper.xml中直接让你找到对应方法的引用文件

Mac下 cmd+鼠标左键

windows下 ctrl+鼠标左键

回溯到对应的controller

可以看到整个过程没有任何的过滤,直接获取参数传入,同时没有采用预编译的方式写SQL拼接,所以产生了注入。

posted @ 2022-04-16 12:53  九天揽月丶  阅读(205)  评论(0编辑  收藏  举报