如何在Spring WS中禁用DOCTYPE声明以防范XXE攻击?
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

