在文件上传中防止Xss注入

问题

之前在《Xss过滤器(Java)》使用OWASP库了,不过当时只能防止一般rest接口,对于上传流就有点搞不定了。这里继续使用OWASP库来防止xss注入。

步骤

Maven

<properties>
	<esapi.version>2.5.1.0</esapi.version>
    <antisamy.version>1.7.2</antisamy.version>
</properties>


<dependency>
    <groupId>org.owasp.esapi</groupId>
    <artifactId>esapi</artifactId>
    <version>${esapi.version}</version>
</dependency>

<dependency>
    <groupId>org.owasp.antisamy</groupId>
    <artifactId>antisamy</artifactId>
    <version>${antisamy.version}</version>
</dependency>

Spring

过滤器

ReplaceRequestBodyFilter.java

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class ReplaceRequestBodyFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String contentType = request.getContentType();
        if (null != contentType && contentType.contains("multipart/")) {//说明是文件上传
            XSSCommonsMultipartResolver commonsMultipartResolver = new XSSCommonsMultipartResolver();
            XSSMultipartHttpServletRequest resolveMultipart = (XSSMultipartHttpServletRequest) commonsMultipartResolver.resolveMultipart(request);
            filterChain.doFilter(resolveMultipart, response);
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

XSSCommonsMultipartResolver.java

import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

import javax.servlet.http.HttpServletRequest;

public class XSSCommonsMultipartResolver extends CommonsMultipartResolver {
    private boolean resolveLazily = false;
    public XSSCommonsMultipartResolver() {
        super();
    }

    @Override
    public void setResolveLazily(boolean resolveLazily) {
        this.resolveLazily = resolveLazily;
    }

    @Override
    public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
        Assert.notNull(request, "Request must not be null");
        if (this.resolveLazily) {
            return new XSSMultipartHttpServletRequest(request) {
                @Override
                protected void initializeMultipart() {
                    MultipartParsingResult parsingResult = parseRequest(request);
                    setMultipartFiles(parsingResult.getMultipartFiles());
                    setMultipartParameters(parsingResult.getMultipartParameters());
                    setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
                }
            };
        } else {
            MultipartParsingResult parsingResult = parseRequest(request);
            return new XSSMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
                    parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
        }
    }
}

XSSMultipartHttpServletRequest.java

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.owasp.esapi.ESAPI;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

@Slf4j
public class XSSMultipartHttpServletRequest extends DefaultMultipartHttpServletRequest {
    public XSSMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles, Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) {
        super(request);
        setMultipartFiles(mpFiles);
        setMultipartParameters(mpParams);
        setMultipartParameterContentTypes(mpParamContentTypes);
    }

    public XSSMultipartHttpServletRequest(HttpServletRequest request) {
        super(request);
    }

    @Override
    protected MultiValueMap<String, MultipartFile> getMultipartFiles() {
        MultiValueMap<String, MultipartFile> result = super.getMultipartFiles();
        Set<String> keySet = result.keySet();
        for (String key : keySet) {
            List<MultipartFile> fileList = result.get(key);
            List<MultipartFile> multipartFiles = new ArrayList<>();
            for (MultipartFile file : fileList) {

                if (file!= null && file.getOriginalFilename()!= null && !file.getContentType().contains("image")) {
                    String fileName = file.getOriginalFilename();
                    try {
                        InputStream inputStream = file.getInputStream();
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
                        FileItem fileItem = new DiskFileItemFactory().createItem("file", file.getContentType(), false, fileName);
                        OutputStream os = fileItem.getOutputStream();
                        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
                        while (bufferedReader.ready()){
                            String line = bufferedReader.readLine();
                            writer.write(stripXSS(line));
                            writer.newLine();
                        }
                        writer.flush();
                        CommonsMultipartFile commonsMultipartFile = new CommonsMultipartFile(fileItem);
                        multipartFiles.add(commonsMultipartFile);

                    } catch (IOException e) {
                        log.error("修改请求流时错误:", e);
                    }
                } else if (file!= null && file.getOriginalFilename()!= null && file.getContentType().startsWith("image")) {
                    try {
                        InputStream inputStream = file.getInputStream();
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
                        while (bufferedReader.ready()){
                            String line = bufferedReader.readLine();
                            if (StringUtils.hasText(line) && !validatorXSS(line)) {
                                throw new BaseException("无效图片,请换一张");
                            }
                        }
                        multipartFiles.add(file);

                    } catch (IOException e) {
                        log.error("修改请求流时错误:", e);
                    }
                } else {
                    multipartFiles.add(file);
                }
            }

            result.put(key, multipartFiles);

        }
        return result;
    }

   private String stripXSS(String value) {
        if (value != null) {
            // NOTE: It's highly recommended to use the ESAPI library and uncomment the following line to
            // avoid encoded attacks.
             value = ESAPI.encoder().canonicalize(value);

            // Avoid null characters
            value = value.replaceAll("", "");

            // Avoid anything between script tags
            Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid anything between pdf script tags
            scriptPattern = Pattern.compile("javascript", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid anything in a src='...' type of expression
            scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Remove any lonesome </script> tag
            scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Remove any lonesome <script ...> tag
            scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid eval(...) expressions
            scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid expression(...) expressions
            scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid javascript:... expressions
            scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid vbscript:... expressions
            scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid php:... expressions
            scriptPattern = Pattern.compile("<\\?php(.*?)\\?>", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");

            // Avoid οnlοad= expressions
            scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
        }
        return value;
    }

    private boolean validatorXSS(String value) {
        value = ESAPI.encoder().canonicalize(value);

        // Avoid anything between script tags
        Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
        Matcher matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }

        // Avoid anything between pdf script tags
        scriptPattern = Pattern.compile("javascript", Pattern.CASE_INSENSITIVE);
        matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }

        // Avoid anything in a src='...' type of expression
        scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }

        scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }

        // Remove any lonesome </script> tag
        scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
        matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }

        // Remove any lonesome <script ...> tag
        scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }

        // Avoid eval(...) expressions
        scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }

        // Avoid expression(...) expressions
        scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }

        // Avoid javascript:... expressions
        scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
        matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }

        // Avoid vbscript:... expressions
        scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
        matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }
        // Avoid php:... expressions
        scriptPattern = Pattern.compile("<\\?php(.*?)\\?>", Pattern.CASE_INSENSITIVE);
        matcher = scriptPattern.matcher(value);
        if (matcher.find()){
            return false;
        }

        // Avoid οnlοad= expressions
        scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        matcher = scriptPattern.matcher(value);
        return !matcher.find();
    }
}

这个类,才是防xss的关键。

esapi需要的配置文件

https://github.com/ESAPI/esapi-java-legacy/releases
这个页面下载esapi-2.5.1.0-configuration.jar文件,从中可以找到antisamy-esapi.xml,ESAPI.properties,esapi-java-logging.properties,validation.properties。这四个配置文件,复制到自己工程即可。

总结

到这里上传文件流防xss攻击,只需要3个类。主要是利用Spring过滤器机制来覆盖掉请求体对象。注意,一定要新建一个对象进行覆盖,而且不能关流。

参考:

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值