sql注入

作者: 一名程序猿 | 来源:发表于2018-07-03 19:09 被阅读169次
    先看一段日常代码
    @Autowired
    private JdbcTemplate template;
    
    private void buildYear(SearchDto s, StringBuilder sql) {
        sql.append(" SELECT ROUND(SUM(CASE (");
        sql.append(s.getYear());
        sql.append(" - substr(li.fztime, 1, 4))");
        sql.append(" WHEN 1 THEN (CASE diwact.sqoneyear WHEN '/' THEN 0 WHEN '' THEN 0 ELSE diwact.sqoneyear END)");
        sql.append(" WHEN 2 THEN (CASE diwact.sqtwoyear WHEN '/' THEN 0 WHEN '' THEN 0 ELSE diwact.sqtwoyear END)");
        sql.append(" WHEN 3 THEN (CASE diwact.sqthreeyear WHEN '/' THEN 0 WHEN '' THEN 0 ELSE diwact.sqthreeyear END)");
        sql.append(" WHEN 4 THEN (CASE diwact.sqfouryear WHEN '/' THEN 0 WHEN '' THEN 0 ELSE diwact.sqfouryear END)");
        sql.append(" WHEN 5 THEN (CASE diwact.sqfiveyear WHEN '/' THEN 0 WHEN '' THEN 0 ELSE diwact.sqfiveyear END)");
        sql.append(" ELSE 0 END),6) as num,");
    }
    
    private void getAreaStatistical(SearchDto s, StringBuilder sql) {
        if (StringUtils.isNotBlank(s.getCodeRegion())) {
            sql.append(" diwact.waterwrwname as one,bi.name_regiondetail as two");
            sql.append(" from license_info li");
            sql.append(" left join drain_info_water_apply_count_two diwact on li.dataid = diwact.dataid ");
            sql.append(" left join pw_base_info pbi on li.dataid = pbi.dataid ");
            sql.append(" left join base_info bi on pbi.enterid = bi.enterid");
            sql.append(" where bi.code_region ='");
            sql.append(s.getCodeRegion());
            sql.append("'");
            sql.append(" and diwact.waterwrwcode in ('");
            sql.append(PollutantEnum.CODCR.getCode());
            sql.append("','");
            sql.append(PollutantEnum.AD.getCode());
            sql.append("' )");
            sql.append(" group by diwact.waterwrwcode,diwact.waterwrwname ,bi.code_regiondetail,name_regiondetail");
            sql.append(" order by bi.code_regiondetail asc ,diwact.waterwrwcode asc ");
        } else {
            sql.append(" diwact.waterwrwname as one,bi.name_region as two");
            sql.append(" from license_info li");
            sql.append(" left join drain_info_water_apply_count_two diwact on li.dataid = diwact.dataid ");
            sql.append(" left join pw_base_info pbi on li.dataid = pbi.dataid");
            sql.append(" left join base_info bi on pbi.enterid = bi.enterid");
            sql.append(" where diwact.waterwrwcode in ('");
            sql.append(PollutantEnum.CODCR.getCode());
            sql.append("','");
            sql.append(PollutantEnum.AD.getCode());
            sql.append("' )");
            sql.append(" group by diwact.waterwrwcode,diwact.waterwrwname,bi.code_region,bi.name_region");
            sql.append(" order by bi.code_region asc ,diwact.waterwrwcode asc ");
        }
    }
    private ApesDto getResult(String sql) {
        log.info("----sql" + sql);
        List<Map<String, Object>> results = template.queryForList(sql);
        log.info("results" + results);
        // 名称集合
        Set<String> nameSet = new LinkedHashSet<String>();
        // 数据 集合
        List<ApeDto> datas = new ArrayList<ApeDto>();
        if (!results.isEmpty()) {
            results.forEach(t -> {
                nameSet.add(t.get("two").toString());
            });
            nameSet.forEach(t -> {
                ApeDto apeDto = new ApeDto();
                Map<String, Object> val = new HashMap<String, Object>();
                results.forEach(t2 -> {
                    if (t.equals(t2.get("two"))) {
                        apeDto.setName(t);
                        val.put(t2.get("one").toString(), t2.get("num").toString());
                    }
                });
                apeDto.setValues(val);
                datas.add(apeDto);
            });
        }
        ApesDto apesDto = new ApesDto();
        apesDto.setVal(datas);
        return apesDto;
    }
    

    这段代码功能很简单,就是根据前端传递的参数,构建sql查询语句。
    代码经过测试满足了功能需求。

    问题

    项目经过第三方测评发现有sql注入漏洞。

    正文

    什么叫sql注入

    所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。 [1] 比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击

    原理

    SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有细致地过滤用户输入的数据,致使非法数据侵入系统。
    根据相关技术原理,SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤,从而执行了非法的数据查询。基于此,SQL注入的产生原因通常表现在以下几方面:①不当的类型处理;②不安全的数据库配置;③不合理的查询集处理;④不当的错误处理;⑤转义字符处理不合适;⑥多个提交处理不当。

    攻击

    当应用程序使用输入内容来构造动态sql语句以访问数据库时,会发生sql注入攻击。如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生sql注入。sql注入可能导致攻击者使用应用程序登陆在数据库中执行命令。相关的SQL注入可以通过测试工具pangolin进行。如果应用程序使用特权过高的帐户连接到数据库,这种问题会变得很严重。在某些表单中,用户输入的内容直接用来构造动态sql命令,或者作为存储过程的输入参数,这些表单特别容易受到sql注入的攻击。而许多网站程序在编写时,没有对用户输入的合法性进行判断或者程序中本身的变量处理不当,使应用程序存在安全隐患。这样,用户就可以提交一段数据库查询的代码,根据程序返回的结果,获得一些敏感的信息或者控制整个服务器,于是sql注入就发生了。

    防止

    防止sql注入最简单的方法就是采用参数化的形式,不要直接拼接sql语句。

    改版代码
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;
    
    private void buildYear(SearchDto s, StringBuilder sql) {
        sql.append(" SELECT ROUND(SUM(CASE (:year");
        sql.append(" - substr(li.fztime, 1, 4))");
        sql.append(" WHEN 1 THEN (CASE diwact.sqoneyear WHEN '/' THEN 0 WHEN '' THEN 0 ELSE diwact.sqoneyear END)");
        sql.append(" WHEN 2 THEN (CASE diwact.sqtwoyear WHEN '/' THEN 0 WHEN '' THEN 0 ELSE diwact.sqtwoyear END)");
        sql.append(" WHEN 3 THEN (CASE diwact.sqthreeyear WHEN '/' THEN 0 WHEN '' THEN 0 ELSE diwact.sqthreeyear END)");
        sql.append(" WHEN 4 THEN (CASE diwact.sqfouryear WHEN '/' THEN 0 WHEN '' THEN 0 ELSE diwact.sqfouryear END)");
        sql.append(" WHEN 5 THEN (CASE diwact.sqfiveyear WHEN '/' THEN 0 WHEN '' THEN 0 ELSE diwact.sqfiveyear END)");
        sql.append(" ELSE 0 END),6) as num,");
    }
    
    private void getAreaStatistical(SearchDto s, StringBuilder sql) {
        if (StringUtils.isNotBlank(s.getCodeRegion())) {
            sql.append(" diwact.waterwrwname as one,bi.name_regiondetail as two");
            sql.append(" from license_info li");
            sql.append(" left join drain_info_water_apply_count_two diwact on li.dataid = diwact.dataid ");
            sql.append(" left join pw_base_info pbi on li.dataid = pbi.dataid ");
            sql.append(" left join base_info bi on pbi.enterid = bi.enterid");
            sql.append(" where bi.code_region = :code");
            sql.append(" and diwact.waterwrwcode in ('");
            sql.append(PollutantEnum.CODCR.getCode());
            sql.append("','");
            sql.append(PollutantEnum.AD.getCode());
            sql.append("' )");
            sql.append(" group by diwact.waterwrwcode,diwact.waterwrwname ,bi.code_regiondetail,name_regiondetail");
            sql.append(" order by bi.code_regiondetail asc ,diwact.waterwrwcode asc ");
        } else {
            sql.append(" diwact.waterwrwname as one,bi.name_region as two");
            sql.append(" from license_info li");
            sql.append(" left join drain_info_water_apply_count_two diwact on li.dataid = diwact.dataid ");
            sql.append(" left join pw_base_info pbi on li.dataid = pbi.dataid");
            sql.append(" left join base_info bi on pbi.enterid = bi.enterid");
            sql.append(" where diwact.waterwrwcode in ('");
            sql.append(PollutantEnum.CODCR.getCode());
            sql.append("','");
            sql.append(PollutantEnum.AD.getCode());
            sql.append("' )");
            sql.append(" group by diwact.waterwrwcode,diwact.waterwrwname,bi.code_region,bi.name_region");
            sql.append(" order by bi.code_region asc ,diwact.waterwrwcode asc ");
        }
    }
    private ApesDto getResult(String sql, SearchDto s) {
        log.info("----sql" + sql);
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("year", Integer.parseInt(s.getYear()));
        if (StringUtils.isNotBlank(s.getCodeRegion())) {
            params.put("code", s.getCodeRegion());
        }
        List<Map<String, Object>> results = jdbcTemplate.queryForList(sql, params);
        // 名称集合
        Set<String> nameSet = new LinkedHashSet<String>();
        // 数据 集合
        List<ApeDto> datas = new ArrayList<ApeDto>();
        if (!results.isEmpty()) {
            results.forEach(t -> {
                nameSet.add(t.get("two").toString());
            });
            for (String t : nameSet) {
                ApeDto apeDto = new ApeDto();
                Map<String, Object> val = new HashMap<String, Object>();
                results.forEach(t2 -> {
                    if (t.equals(t2.get("two"))) {
                        apeDto.setName(t);
                        val.put(t2.get("one").toString(), t2.get("num").toString());
                    }
                });
                apeDto.setValues(val);
                datas.add(apeDto);
            }
        }
        ApesDto apesDto = new ApesDto();
        apesDto.setVal(datas);
        return apesDto;
    }
    

    通过这种方式就可以防止sql注入漏洞了。

    why

    sql 执行过程简化版:收到指令 -> 编译SQL生成执行计划 ->选择执行计划 ->执行执行计划。
    举例:
    以往在Web应用程序访问数据库时一般是采取拼接字符串的形式,比如登录的时候就是根据用户名和密码去查询:
    string sql = "SELECT TOP 1 * FROM [User] WHERE UserName = '" + userName + "' AND Password = '" + password + "'";

    其中userName和password两个变量的值是由用户输入的。在userName和password都合法的情况下,这自然没有问题,但是用户输入是不可信的,一些恶意用户只要用一些技巧,就可以绕过用户名、密码登录。

    假设password的值是"1' or '1' = '1",userName的值随便取,比如是"abc",那变量sql的值就是:

    "SELECT TOP 1 * FROM [User] WHERE UserName = 'abc' AND Password = '1' or '1' = '1'"
    由于'1' = '1'恒为真,因此只要User表中有数据,不管UserName、Password的值是否匹配,这条SQL命令准能查出记录来。就这样,登录系统就被破解了。

    防御方式

    字符串检测:限定内容只能由英文、数字等常规字符,如果检查到用户输入有特殊字符,直接拒绝。但缺点是,系统中不可避免地会有些内容包含特殊字符,这时候总不能拒绝入库。

    字符串替换:把危险字符替换成其他字符,缺点是危险字符可能有很多,一一枚举替换相当麻烦,也可能有漏网之鱼。

    存储过程:把参数传到存储过程进行处理,但并不是所有数据库都支持存储过程。如果存储过程中执行的命令也是通过拼接字符串出来的,还是会有漏洞。

    参数化查询
      参数化查询(Parameterized Query 或 Parameterized Statement)是访问数据库时,在需要填入数值或数据的地方,使用参数 (Parameter) 来给值。

    在使用参数化查询的情况下,数据库服务器不会将参数的内容视为SQL指令的一部份来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,因此就算参数中含有指令,也不会被数据库运行。

    结语

    大神之路需要一滴一滴的积累,尤其是基础。

    相关文章

      网友评论

      本文标题:sql注入

      本文链接:https://www.haomeiwen.com/subject/uhfjuftx.html