如何使用 Java 防止 XSS 攻击或 Rest API JSON 中的不可信数据?

Posted

技术标签:

【中文标题】如何使用 Java 防止 XSS 攻击或 Rest API JSON 中的不可信数据?【英文标题】:How to prevent XSS attacks or untrusted data in Rest API JSON using Java? 【发布时间】:2020-03-14 13:59:27 【问题描述】:

我开发了一个 Rest API 应用程序,并使用自定义 JWT 处理了身份验证和授权。 我想进一步使应用程序免受 XSS 攻击或验证不受信任的数据,这些数据可以针对 JSON 请求的每个字段进行处理。

我能否在这方面获得一些帮助,以便在请求的入门级进行有效的数据处理,而无需触及内部业务验证?

【问题讨论】:

【参考方案1】:

您不会在 Restful API 中过滤或转义数据。 API 应该与客户端无关。提供 XSS 保护是客户的责任。如果客户正确地完成了他们的工作,您最终会得到双重转义的数据。请记住,潜在客户可以是:

移动应用 后端 Web 服务器 网络浏览器 桌面应用程序 嵌入式系统/物联网

在上述情况下,只有有限数量的客户端和配置容易受到 XSS 攻击。

【讨论】:

【参考方案2】:

需要覆盖 Servlet 过滤器中的 HttpServletRequest(如果您使用的是 Servlet)。

    扩展存储 JSON 正文的 HttpServletRequestWrapper(目的是清理 JSON 正文)。

    剥离/转义符合条件的 JSON 值

扩展的“HttpServletRequestWrapper”

public class SanitizationRequestWrapper extends HttpServletRequestWrapper 
    
        public byte[] getBody() 
            return body;
        
    
        public void setBody(byte[] body) 
            this.body = body;
        
    
        private byte[] body;
    
        public SanitizationRequestWrapper(HttpServletRequest request) throws IOException 
            super(request);
            try 
                body = IOUtils.toByteArray(super.getInputStream());
            catch (NullPointerException e)
    
            
        
    
        @Override
        public ServletInputStream getInputStream() throws IOException 
            return new ServletInputStreamImpl(new ByteArrayInputStream(body));
        
    
        @Override
        public BufferedReader getReader() throws IOException 
            String enc = getCharacterEncoding();
            if (enc == null) enc = "UTF-8";
            return new BufferedReader(new InputStreamReader(getInputStream(), enc));
        
    
        private class ServletInputStreamImpl extends ServletInputStream 
    
            private InputStream is;
    
            public ServletInputStreamImpl(InputStream is) 
                this.is = is;
            
    
            public int read() throws IOException 
                return is.read();
            
    
            public boolean markSupported() 
                return false;
            
    
            public synchronized void mark(int i) 
                throw new RuntimeException(new IOException("mark/reset not supported"));
            
    
            public synchronized void reset() throws IOException 
                throw new IOException("mark/reset not supported");
            
        
    
    

净化请求正文的 Servlet 过滤器:

    public class XSSSanitizeFilters implements Filter 
            @Override
        public void destroy() 
        
    
        @Override
        public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException 
            HttpServletRequest request = (HttpServletRequest) arg0;
            HttpServletResponse response = (HttpServletResponse) arg1;
            SanitizationRequestWrapper sanitizeRequest = new SanitizationRequestWrapper(request);
                if (null != sanitizeRequest.getBody()) 
                    try 
                        sanitizeJson(sanitizeRequest);
                     catch (ParseException e) 
                        LOG.error("Unable to Sanitize the provided JSON .");
                    
                    arg2.doFilter(sanitizeRequest, arg1);
    
                 else 
                    arg2.doFilter(arg0, arg1);
                       
        
    
        public void init(FilterConfig filterConfig) throws ServletException 
    
        
    
        private void sanitizeJson(SanitizationRequestWrapper sanitizeRequest ) throws IOException, ParseException 
                JSONParser parser= new JSONParser();
                Object obj = parser.parse(sanitizeRequest.getReader());
                 ObjectMapper oMapper = new ObjectMapper();
                Map <String, Object> map = oMapper.convertValue(obj, Map.class);
                sanitizeRequest.setBody((new JSONObject(map)).toString().getBytes());
        
    
       

【讨论】:

感谢 Shantimoy!这非常适合我的要求。 "您需要覆盖 Servlet 过滤器中的 HttpServletRequest。" - 这个问题并不表明他们甚至在使用 servlet。 @JeffScottBrown:已更正!虽然示例使用的是servlet过滤器,但无论servlet如何,都可以使用“sanitizeJson()”方法。 @SHANTIMOYBANERJEE:这段代码非常适合我。我想了解正文中如何仍然存储请求正文流(json)。您已经使用了将引用传递给 HttpServletRequestWrapper 的 super(request)。一旦我们使用 IoUtils,它就会在内部调用 reader() 并且请求正文为空。所以在 SanitizationRequestWrapper 中,我们不应该有请求正文吗?当我们将新请求传递给 doFilter() 时,它是在内部使用在 SanitizationRequestWrapper 中创建的 body 属性,还是这里发生了其他事情? 如果 API 被提供自己的 XSS 清理或移动应用程序的 Web 服务器使用怎么办?这个问题专门针对 RESTful JSON API。你不应该为 XSS 清理输入或输出。【参考方案3】:

如果您使用的是 Spring,Spring 安全性可以保证基本级别的 XSS 攻击保护。你也可以使用

@Safehtml
private String value;

您还需要添加 org.jsoup 依赖项。

【讨论】:

【参考方案4】:

如果您的 API 不接受任何 HTML 字符,那么您可以遵循以下逻辑。

您可以使用 EncodeHtml 清理输入有效负载并将其与提供的有效负载进行比较。

如果 Sanitized Payload 和 Provided payload 不匹配,则存在一些 Html 内容,直接抛出异常。

String unsanitizedPayload = IOUtils.toString(multiReadRequest.getReader());
String sanitizedPayload = Encode.forHtmlContent(unsanitizedPayload);

if(!unsanitizedPayload.equals(sanitizedPayload)) 
    throw new Exception("Improper Payload");

【讨论】:

【参考方案5】:

为此,您需要使用 HTMLUtils 过滤 XSS 过滤器,该过滤器将过滤任何注入的脚本并阻止您的站点。完整的代码和实现请参考我的回答https://***.com/a/55741351/10232467。

【讨论】:

此解决方案适用于与请求标头一起传递的查询参数,但是我需要在 JSON 正文中的每个字段中删除相同的 PUT 或 POST 请求。

以上是关于如何使用 Java 防止 XSS 攻击或 Rest API JSON 中的不可信数据?的主要内容,如果未能解决你的问题,请参考以下文章

Java防止XSS攻击

asp项目中如何防止xss攻击

PHP如何防止XSS攻击

PHP如何防止XSS攻击与XSS攻击原理

如何正确防御xss攻击

apache 禁止trace或track防止xss攻击