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

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章