sql注入

先看一段日常代码
@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指令的编译后,才套用参数运行,因此就算参数中含有指令,也不会被数据库运行。

结语

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券