/* Copyright (c) 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.gdata.util.common.xml.parsing; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.SAXNotSupportedException; import org.xml.sax.XMLReader; import java.io.StringReader; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.validation.Schema; /** * Secures JDK-inbuilt Xerces parsers using the public APIs. * * * */ public class SecureGenericXMLFactory { private static final SecureEntityResolver NOOP_RESOLVER = new SecureEntityResolver(); /* Supports customization via subclassing */ protected SecureGenericXMLFactory() {} public static SAXParserFactory getSAXParserFactory(SAXParserFactory factory) throws ParserConfigurationException, SAXException { return new SecureSAXParserFactory(factory); } public static DocumentBuilderFactory getDocumentBuilderFactory( DocumentBuilderFactory factory) { return new SecureDocumentBuilderFactory(factory); } /** * Wraps an existing SAXParserFactory and ensures that any returned * SAXParser instances are secure. */ static protected class SecureSAXParserFactory extends SAXParserFactory { private SAXParserFactory factory; /** * Constructs a new SecureSAXParserFactory instance that delegates * most functionality to an existing instance, but overrides where * needed to protect against XXE attacks. * * @param factory the existing SAXParserFactory that should be secured. * @throws ParserConfigurationException on configuration errors. * @throws SAXException on configuration failures. */ protected SecureSAXParserFactory(SAXParserFactory factory) throws ParserConfigurationException, SAXException { this.factory = factory; /* Since we disable DTDs, we can't be validating. */ factory.setValidating(false); /* This should be the default, but let's be safe and try and disable it. * We also have to cater for older XML parsers that do not support this. */ try { factory.setXIncludeAware(false); } catch (UnsupportedOperationException e) { /* This is OK; older versions of the parser do not support XInclude at * all. */ } catch (NoSuchMethodError e) { /* This is OK; older versions of the parser do not support XInclude at * all. This is here for jdk 1.4 and earlier Xerces versions. */ } /* Setting the attribute * http://apache.org/xml/features/disallow-doctype-decl to true causes an * immediate exception when a DTD is encountered. Unfortunately, an XML * document will sometimes include a harmless DTD so we cannot ban DTDs * outright. */ try { factory.setFeature( "http://xml.org/sax/features/external-general-entities", false); } catch (IllegalArgumentException e) { /* OK. Not all parsers will support this attribute */ } catch (SAXNotRecognizedException e) { /* OK. Not all parsers will support this attribute */ } try { factory.setFeature( "http://xml.org/sax/features/external-parameter-entities",false); } catch (IllegalArgumentException e) { /* OK. Not all parsers will support this attribute */ } catch (SAXNotRecognizedException e) { /* OK. Not all parsers will support this attribute */ } try { factory.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false); } catch (IllegalArgumentException e) { /* OK. Not all parsers will support this attribute */ } catch (SAXNotRecognizedException e) { /* OK. Not all parsers will support this attribute */ } /* Again, older XML parsers do not support this. */ try { factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); } catch (IllegalArgumentException e) { /* OK. Not all parsers will support this attribute */ } catch (SAXNotRecognizedException e) { /* OK. Not all parsers will support this attribute */ } } @Override public SAXParser newSAXParser() throws ParserConfigurationException, SAXException { SAXParser parser = factory.newSAXParser(); XMLReader xmlReader = parser.getXMLReader(); xmlReader.setEntityResolver(NOOP_RESOLVER); return parser; } @Override public void setNamespaceAware(boolean awareness) { factory.setNamespaceAware(awareness); } @Override public void setValidating(boolean validating) { factory.setValidating(validating); } @Override public boolean isNamespaceAware() { return factory.isNamespaceAware(); } @Override public boolean isValidating() { return factory.isValidating(); } @Override public void setFeature(String name, boolean value) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException { factory.setFeature(name, value); } @Override public boolean getFeature(String name) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException { return factory.getFeature(name); } @Override public Schema getSchema() throws UnsupportedOperationException { return factory.getSchema(); } @Override public void setSchema(Schema schema) throws UnsupportedOperationException { factory.setSchema(schema); } @Override public void setXIncludeAware(boolean state) throws UnsupportedOperationException { factory.setXIncludeAware(state); } @Override public boolean isXIncludeAware() throws UnsupportedOperationException { return factory.isXIncludeAware(); } } /** * Wraps an existing DocumentBuilderFactory and ensures that any returned * DocumentBuilder instances are secure. */ static protected class SecureDocumentBuilderFactory extends DocumentBuilderFactory { private DocumentBuilderFactory factory; /** * Constructs a new SecureDocumentBuilderFactory instance that delegates * most functionality to an existing instance, but overrides where * needed to protect against XXE attacks. * * @param factory the existing DocumentBuilderFactory that should be * secured. */ protected SecureDocumentBuilderFactory(DocumentBuilderFactory factory) { this.factory = factory; /* Since we disable DTDs, we can't be validating. */ factory.setValidating(false); /* This should be the default, but let's be safe and try and disable it. * We also have to cater for older XML parsers that do not support this. */ try { factory.setXIncludeAware(false); } catch (UnsupportedOperationException e) { /* This is OK; older versions of the parser do not support XInclude at * all. */ } catch (NoSuchMethodError e) { /* This is OK; older versions of the parser do not support XInclude at * all. This is here for jdk 1.4 and earlier Xerces versions. */ } /* Setting the attribute * http://apache.org/xml/features/disallow-doctype-decl to true causes an * immediate exception when a DTD is encountered. Unfortunately, an XML * document will sometimes include a harmless DTD so we cannot ban DTDs * outright. */ try { factory.setAttribute( "http://xml.org/sax/features/external-general-entities", false); } catch (IllegalArgumentException e) { /* OK. Not all parsers will support this attribute */ } try { factory.setAttribute( "http://xml.org/sax/features/external-parameter-entities", false); } catch (IllegalArgumentException e) { /* OK. Not all parsers will support this attribute */ } try { factory.setAttribute( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false); } catch (IllegalArgumentException e) { /* OK. Not all parsers will support this attribute */ } /* Again, older XML parsers do not support this. */ try { factory.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); } catch (IllegalArgumentException e) { /* OK. Not all parsers will support this attribute */ } } @Override public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { DocumentBuilder docBuilder = factory.newDocumentBuilder(); docBuilder.setEntityResolver(NOOP_RESOLVER); return docBuilder; } @Override public void setNamespaceAware(boolean awareness) { factory.setNamespaceAware(awareness); } @Override public void setValidating(boolean validating) { factory.setValidating(validating); } @Override public void setIgnoringElementContentWhitespace(boolean whitespace) { factory.setIgnoringElementContentWhitespace(whitespace); } @Override public void setExpandEntityReferences(boolean expandEntityRef) { factory.setExpandEntityReferences(expandEntityRef); } @Override public void setIgnoringComments(boolean ignoreComments) { factory.setIgnoringComments(ignoreComments); } @Override public void setCoalescing(boolean coalescing) { factory.setCoalescing(coalescing); } @Override public boolean isNamespaceAware() { return factory.isNamespaceAware(); } @Override public boolean isValidating() { return factory.isValidating(); } @Override public boolean isIgnoringElementContentWhitespace() { return factory.isIgnoringElementContentWhitespace(); } @Override public boolean isExpandEntityReferences() { return factory.isExpandEntityReferences(); } @Override public boolean isIgnoringComments() { return factory.isIgnoringComments(); } @Override public boolean isCoalescing() { return factory.isCoalescing(); } @Override public void setAttribute(String name, Object value) throws IllegalArgumentException { factory.setAttribute(name, value); } @Override public Object getAttribute(String name) throws IllegalArgumentException { return factory.getAttribute(name); } @Override public void setFeature(String name, boolean value) throws ParserConfigurationException { factory.setFeature(name, value); } @Override public boolean getFeature(String name) throws ParserConfigurationException { return factory.getFeature(name); } @Override public Schema getSchema() throws UnsupportedOperationException { return factory.getSchema(); } @Override public void setSchema(Schema schema) throws UnsupportedOperationException { factory.setSchema(schema); } @Override public void setXIncludeAware(boolean state) throws UnsupportedOperationException { factory.setXIncludeAware(state); } public boolean isIncludeAware() throws UnsupportedOperationException { return factory.isXIncludeAware(); } } /** * A secure EntityResolver that returns an empty string in response to * any attempt to resolve an external entitity. The class is used by our * secure version of the internal saxon SAX parser. */ private static final class SecureEntityResolver implements EntityResolver { public InputSource resolveEntity(String publicId, String systemId) { return new InputSource(new StringReader("")); } } }