You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring WS中禁用DOCTYPE声明以防范XXE攻击?

Blocking DOCTYPE Declarations in Spring WS to Prevent XXE Bypasses

Great question—let's break down how to fully block DOCTYPE declarations in your Spring WS setup, so even escaped entity references can't sneak through to your downstream processing.

First, let's recap your current situation: You're using AxiomSoapMessageFactory with its default secure settings (supportingExternalEntities=false, replacingEntityReferences=false), which blocks direct entity references but allows requests with escaped references like &xxe;. To fix this, we need to stop any request containing a DOCTYPE declaration before it even gets parsed.

Here are three robust solutions, ordered by integration tightness with Spring WS:


1. Customize Axiom's StAX Parser to Reject DTDs Entirely

The cleanest approach is to configure the underlying StAX XML parser used by Axiom to outright reject any document containing a DTD. This works at the parsing level, so it's impossible for DOCTYPE declarations to slip through.

Step 1: Create a Secure Axiom Message Factory

Extend the default AxiomSoapMessageFactory to override the StAX input factory settings:

import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory;
import javax.xml.stream.XMLInputFactory;

public class SecureAxiomSoapMessageFactory extends AxiomSoapMessageFactory {

    @Override
    protected XMLInputFactory createXmlInputFactory() {
        XMLInputFactory inputFactory = super.createXmlInputFactory();
        // Disable DTD support completely
        inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
        // Add extra safeguards for external entities (redundant here but good practice)
        inputFactory.setProperty(XMLInputFactory.EXTERNAL_GENERAL_ENTITIES, false);
        inputFactory.setProperty(XMLInputFactory.EXTERNAL_PARAMETER_ENTITIES, false);
        return inputFactory;
    }
}

Step 2: Register the Custom Factory in Spring

Replace your default SoapMessageFactory bean with the secure version:

@Configuration
@EnableWs
public class WebServiceConfig extends WsConfigurerAdapter {

    @Bean
    public SoapMessageFactory messageFactory() {
        SecureAxiomSoapMessageFactory factory = new SecureAxiomSoapMessageFactory();
        // Keep your existing secure settings
        factory.setSupportingExternalEntities(false);
        factory.setReplacingEntityReferences(false);
        return factory;
    }

    // Your other bean definitions (endpoint mappings, etc.) go here
}

When a request with a DOCTYPE comes in, the StAX parser will throw an exception immediately, preventing it from reaching your endpoint logic.


2. Add a Spring WS Endpoint Interceptor

If you want more control over the error response, you can create an interceptor that checks the raw request content for DOCTYPE declarations before parsing.

Step 1: Implement the Interceptor

import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.EndpointInterceptor;
import org.springframework.ws.transport.TransportInputStream;
import org.apache.axiom.soap.SOAPProcessingException;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class DoctypeBlockingInterceptor implements EndpointInterceptor {

    private static final String DOCTYPE_PATTERN = "<!DOCTYPE";

    @Override
    public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
        TransportInputStream inputStream = (TransportInputStream) messageContext.getRequest().getPayloadSource();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream.getInputStream(), StandardCharsets.UTF_8));
        
        StringBuilder requestContent = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            requestContent.append(line);
            // Check for DOCTYPE (case-insensitive)
            if (line.toUpperCase().contains(DOCTYPE_PATTERN)) {
                throw new SOAPProcessingException("DOCTYPE declarations are prohibited in SOAP requests");
            }
        }
        
        // Reset the input stream so downstream components can read it
        inputStream.reset();
        return true;
    }

    // Default implementations for other interceptor methods
    @Override
    public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
        return true;
    }

    @Override
    public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
        return true;
    }

    @Override
    public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception {}
}

Step 2: Register the Interceptor

Add it to your Spring WS configuration:

@Configuration
@EnableWs
public class WebServiceConfig extends WsConfigurerAdapter {

    @Override
    public void addInterceptors(List<EndpointInterceptor> interceptors) {
        interceptors.add(new DoctypeBlockingInterceptor());
        super.addInterceptors(interceptors);
    }

    // Your other beans here
}

3. Servlet-Level Filter (For Global Protection)

If you want to block DOCTYPEs across your entire application (not just Spring WS endpoints), use a Servlet Filter to intercept requests before they reach Spring WS.

Step 1: Create the Filter

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class DoctypeBlockingFilter implements Filter {

    private static final String DOCTYPE_PATTERN = "<!DOCTYPE";

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        BufferedReader reader = new BufferedReader(new InputStreamReader(httpRequest.getInputStream(), StandardCharsets.UTF_8));
        
        StringBuilder content = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            content.append(line);
            if (line.toUpperCase().contains(DOCTYPE_PATTERN)) {
                HttpServletResponse httpResponse = (HttpServletResponse) response;
                httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                httpResponse.getWriter().write("DOCTYPE declarations are not allowed");
                return;
            }
        }
        
        // Wrap the request to allow downstream components to re-read the content
        ReusableRequestWrapper wrappedRequest = new ReusableRequestWrapper(httpRequest, content.toString());
        chain.doFilter(wrappedRequest, response);
    }

    // Helper class to wrap request for reusability
    private static class ReusableRequestWrapper extends HttpServletRequestWrapper {
        private final byte[] content;

        public ReusableRequestWrapper(HttpServletRequest request, String content) {
            super(request);
            this.content = content.getBytes(StandardCharsets.UTF_8);
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return new ServletInputStream() {
                private final ByteArrayInputStream input = new ByteArrayInputStream(content);

                @Override
                public int read() throws IOException {
                    return input.read();
                }

                @Override
                public boolean isFinished() {
                    return input.available() == 0;
                }

                @Override
                public boolean isReady() {
                    return true;
                }

                @Override
                public void setReadListener(ReadListener listener) {}
            };
        }
    }

    // No-op init and destroy methods
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void destroy() {}
}

Step 2: Register the Filter

Map it to your Spring WS servlet path (e.g., in web.xml or via Servlet 3.0+ annotations):

<filter>
    <filter-name>doctypeBlockingFilter</filter-name>
    <filter-class>com.yourpackage.DoctypeBlockingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>doctypeBlockingFilter</filter-name>
    <url-pattern>/ws/*</url-pattern> <!-- Match your Spring WS endpoint path -->
</filter-mapping>

Recommendation

The custom Axiom message factory (Option 1) is the most reliable and maintainable solution—it integrates directly with Spring WS's parsing pipeline and requires minimal extra code. It ensures that any DOCTYPE declaration is rejected at the lowest possible level, preventing any bypass attempts.

内容的提问来源于stack exchange,提问作者sonu131

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:23:07