• springcloud3(七) 安全框架Hdiv


    Hdiv Security 是支持应用程序自我保护的先驱,是同类产品中的第一款产品,可在整个软件开发生命周期 (SDLC) 中提供针对安全漏洞和业务逻辑缺陷的保护。Hdiv Security 的统一平台使 DevSecOps 成为现实。Hdiv 的解决方案目前被政府、银行、航空航天和财富 500 强公司使用。官方网站: hdivsecurity.com

    目前Hdiv分为商业版和社区版,但是网上google和百度能够查到资料很少,但是可以在hdiv-config这个包基础上做二次开发

    1. hdiv社区版,默认能够做哪些安全防护

    1.1 SQL注入攻击 (SQL Injection)

    1.2 代码执行攻击 (Code execution)

    1.3 跨站脚本攻击(XSS)

    包含简单的跨站脚本攻击, 图片攻击,script标签攻击,eval攻击

    关于常见的攻击方式,这篇博客写的比较全: https://blog.csdn.net/tushanpeipei/article/details/120382711 

    2. spring cloud gateway + hdiv-config 实现的demo

    <dependency>
       <groupId>org.hdiv</groupId>
       <artifactId>hdiv-config</artifactId>
       <version>3.4.0</version>
    </dependency>

     hdiv-config的安全保护逻辑在defaultEditableValidations.xml中,即正则表达式中

    <?xml version="1.0" encoding="UTF-8"?>
    <defaultValidations>
        <!-- SQL Injection attacks detection validation rule -->
        <validation id="SQLInjection">
             <![CDATA[(\s|\S)*((%27)|(')|(%3D)|(=)|(%2F)|(")|((%22)|(-|%2D){2})|(%23)|(%3B)|(;))+(\s|\S)*]]>
        </validation>
        <!-- Code execution attacks detection validation rule -->
        <validation id="execCommand">
            <![CDATA[(\s|\S)*(exec(\s|\+)+(s|x)p\w+)(\s|\S)*]]>
        </validation>
        <!-- XSS attacks detection validation rules -->
        <validation id="simpleXSS">
            <![CDATA[(\s|\S)*((%3C)|<)((%2F)|/)*[a-z0-9%]+((%3E)|>)(\s|\S)*]]>
        </validation>
        <validation id="imageXSS">
            <![CDATA[(\s|\S)*((%3C)|<)((%69)|i|I|(%49))((%6D)|m|M|(%4D))((%67)|g|G|(%47))[^\n]+((%3E)|>)(\s|\S)*]]>
        </validation>
        <validation id="scriptXSS">
            <![CDATA[(\s|\S)*((%73)|s)(\s)*((%63)|c)(\s)*((%72)|r)(\s)*((%69)|i)(\s)*((%70)|p)(\s)*((%74)|t)((\s)|(\:)){1}(\s|\S)*]]>
        </validation>
        <validation id="evalXSS">
            <![CDATA[(\s|\S)*((%65)|e)(\s)*((%76)|v)(\s)*((%61)|a)(\s)*((%6C)|l)(\s)*(\()(\s|\S)*]]>
        </validation>
    </defaultValidations>

    上面的%加字符的是ASCII码,即一个百分号%后面跟对应字符的ASCII(16进制)码值。如果不使用转义字符,这些编码就会当URL中定义的特殊字符处理。常见的URL特殊符号及编码十六进制值可以看下这篇博客:https://blog.csdn.net/WuLex/article/details/98850868

    我这边demo的逻辑其实很简单就是在gateway的filter中对请求的header和body进行以上正则表达式的校验 

    package com.kawa.spbgateway.filter;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.node.ArrayNode;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import com.sun.org.apache.xerces.internal.impl.Constants;
    import lombok.extern.slf4j.Slf4j;
    import org.hdiv.config.validations.DefaultValidationParser;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.autoconfigure.codec.CodecProperties;
    import org.springframework.boot.context.properties.PropertyMapper;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.core.io.buffer.DataBufferUtils;
    import org.springframework.http.MediaType;
    import org.springframework.http.codec.CodecConfigurer;
    import org.springframework.http.codec.HttpMessageReader;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.util.unit.DataSize;
    import org.springframework.web.reactive.function.server.ServerRequest;
    import org.springframework.web.server.ServerWebExchange;
    import org.xml.sax.SAXException;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import javax.xml.parsers.ParserConfigurationException;
    import javax.xml.parsers.SAXParser;
    import javax.xml.parsers.SAXParserFactory;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import java.util.stream.Collectors;
    
    
    @Slf4j
    @Component
    public class InjectProtectFilter implements GlobalFilter, Ordered {
    
        private List<Pattern> hdivRules = new ArrayList<>();
        DefaultValidationParser parser = new DefaultValidationParser();
        private ObjectMapper objectMapper;
        private List<HttpMessageReader<?>> messageReaders;
    
        public InjectProtectFilter(@Value("${gateway.inject-protect.file:}") String validationFilePath, ObjectMapper objectMapper,
                                   CodecConfigurer codecConfigurer, CodecProperties codecProperties) {
            log.info(">>>>>>>>>> InjectProtectFilter");
            if (StringUtils.hasText(validationFilePath)) {
                readValidations(validationFilePath, parser);
            } else {
                parser.readDefaultValidations();
            }
            List<Map<DefaultValidationParser.ValidationParam, String>> validations = parser.getValidations();
            validations.forEach(val -> {
                String regex = val.get(DefaultValidationParser.ValidationParam.REGEX);
                hdivRules.add(Pattern.compile(regex));
            });
            this.objectMapper = objectMapper;
            this.messageReaders = fetchMessageReaders(codecConfigurer, codecProperties);
    
        }
    
        private List<HttpMessageReader<?>> fetchMessageReaders(CodecConfigurer codecConfigurer, CodecProperties codecProperties) {
            PropertyMapper propertyMapper = PropertyMapper.get();
            CodecConfigurer.DefaultCodecs defaultCodecs = codecConfigurer.defaultCodecs();
            propertyMapper
                    .from(codecProperties.getMaxInMemorySize())
                    .whenNonNull()
                    .asInt(DataSize::toBytes)
                    .to(defaultCodecs::maxInMemorySize);
            return codecConfigurer.getReaders();
        }
    
        /**
         * prevent external entities operate
         *
         * @param validationFilePath
         * @param parser
         */
        private void readValidations(String validationFilePath, DefaultValidationParser parser) {
            try (var fis = new FileInputStream(validationFilePath)) {
                SAXParserFactory spf = SAXParserFactory.newInstance();
                spf.setFeature(Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE, false);
                spf.setFeature(Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE, false);
                spf.setFeature(Constants.SAX_FEATURE_PREFIX + Constants.DISALLOW_DOCTYPE_DECL_FEATURE, true);
                SAXParser sp = spf.newSAXParser();
                sp.parse(fis, parser);
            } catch (IOException | ParserConfigurationException | SAXException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            long contentLength = exchange.getRequest().getHeaders().getContentLength();
            MediaType contentType = exchange.getRequest().getHeaders().getContentType();
            List<String> headers = exchange.getRequest().getHeaders().values().stream().flatMap(list -> list.stream()).collect(Collectors.toList());
            log.info(">>>>>>>>>> InjectProtectFilter-filter request headers: {}", headers);
            if (contentLength > 0 && (MediaType.APPLICATION_JSON.equals(contentType))) {
                return DataBufferUtils
                        .join(exchange.getRequest().getBody())
                        .flatMap(dataBuffer -> validateJson(exchange, chain, dataBuffer, headers));
            }
            validateParamList(headers);
            return chain.filter(exchange);
        }
    
        private boolean validateParam(String paramStr, Pattern rule) {
            Matcher matcher = rule.matcher(paramStr);
            return matcher.matches();
        }
    
        private Mono<? extends Void> validateJson(ServerWebExchange exchange, GatewayFilterChain chain,
                                                  DataBuffer dataBuffer, List<String> params) {
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bytes);
            DataBufferUtils.release(dataBuffer);
    
            Flux<DataBuffer> cacheFlux = Flux.defer(() -> {
                DataBuffer wrap = exchange.getResponse().bufferFactory().wrap(bytes);
                DataBufferUtils.retain(wrap);
                return Mono.just(wrap);
            });
            ServerHttpRequest serverHttpRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return cacheFlux;
                }
            };
    
            ServerWebExchange serverWebExchange = exchange.mutate().request(serverHttpRequest).build();
            return ServerRequest.create(serverWebExchange, messageReaders)
                    .bodyToMono(String.class)
                    .doOnNext(bodyStr -> {
                        try {
                            JsonNode jsonNode = objectMapper.readValue(bodyStr, JsonNode.class);
                            add2ParamList(jsonNode, params);
                        } catch (JsonProcessingException e) {
                            log.info(">>>>>>>>> invalid json body: {}", bodyStr);
                        }
                        validateParamList(params);
                    }).then(chain.filter(serverWebExchange));
        }
    
    
        private void add2ParamList(JsonNode jsonNode, List<String> params) {
            if (jsonNode.isObject()) {
                ObjectNode objNode = (ObjectNode) jsonNode;
                objNode.fields().forEachRemaining(val -> add2ParamList(val.getValue(), params));
            } else if (jsonNode.isArray()) {
                ArrayNode arrayNode = (ArrayNode) jsonNode;
                arrayNode.forEach(node -> add2ParamList(node, params));
            } else {
                params.add(jsonNode.asText());
            }
        }
    
        private void validateParamList(List<String> params) {
            Iterator<String> paramIterator = params.iterator();
            stop:
            while (paramIterator.hasNext()) {
                String param = paramIterator.next();
                Iterator<Pattern> ruleIterator = hdivRules.iterator();
                while (ruleIterator.hasNext()) {
                    Pattern rule = ruleIterator.next();
                    if (validateParam(param, rule)) {
                        log.error(">>>>>>>>>> hit the security rule, param:{} rule:{}", param, rule);
                        // throw exception
                        break stop;
                    }
                }
            }
        }
    
        @Override
        public int getOrder() {
            return Integer.MIN_VALUE + 99;
        }
    }

    测试demo

     
  • 相关阅读:
    [HDU2866] Special Prime (数论,公式)
    [骗分大法好] 信息学竞赛 骗分导论(论文搬运)
    flayway数据库管理
    RabbitMQ的基本概念与原理
    springboot+ideal实现远程调试
    盘点总结
    mysql查看进程命令
    Java字符串正则文本替换
    springboot代码级全局敏感信息加解密和脱敏方案
    使用PMD进行代码审查
  • 原文地址:https://www.cnblogs.com/hlkawa/p/15795466.html
Copyright © 2020-2023  润新知