在文件上传中防止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
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
当面试官询问我SQL注入XSS文件上传以及RCE远程命令执行漏洞的相关知识时,你可以使用以下话术进行回答: 1. 面试官:请介绍一下SQL注入漏洞。 你:SQL注入是一种常见的web应用程序漏洞,攻击者通过在用户输入插入恶意的SQL代码,成功绕过应用程序的输入验证,从而可以执行未经授权的数据库查询、修改或删除操作。为防止SQL注入攻击,我们需要对用户输入进行严格的验证和过滤,并使用参数化查询或预编译语句来防止恶意SQL代码的执行。 2. 面试官:请解释一下XSS漏洞。 你:XSS(跨站脚本)漏洞是一种允许攻击者将恶意脚本注入到web应用程序的漏洞。当用户浏览包含恶意脚本的页面时,这些脚本会在用户的浏览器上执行,导致攻击者能够窃取用户的登录凭证、修改页面内容或进行其他恶意操作。为了防止XSS攻击,我们需要对用户输入进行合适的验证和过滤,并使用安全的编码方式来输出数据,如HTML转义或使用Content Security Policy(CSP)来限制脚本的执行。 3. 面试官:请讲解一下文件上传漏洞。 你:文件上传漏洞是指在web应用程序存在未正确验证用户上传文件的安全问题。攻击者可以通过上传带有恶意代码的文件,从而在服务器上执行恶意操作,如执行任意命令、获取敏感数据或远程控制服务器。为了防止文件上传漏洞,我们需要对用户上传的文件进行严格的验证和过滤,限制可上传文件的类型和大小,并在保存、读取和执行文件时采取适当的安全措施。 4. 面试官:请简要说明一下RCE远程命令执行漏洞。 你:RCE(远程命令执行)漏洞是一种允许攻击者在受影响的系统上执行任意命令的漏洞。攻击者通过利用应用程序的安全漏洞,成功注入恶意代码并执行系统命令。这种漏洞可能导致攻击者完全控制受影响的系统,进行敏感数据的窃取、服务器崩溃等恶意行为。为了防止RCE漏洞,我们需要保持应用程序和服务器的补丁更新,进行输入验证和过滤,以及使用安全编码和访问控制机制来限制恶意代码的执行。 请记住,以上是对每种漏洞的简要解释,你可以根据自己的了解和经验进行适当的展开和补充。在回答问题时,尽量用简明扼要的语言表达,并展示你对漏洞原理和相关防御措施的深入理解。祝你面试顺利!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值