添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

问题描述

系统被检测出多处大量接口有SQL注入风险,并且指明了存在多种注入的方式

原因

框架架构初期约定的JPA与数据库交互没有被开发人员恪守,存在大量直接拼接的SQL执行语句,既没有预编译,也没有做危险SQL关键词的统一过滤

  • 方案一:采用预编译方式运行SQL,如今基本所有数据库都支持预编译,所以直接使用数据库连接包中的预编译类即可实现,不过所有拼接SQL的代码都需要更改,工作量非常巨大,此篇不再赘述,百度 java SQL预编译 即可
  • 方案二:在网关拦截处做处理,拦截获取的请求的所有参数,判断参数是否有SQL注入风险的关键词,有危险关键词就一律报错不予执行,这样侵入性太强,大量参数传入可能会给系统造成一定负担,而且有些关键词存在SQL注入风险但是还是需要使用的
  • 方案三:这个方案是后来才发现的,因为系统使用的数据库连接池是Druid,Druid自带防止SQL注入的配置,在第一个方案被否决,第二个方案发布测试后很多接口被测试和开发怼,不得已的情况下发现了这个最佳的解决方案
  • 实现代码

    开门见山,最佳方案三的配置

    添加配置文件中 Druid 配置 filters= wall 表示防止SQL注入

    1
    2
    3
    4
    spring:
    datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    filters: wall

    当然有的项目不是使用Druid做数据库连接池,其他的连接池有些同样自带防SQL注入的配置,可以检索一下

    方案二的实现代码

    SqlInjectionFilter类继承ZuulFilter重写run方法(此类还带有预防XSS攻击的代码,如果配置了Druid配置,可以删除此处预防sql注入相关代码,改为预防XSS攻击的类)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    @Component
    @Slf4j
    public class SqlInjectionFilter extends ZuulFilter {

    @Value("${custom.sql-injection-filter.enabled}")
    private boolean enabled;

    @Override
    public String filterType() {
    return FilterConstants.PRE_TYPE;
    }

    // 自定义过滤器执行的顺序,数值越大越靠后执行,越小就越先执行
    @Override
    public int filterOrder() {
    return FilterConstants.PRE_DECORATION_FILTER_ORDER - 2;
    }

    @Override
    public boolean shouldFilter() {
    return enabled;
    }

    @Override
    public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    HttpServletRequest request = ctx.getRequest();
    try {
    //判断需要跳过过滤的url
    for (String skipUrl : SqlInjectionConfig.getSkipUrls()) {
    if(-1 != request.getRequestURI().indexOf(skipUrl)){
    return null;
    }
    }
    // 执行过滤逻辑
    InputStream in = ctx.getRequest().getInputStream();
    String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
    if (StringUtils.isBlank(body)) {
    body = JSONObject.fromObject(request.getParameterMap()).toString();
    if (StringUtils.isBlank(body)) {
    return null;
    }
    }
    Map<String, Object> stringObjectMap = cleanXSS(body);
    JSONObject json = JSONObject.fromObject(stringObjectMap);
    String newBody = json.toString();
    // 如果存在sql注入,直接拦截请求
    if (newBody.contains("forbid")) {
    setUnauthorizedResponse(ctx);
    }
    final byte[] reqBodyBytes = newBody.getBytes();
    ctx.setRequest(new HttpServletRequestWrapper(request) {

    @Override
    public ServletInputStream getInputStream() throws IOException {
    return new ServletInputStreamWrapper(reqBodyBytes);
    }

    @Override
    public int getContentLength() {
    return reqBodyBytes.length;
    }

    @Override
    public long getContentLengthLong() {
    return reqBodyBytes.length;
    }
    });
    } catch (IOException e) {
    e.printStackTrace();
    }
    return null;
    }

    private Map<String, Object> cleanXSS(String value) {
    value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
    value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
    value = value.replaceAll("'", "& #39;");
    value = value.replaceAll("eval\\((.*)\\)", "");
    value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
    value = value.replaceAll("script", "");
    value = value.replaceAll("[*]", "[" + "*]");
    value = value.replaceAll("[+]", "[" + "+]");
    value = value.replaceAll("[?]", "[" + "?]");

    String badStr = "'|and|exec|execute|insert|select|delete|update|count|drop|chr|mid|master|truncate|" +
    "char|declare|sitename|net user|xp_cmdshell|;|or|+|create|table|from|grant|group_concat|" +
    "column_name|information_schema.columns|table_schema|union|where|--|,|like|//|/|%|#";

    JSONObject json = JSONObject.fromObject(value);
    String[] badStrs = badStr.split("\\|");
    Map<String, Object> map = json;
    Map<String, Object> mapjson = new HashMap<>();
    for (Map.Entry<String, Object> entry : map.entrySet()) {
    String value1 = entry.getValue().toString().toLowerCase();
    for (String bad : badStrs) {
    if (-1 != value1.indexOf(bad)) {
    log.info("拦截的参数#####################"+value1);
    log.info("拦截的关键字#####################"+bad);
    value1 = "forbid";
    mapjson.put(entry.getKey(), value1);
    break;
    } else {
    mapjson.put(entry.getKey(), entry.getValue());
    }
    }
    }
    return mapjson;
    }

    private void setUnauthorizedResponse(RequestContext requestContext) {
    Gson gson = new Gson();
    BaseResponse result = new BaseResponse();
    result.setCode(ErrorCode.ERR_GLOBAL_PARA_CHECK);
    result.setMsg("SQL Injection Risk");
    requestContext.setResponseBody(gson.toJson(result));
    }

    }

    SqlInjectionConfig类读取配置文件中需要跳过检查的接口数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;

    @ConfigurationProperties(prefix = "custom.sql-injection-filter")
    @Component
    public class SqlInjectionConfig {


    public static String[] skipUrls;

    public static String[] getSkipUrls() {
    return skipUrls;
    }

    public void setSkipUrls(String[] skipUrls) {
    SqlInjectionConfig.skipUrls = skipUrls;
    }

    }

    yml配置文件,如果想要关闭SQL注入检测将 enable 改为 false 即可,如果想要某个接口跳过检测,添加到 skipUrls 数组即可

    1
    2
    3
    4
    custom:
    sql-injection-filter:
    enabled: true
    skipUrls: /api-file,/api-source,/api-user/user/info.do