Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions api/src/org/labkey/api/util/XmlBeansUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.labkey.api.util;

import org.apache.logging.log4j.Logger;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlError;
import org.apache.xmlbeans.XmlException;
Expand All @@ -26,19 +27,26 @@
import org.labkey.api.portal.ProjectUrls;
import org.labkey.api.security.User;
import org.labkey.api.settings.LookAndFeelProperties;
import org.labkey.api.util.logging.LogHelper;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;

public class XmlBeansUtil
{
private static final Logger LOG = LogHelper.getLogger(XmlBeansUtil.class, "XML schema and validator XXE hardening");

private XmlBeansUtil()
{
}
Expand Down Expand Up @@ -197,4 +205,69 @@ private static DocumentBuilderFactory documentBuilderFactory(boolean allowDocTyp
result.setExpandEntityReferences(false);
return result;
}

/**
* A {@link SchemaFactory} hardened against XXE (CWE-611): external DTD access blocked and external
* schema access limited to local protocols, secure processing on. Not thread-safe, so a fresh
* instance is returned per call.
*/
public static SchemaFactory schemaFactory()
{
//noinspection SchemaFactory
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
require(() -> factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true));
attempt(() -> factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""), XMLConstants.ACCESS_EXTERNAL_DTD);
// Bundled schemas compose sibling XSDs via import/include; permit local file/jar resolution while blocking network (http/https/ftp) access
attempt(() -> factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file,jar"), XMLConstants.ACCESS_EXTERNAL_SCHEMA);
return factory;
}

// The Validator resolves entities in the instance document independently of the SchemaFactory, so it must be locked down separately.
public static Validator hardenValidator(Validator validator)
{
require(() -> validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true));
// Standalone Apache Xerces ignores the accessExternal* properties below, so these SAX features (which it does honor) are what actually block instance-document XXE on the server; the JDK factory relies on the properties instead.
attempt(() -> validator.setFeature("http://xml.org/sax/features/external-general-entities", false), "external-general-entities");
attempt(() -> validator.setFeature("http://xml.org/sax/features/external-parameter-entities", false), "external-parameter-entities");
attempt(() -> validator.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false), "load-external-dtd");
attempt(() -> validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""), XMLConstants.ACCESS_EXTERNAL_DTD);
attempt(() -> validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""), XMLConstants.ACCESS_EXTERNAL_SCHEMA);
return validator;
}

@FunctionalInterface
private interface XmlSetting
{
void apply() throws SAXException;
}

// FEATURE_SECURE_PROCESSING is honored by every JAXP implementation, so failure to set it is fatal.
private static void require(XmlSetting setting)
{
try
{
setting.apply();
}
catch (SAXException e)
{
throw UnexpectedException.wrap(e);
}
}

// Implementations recognize different subsets of the XXE controls (e.g. standalone Apache Xerces rejects the JAXP 1.5 accessExternal* properties, XERCESJ-1654), so each is attempted independently and a not-recognized setting is skipped rather than aborting the rest.
private static void attempt(XmlSetting setting, String name)
{
try
{
setting.apply();
}
catch (SAXNotRecognizedException | SAXNotSupportedException e)
{
LOG.debug("XML implementation does not recognize {}; relying on the other hardening settings", name);
}
catch (SAXException e)
{
throw UnexpectedException.wrap(e);
}
}
}