c226ae
From 3b2a73ad85da069637a73beca432950204535979 Mon Sep 17 00:00:00 2001
c226ae
From: Ondrej Dubaj <odubaj@redhat.com>
c226ae
Date: Wed, 22 Jul 2020 11:39:42 +0200
c226ae
Subject: [PATCH] Fix for XXE vulnerability
c226ae
c226ae
by defaulting to disabling external access and doc types. The
c226ae
legacy insecure behavior can be restored via the new connection property xmlFactoryFactory
c226ae
with a value of LEGACY_INSECURE. Alternatively, a custom class name can be specified that
c226ae
implements org.postgresql.xml.PGXmlFactoryFactory and takes a no argument constructor.
c226ae
c226ae
* refactor: Clean up whitespace in existing PgSQLXMLTest
c226ae
* fix: Fix XXE vulnerability in PgSQLXML by disabling external access and doctypes
c226ae
* fix: Add missing getter and setter for XML_FACTORY_FACTORY to BasicDataSource
c226ae
---
c226ae
 .../main/java/org/postgresql/PGProperty.java  |  11 ++
c226ae
 .../org/postgresql/core/BaseConnection.java   |   9 ++
c226ae
 .../postgresql/ds/common/BaseDataSource.java  |   8 +
c226ae
 .../org/postgresql/jdbc/PgConnection.java     |  41 +++++
c226ae
 .../java/org/postgresql/jdbc/PgSQLXML.java    |  44 +++---
c226ae
 .../xml/DefaultPGXmlFactoryFactory.java       | 141 ++++++++++++++++++
c226ae
 .../xml/EmptyStringEntityResolver.java        |  23 +++
c226ae
 .../LegacyInsecurePGXmlFactoryFactory.java    |  57 +++++++
c226ae
 .../org/postgresql/xml/NullErrorHandler.java  |  25 ++++
c226ae
 .../postgresql/xml/PGXmlFactoryFactory.java   |  30 ++++
c226ae
 .../org/postgresql/jdbc/PgSQLXMLTest.java     | 124 +++++++++++++++
c226ae
 .../postgresql/test/jdbc2/Jdbc2TestSuite.java |   2 +
c226ae
 12 files changed, 489 insertions(+), 26 deletions(-)
c226ae
 create mode 100644 pgjdbc/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java
c226ae
 create mode 100644 pgjdbc/src/main/java/org/postgresql/xml/EmptyStringEntityResolver.java
c226ae
 create mode 100644 pgjdbc/src/main/java/org/postgresql/xml/LegacyInsecurePGXmlFactoryFactory.java
c226ae
 create mode 100644 pgjdbc/src/main/java/org/postgresql/xml/NullErrorHandler.java
c226ae
 create mode 100644 pgjdbc/src/main/java/org/postgresql/xml/PGXmlFactoryFactory.java
c226ae
 create mode 100644 pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java
c226ae
c226ae
diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
c226ae
index e56e05e..7c2eed8 100644
c226ae
--- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java
c226ae
+++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
c226ae
@@ -331,6 +331,17 @@ public enum PGProperty {
c226ae
    */
c226ae
   USE_SPNEGO("useSpnego", "false", "Use SPNEGO in SSPI authentication requests"),
c226ae
 
c226ae
+  /**
c226ae
+   * Factory class to instantiate factories for XML processing.
c226ae
+   * The default factory disables external entity processing.
c226ae
+   * Legacy behavior with external entity processing can be enabled by specifying a value of LEGACY_INSECURE.
c226ae
+   * Or specify a custom class that implements {@code org.postgresql.xml.PGXmlFactoryFactory}.
c226ae
+   */
c226ae
+  XML_FACTORY_FACTORY(
c226ae
+    "xmlFactoryFactory",
c226ae
+    "",
c226ae
+    "Factory class to instantiate factories for XML processing"),
c226ae
+
c226ae
   /**
c226ae
    * Force one of
c226ae
    * 
    c226ae
    diff --git a/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java b/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java
    c226ae
    index 1d316a0..5f85964 100644
    c226ae
    --- a/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java
    c226ae
    +++ b/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java
    c226ae
    @@ -9,6 +9,7 @@ import org.postgresql.PGConnection;
    c226ae
     import org.postgresql.jdbc.FieldMetadata;
    c226ae
     import org.postgresql.jdbc.TimestampUtils;
    c226ae
     import org.postgresql.util.LruCache;
    c226ae
    +import org.postgresql.xml.PGXmlFactoryFactory;
    c226ae
     
    c226ae
     import java.sql.Connection;
    c226ae
     import java.sql.ResultSet;
    c226ae
    @@ -202,4 +203,12 @@ public interface BaseConnection extends PGConnection, Connection {
    c226ae
        * @param flushCacheOnDeallocate true if statement cache should be reset when "deallocate/discard" message observed
    c226ae
        */
    c226ae
       void setFlushCacheOnDeallocate(boolean flushCacheOnDeallocate);
    c226ae
    +
    c226ae
    +  /**
    c226ae
    +   * Retrieve the factory to instantiate XML processing factories.
    c226ae
    +   *
    c226ae
    +   * @return The factory to use to instantiate XML processing factories
    c226ae
    +   * @throws SQLException if the class cannot be found or instantiated.
    c226ae
    +   */
    c226ae
    +  PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException;
    c226ae
     }
    c226ae
    diff --git a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
    c226ae
    index 268d936..2fb4e06 100644
    c226ae
    --- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
    c226ae
    +++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
    c226ae
    @@ -1313,4 +1313,12 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
    c226ae
         return Logger.getLogger("org.postgresql");
    c226ae
       }
    c226ae
       //#endif
    c226ae
    +
    c226ae
    +  public String getXmlFactoryFactory() {
    c226ae
    +    return PGProperty.XML_FACTORY_FACTORY.get(properties);
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  public void setXmlFactoryFactory(String xmlFactoryFactory) {
    c226ae
    +    PGProperty.XML_FACTORY_FACTORY.set(properties, xmlFactoryFactory);
    c226ae
    +  }
    c226ae
     }
    c226ae
    diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
    c226ae
    index 7140ab4..c9c4ada 100644
    c226ae
    --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
    c226ae
    +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
    c226ae
    @@ -37,6 +37,9 @@ import org.postgresql.util.PGBinaryObject;
    c226ae
     import org.postgresql.util.PGobject;
    c226ae
     import org.postgresql.util.PSQLException;
    c226ae
     import org.postgresql.util.PSQLState;
    c226ae
    +import org.postgresql.xml.DefaultPGXmlFactoryFactory;
    c226ae
    +import org.postgresql.xml.LegacyInsecurePGXmlFactoryFactory;
    c226ae
    +import org.postgresql.xml.PGXmlFactoryFactory;
    c226ae
     
    c226ae
     import java.io.IOException;
    c226ae
     import java.sql.Array;
    c226ae
    @@ -142,6 +145,9 @@ public class PgConnection implements BaseConnection {
    c226ae
     
    c226ae
       private final LruCache<FieldMetadata.Key, FieldMetadata> fieldMetadataCache;
    c226ae
     
    c226ae
    +  private final String xmlFactoryFactoryClass;
    c226ae
    +  private PGXmlFactoryFactory xmlFactoryFactory;
    c226ae
    +
    c226ae
       final CachedQuery borrowQuery(String sql) throws SQLException {
    c226ae
         return queryExecutor.borrowQuery(sql);
    c226ae
       }
    c226ae
    @@ -290,6 +296,8 @@ public class PgConnection implements BaseConnection {
    c226ae
             false);
    c226ae
     
    c226ae
         replicationConnection = PGProperty.REPLICATION.get(info) != null;
    c226ae
    +
    c226ae
    +    xmlFactoryFactoryClass = PGProperty.XML_FACTORY_FACTORY.get(info);
    c226ae
       }
    c226ae
     
    c226ae
       private static Set<Integer> getBinaryOids(Properties info) throws PSQLException {
    c226ae
    @@ -1729,4 +1737,37 @@ public class PgConnection implements BaseConnection {
    c226ae
         }
    c226ae
         return ps;
    c226ae
       }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException {
    c226ae
    +    if (xmlFactoryFactory == null) {
    c226ae
    +      if (xmlFactoryFactoryClass == null || xmlFactoryFactoryClass.equals("")) {
    c226ae
    +        xmlFactoryFactory = DefaultPGXmlFactoryFactory.INSTANCE;
    c226ae
    +      } else if (xmlFactoryFactoryClass.equals("LEGACY_INSECURE")) {
    c226ae
    +        xmlFactoryFactory = LegacyInsecurePGXmlFactoryFactory.INSTANCE;
    c226ae
    +      } else {
    c226ae
    +        Class clazz;
    c226ae
    +        try {
    c226ae
    +          clazz = Class.forName(xmlFactoryFactoryClass);
    c226ae
    +        } catch (ClassNotFoundException ex) {
    c226ae
    +          throw new PSQLException(
    c226ae
    +              GT.tr("Could not instantiate xmlFactoryFactory: {0}", xmlFactoryFactoryClass),
    c226ae
    +              PSQLState.INVALID_PARAMETER_VALUE, ex);
    c226ae
    +        }
    c226ae
    +        if (!clazz.isAssignableFrom(PGXmlFactoryFactory.class)) {
    c226ae
    +          throw new PSQLException(
    c226ae
    +              GT.tr("Connection property xmlFactoryFactory must implement PGXmlFactoryFactory: {0}", xmlFactoryFactoryClass),
    c226ae
    +              PSQLState.INVALID_PARAMETER_VALUE);
    c226ae
    +        }
    c226ae
    +        try {
    c226ae
    +          xmlFactoryFactory = (PGXmlFactoryFactory) clazz.newInstance();
    c226ae
    +        } catch (Exception ex) {
    c226ae
    +          throw new PSQLException(
    c226ae
    +              GT.tr("Could not instantiate xmlFactoryFactory: {0}", xmlFactoryFactoryClass),
    c226ae
    +              PSQLState.INVALID_PARAMETER_VALUE, ex);
    c226ae
    +        }
    c226ae
    +      }
    c226ae
    +    }
    c226ae
    +    return xmlFactoryFactory;
    c226ae
    +  }
    c226ae
     }
    c226ae
    diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java
    c226ae
    index 9fb0eed..dd7d5ac 100644
    c226ae
    --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java
    c226ae
    +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java
    c226ae
    @@ -9,10 +9,11 @@ import org.postgresql.core.BaseConnection;
    c226ae
     import org.postgresql.util.GT;
    c226ae
     import org.postgresql.util.PSQLException;
    c226ae
     import org.postgresql.util.PSQLState;
    c226ae
    +import org.postgresql.xml.DefaultPGXmlFactoryFactory;
    c226ae
    +import org.postgresql.xml.PGXmlFactoryFactory;
    c226ae
     
    c226ae
    -import org.xml.sax.ErrorHandler;
    c226ae
     import org.xml.sax.InputSource;
    c226ae
    -import org.xml.sax.SAXParseException;
    c226ae
    +import org.xml.sax.XMLReader;
    c226ae
     
    c226ae
     import java.io.ByteArrayInputStream;
    c226ae
     import java.io.ByteArrayOutputStream;
    c226ae
    @@ -27,7 +28,6 @@ import java.sql.SQLException;
    c226ae
     import java.sql.SQLXML;
    c226ae
     
    c226ae
     import javax.xml.parsers.DocumentBuilder;
    c226ae
    -import javax.xml.parsers.DocumentBuilderFactory;
    c226ae
     import javax.xml.stream.XMLInputFactory;
    c226ae
     import javax.xml.stream.XMLOutputFactory;
    c226ae
     import javax.xml.stream.XMLStreamException;
    c226ae
    @@ -77,6 +77,13 @@ public class PgSQLXML implements SQLXML {
    c226ae
         _freed = false;
    c226ae
       }
    c226ae
     
    c226ae
    +  private PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException {
    c226ae
    +    if (_conn != null) {
    c226ae
    +      return _conn.getXmlFactoryFactory();
    c226ae
    +    }
    c226ae
    +    return DefaultPGXmlFactoryFactory.INSTANCE;
    c226ae
    +  }
    c226ae
    +
    c226ae
       public synchronized void free() {
    c226ae
         _freed = true;
    c226ae
         _data = null;
    c226ae
    @@ -128,18 +135,17 @@ public class PgSQLXML implements SQLXML {
    c226ae
     
    c226ae
         try {
    c226ae
           if (sourceClass == null || DOMSource.class.equals(sourceClass)) {
    c226ae
    -        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    c226ae
    -        DocumentBuilder builder = factory.newDocumentBuilder();
    c226ae
    -        builder.setErrorHandler(new NonPrintingErrorHandler());
    c226ae
    +        DocumentBuilder builder = getXmlFactoryFactory().newDocumentBuilder();
    c226ae
             InputSource input = new InputSource(new StringReader(_data));
    c226ae
             return (T) new DOMSource(builder.parse(input));
    c226ae
           } else if (SAXSource.class.equals(sourceClass)) {
    c226ae
    +        XMLReader reader = getXmlFactoryFactory().createXMLReader();
    c226ae
             InputSource is = new InputSource(new StringReader(_data));
    c226ae
    -        return (T) new SAXSource(is);
    c226ae
    +        return (T) new SAXSource(reader, is);
    c226ae
           } else if (StreamSource.class.equals(sourceClass)) {
    c226ae
             return (T) new StreamSource(new StringReader(_data));
    c226ae
           } else if (StAXSource.class.equals(sourceClass)) {
    c226ae
    -        XMLInputFactory xif = XMLInputFactory.newInstance();
    c226ae
    +        XMLInputFactory xif = getXmlFactoryFactory().newXMLInputFactory();
    c226ae
             XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(_data));
    c226ae
             return (T) new StAXSource(xsr);
    c226ae
           }
    c226ae
    @@ -168,6 +174,7 @@ public class PgSQLXML implements SQLXML {
    c226ae
       public synchronized Writer setCharacterStream() throws SQLException {
    c226ae
         checkFreed();
    c226ae
         initialize();
    c226ae
    +    _active = true;
    c226ae
         _stringWriter = new StringWriter();
    c226ae
         return _stringWriter;
    c226ae
       }
    c226ae
    @@ -182,8 +189,7 @@ public class PgSQLXML implements SQLXML {
    c226ae
           return (T) _domResult;
    c226ae
         } else if (SAXResult.class.equals(resultClass)) {
    c226ae
           try {
    c226ae
    -        SAXTransformerFactory transformerFactory =
    c226ae
    -            (SAXTransformerFactory) SAXTransformerFactory.newInstance();
    c226ae
    +        SAXTransformerFactory transformerFactory = getXmlFactoryFactory().newSAXTransformerFactory();
    c226ae
             TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
    c226ae
             _stringWriter = new StringWriter();
    c226ae
             transformerHandler.setResult(new StreamResult(_stringWriter));
    c226ae
    @@ -200,7 +206,7 @@ public class PgSQLXML implements SQLXML {
    c226ae
         } else if (StAXResult.class.equals(resultClass)) {
    c226ae
           _stringWriter = new StringWriter();
    c226ae
           try {
    c226ae
    -        XMLOutputFactory xof = XMLOutputFactory.newInstance();
    c226ae
    +        XMLOutputFactory xof = getXmlFactoryFactory().newXMLOutputFactory();
    c226ae
             XMLStreamWriter xsw = xof.createXMLStreamWriter(_stringWriter);
    c226ae
             _active = true;
    c226ae
             return (T) new StAXResult(xsw);
    c226ae
    @@ -262,7 +268,7 @@ public class PgSQLXML implements SQLXML {
    c226ae
           // and use the identify transform to get it into a
    c226ae
           // friendlier result format.
    c226ae
           try {
    c226ae
    -        TransformerFactory factory = TransformerFactory.newInstance();
    c226ae
    +        TransformerFactory factory = getXmlFactoryFactory().newTransformerFactory();
    c226ae
             Transformer transformer = factory.newTransformer();
    c226ae
             DOMSource domSource = new DOMSource(_domResult.getNode());
    c226ae
             StringWriter stringWriter = new StringWriter();
    c226ae
    @@ -289,19 +295,5 @@ public class PgSQLXML implements SQLXML {
    c226ae
         }
    c226ae
         _initialized = true;
    c226ae
       }
    c226ae
    -
    c226ae
    -  // Don't clutter System.err with errors the user can't silence.
    c226ae
    -  // If something bad really happens an exception will be thrown.
    c226ae
    -  static class NonPrintingErrorHandler implements ErrorHandler {
    c226ae
    -    public void error(SAXParseException e) {
    c226ae
    -    }
    c226ae
    -
    c226ae
    -    public void fatalError(SAXParseException e) {
    c226ae
    -    }
    c226ae
    -
    c226ae
    -    public void warning(SAXParseException e) {
    c226ae
    -    }
    c226ae
    -  }
    c226ae
    -
    c226ae
     }
    c226ae
     
    c226ae
    diff --git a/pgjdbc/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java b/pgjdbc/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java
    c226ae
    new file mode 100644
    c226ae
    index 0000000..b6a381d
    c226ae
    --- /dev/null
    c226ae
    +++ b/pgjdbc/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java
    c226ae
    @@ -0,0 +1,141 @@
    c226ae
    +
    c226ae
    +/*
    c226ae
    + * Copyright (c) 2020, PostgreSQL Global Development Group
    c226ae
    + * See the LICENSE file in the project root for more information.
    c226ae
    + */
    c226ae
    +
    c226ae
    +package org.postgresql.xml;
    c226ae
    +
    c226ae
    +import org.xml.sax.SAXException;
    c226ae
    +import org.xml.sax.XMLReader;
    c226ae
    +import org.xml.sax.helpers.XMLReaderFactory;
    c226ae
    +
    c226ae
    +import javax.xml.XMLConstants;
    c226ae
    +import javax.xml.parsers.DocumentBuilder;
    c226ae
    +import javax.xml.parsers.DocumentBuilderFactory;
    c226ae
    +import javax.xml.parsers.ParserConfigurationException;
    c226ae
    +import javax.xml.stream.XMLInputFactory;
    c226ae
    +import javax.xml.stream.XMLOutputFactory;
    c226ae
    +import javax.xml.transform.TransformerFactory;
    c226ae
    +import javax.xml.transform.sax.SAXTransformerFactory;
    c226ae
    +
    c226ae
    +/**
    c226ae
    + * Default implementation of PGXmlFactoryFactory that configures each factory per OWASP recommendations.
    c226ae
    + *
    c226ae
    + * @see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
    c226ae
    + */
    c226ae
    +public class DefaultPGXmlFactoryFactory implements PGXmlFactoryFactory {
    c226ae
    +  public static final DefaultPGXmlFactoryFactory INSTANCE = new DefaultPGXmlFactoryFactory();
    c226ae
    +
    c226ae
    +  private DefaultPGXmlFactoryFactory() {
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  private DocumentBuilderFactory getDocumentBuilderFactory() {
    c226ae
    +    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    c226ae
    +    setFactoryProperties(factory);
    c226ae
    +    factory.setXIncludeAware(false);
    c226ae
    +    factory.setExpandEntityReferences(false);
    c226ae
    +    return factory;
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
    c226ae
    +    DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder();
    c226ae
    +    builder.setEntityResolver(EmptyStringEntityResolver.INSTANCE);
    c226ae
    +    builder.setErrorHandler(NullErrorHandler.INSTANCE);
    c226ae
    +    return builder;
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public TransformerFactory newTransformerFactory() {
    c226ae
    +    TransformerFactory factory = TransformerFactory.newInstance();
    c226ae
    +    setFactoryProperties(factory);
    c226ae
    +    return factory;
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public SAXTransformerFactory newSAXTransformerFactory() {
    c226ae
    +    SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
    c226ae
    +    setFactoryProperties(factory);
    c226ae
    +    return factory;
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public XMLInputFactory newXMLInputFactory() {
    c226ae
    +    XMLInputFactory factory = XMLInputFactory.newInstance();
    c226ae
    +    setPropertyQuietly(factory, XMLInputFactory.SUPPORT_DTD, false);
    c226ae
    +    setPropertyQuietly(factory, XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
    c226ae
    +    return factory;
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public XMLOutputFactory newXMLOutputFactory() {
    c226ae
    +    XMLOutputFactory factory = XMLOutputFactory.newInstance();
    c226ae
    +    return factory;
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public XMLReader createXMLReader() throws SAXException {
    c226ae
    +    XMLReader factory = XMLReaderFactory.createXMLReader();
    c226ae
    +    setFeatureQuietly(factory, "http://apache.org/xml/features/disallow-doctype-decl", true);
    c226ae
    +    setFeatureQuietly(factory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
    c226ae
    +    setFeatureQuietly(factory, "http://xml.org/sax/features/external-general-entities", false);
    c226ae
    +    setFeatureQuietly(factory, "http://xml.org/sax/features/external-parameter-entities", false);
    c226ae
    +    factory.setErrorHandler(NullErrorHandler.INSTANCE);
    c226ae
    +    return factory;
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  private static void setFeatureQuietly(Object factory, String name, boolean value) {
    c226ae
    +    try {
    c226ae
    +      if (factory instanceof DocumentBuilderFactory) {
    c226ae
    +        ((DocumentBuilderFactory) factory).setFeature(name, value);
    c226ae
    +      } else if (factory instanceof TransformerFactory) {
    c226ae
    +        ((TransformerFactory) factory).setFeature(name, value);
    c226ae
    +      } else if (factory instanceof XMLReader) {
    c226ae
    +        ((XMLReader) factory).setFeature(name, value);
    c226ae
    +      } else {
    c226ae
    +        throw new Error("Invalid factory class: " + factory.getClass());
    c226ae
    +      }
    c226ae
    +      return;
    c226ae
    +    } catch (Exception ignore) {
    c226ae
    +    }
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  private static void setAttributeQuietly(Object factory, String name, Object value) {
    c226ae
    +    try {
    c226ae
    +      if (factory instanceof DocumentBuilderFactory) {
    c226ae
    +        ((DocumentBuilderFactory) factory).setAttribute(name, value);
    c226ae
    +      } else if (factory instanceof TransformerFactory) {
    c226ae
    +        ((TransformerFactory) factory).setAttribute(name, value);
    c226ae
    +      } else {
    c226ae
    +        throw new Error("Invalid factory class: " + factory.getClass());
    c226ae
    +      }
    c226ae
    +    } catch (Exception ignore) {
    c226ae
    +    }
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  private static void setFactoryProperties(Object factory) {
    c226ae
    +    setFeatureQuietly(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
    c226ae
    +    setFeatureQuietly(factory, "http://apache.org/xml/features/disallow-doctype-decl", true);
    c226ae
    +    setFeatureQuietly(factory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
    c226ae
    +    setFeatureQuietly(factory, "http://xml.org/sax/features/external-general-entities", false);
    c226ae
    +    setFeatureQuietly(factory, "http://xml.org/sax/features/external-parameter-entities", false);
    c226ae
    +    // Values from XMLConstants inlined for JDK 1.6 compatibility
    c226ae
    +    setAttributeQuietly(factory, "http://javax.xml.XMLConstants/property/accessExternalDTD", "");
    c226ae
    +    setAttributeQuietly(factory, "http://javax.xml.XMLConstants/property/accessExternalSchema", "");
    c226ae
    +    setAttributeQuietly(factory, "http://javax.xml.XMLConstants/property/accessExternalStylesheet", "");
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  private static void setPropertyQuietly(Object factory, String name, Object value) {
    c226ae
    +    try {
    c226ae
    +      if (factory instanceof XMLReader) {
    c226ae
    +        ((XMLReader) factory).setProperty(name, value);
    c226ae
    +      } else if (factory instanceof XMLInputFactory) {
    c226ae
    +        ((XMLInputFactory) factory).setProperty(name, value);
    c226ae
    +      } else {
    c226ae
    +        throw new Error("Invalid factory class: " + factory.getClass());
    c226ae
    +      }
    c226ae
    +    } catch (Exception ignore) {
    c226ae
    +    }
    c226ae
    +  }
    c226ae
    +}
    c226ae
    \ No newline at end of file
    c226ae
    diff --git a/pgjdbc/src/main/java/org/postgresql/xml/EmptyStringEntityResolver.java b/pgjdbc/src/main/java/org/postgresql/xml/EmptyStringEntityResolver.java
    c226ae
    new file mode 100644
    c226ae
    index 0000000..39227e0
    c226ae
    --- /dev/null
    c226ae
    +++ b/pgjdbc/src/main/java/org/postgresql/xml/EmptyStringEntityResolver.java
    c226ae
    @@ -0,0 +1,23 @@
    c226ae
    +/*
    c226ae
    + * Copyright (c) 2020, PostgreSQL Global Development Group
    c226ae
    + * See the LICENSE file in the project root for more information.
    c226ae
    + */
    c226ae
    +
    c226ae
    +package org.postgresql.xml;
    c226ae
    +
    c226ae
    +import org.xml.sax.EntityResolver;
    c226ae
    +import org.xml.sax.InputSource;
    c226ae
    +import org.xml.sax.SAXException;
    c226ae
    +
    c226ae
    +import java.io.IOException;
    c226ae
    +import java.io.StringReader;
    c226ae
    +
    c226ae
    +public class EmptyStringEntityResolver implements EntityResolver {
    c226ae
    +  public static final EmptyStringEntityResolver INSTANCE = new EmptyStringEntityResolver();
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public InputSource resolveEntity(String publicId, String systemId)
    c226ae
    +      throws SAXException, IOException {
    c226ae
    +    return new InputSource(new StringReader(""));
    c226ae
    +  }
    c226ae
    +}
    c226ae
    \ No newline at end of file
    c226ae
    diff --git a/pgjdbc/src/main/java/org/postgresql/xml/LegacyInsecurePGXmlFactoryFactory.java b/pgjdbc/src/main/java/org/postgresql/xml/LegacyInsecurePGXmlFactoryFactory.java
    c226ae
    new file mode 100644
    c226ae
    index 0000000..ed7a66b
    c226ae
    --- /dev/null
    c226ae
    +++ b/pgjdbc/src/main/java/org/postgresql/xml/LegacyInsecurePGXmlFactoryFactory.java
    c226ae
    @@ -0,0 +1,57 @@
    c226ae
    +/*
    c226ae
    + * Copyright (c) 2020, PostgreSQL Global Development Group
    c226ae
    + * See the LICENSE file in the project root for more information.
    c226ae
    + */
    c226ae
    +
    c226ae
    +package org.postgresql.xml;
    c226ae
    +
    c226ae
    +import org.xml.sax.SAXException;
    c226ae
    +import org.xml.sax.XMLReader;
    c226ae
    +import org.xml.sax.helpers.XMLReaderFactory;
    c226ae
    +
    c226ae
    +import javax.xml.parsers.DocumentBuilder;
    c226ae
    +import javax.xml.parsers.DocumentBuilderFactory;
    c226ae
    +import javax.xml.parsers.ParserConfigurationException;
    c226ae
    +import javax.xml.stream.XMLInputFactory;
    c226ae
    +import javax.xml.stream.XMLOutputFactory;
    c226ae
    +import javax.xml.transform.TransformerFactory;
    c226ae
    +import javax.xml.transform.sax.SAXTransformerFactory;
    c226ae
    +
    c226ae
    +public class LegacyInsecurePGXmlFactoryFactory implements PGXmlFactoryFactory {
    c226ae
    +  public static final LegacyInsecurePGXmlFactoryFactory INSTANCE = new LegacyInsecurePGXmlFactoryFactory();
    c226ae
    +
    c226ae
    +  private LegacyInsecurePGXmlFactoryFactory() {
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
    c226ae
    +    DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    c226ae
    +    builder.setErrorHandler(NullErrorHandler.INSTANCE);
    c226ae
    +    return builder;
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public TransformerFactory newTransformerFactory() {
    c226ae
    +    return TransformerFactory.newInstance();
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public SAXTransformerFactory newSAXTransformerFactory() {
    c226ae
    +    return (SAXTransformerFactory) SAXTransformerFactory.newInstance();
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public XMLInputFactory newXMLInputFactory() {
    c226ae
    +    return XMLInputFactory.newInstance();
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public XMLOutputFactory newXMLOutputFactory() {
    c226ae
    +    return XMLOutputFactory.newInstance();
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  public XMLReader createXMLReader() throws SAXException {
    c226ae
    +    return XMLReaderFactory.createXMLReader();
    c226ae
    +  }
    c226ae
    +}
    c226ae
    \ No newline at end of file
    c226ae
    diff --git a/pgjdbc/src/main/java/org/postgresql/xml/NullErrorHandler.java b/pgjdbc/src/main/java/org/postgresql/xml/NullErrorHandler.java
    c226ae
    new file mode 100644
    c226ae
    index 0000000..ad486c7
    c226ae
    --- /dev/null
    c226ae
    +++ b/pgjdbc/src/main/java/org/postgresql/xml/NullErrorHandler.java
    c226ae
    @@ -0,0 +1,25 @@
    c226ae
    +/*
    c226ae
    + * Copyright (c) 2020, PostgreSQL Global Development Group
    c226ae
    + * See the LICENSE file in the project root for more information.
    c226ae
    + */
    c226ae
    +
    c226ae
    +package org.postgresql.xml;
    c226ae
    +
    c226ae
    +import org.xml.sax.ErrorHandler;
    c226ae
    +import org.xml.sax.SAXParseException;
    c226ae
    +
    c226ae
    +/**
    c226ae
    + * Error handler that silently suppresses all errors.
    c226ae
    + */
    c226ae
    +public class NullErrorHandler implements ErrorHandler {
    c226ae
    +  public static final NullErrorHandler INSTANCE = new NullErrorHandler();
    c226ae
    +
    c226ae
    +  public void error(SAXParseException e) {
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  public void fatalError(SAXParseException e) {
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  public void warning(SAXParseException e) {
    c226ae
    +  }
    c226ae
    +}
    c226ae
    \ No newline at end of file
    c226ae
    diff --git a/pgjdbc/src/main/java/org/postgresql/xml/PGXmlFactoryFactory.java b/pgjdbc/src/main/java/org/postgresql/xml/PGXmlFactoryFactory.java
    c226ae
    new file mode 100644
    c226ae
    index 0000000..4bb98e4
    c226ae
    --- /dev/null
    c226ae
    +++ b/pgjdbc/src/main/java/org/postgresql/xml/PGXmlFactoryFactory.java
    c226ae
    @@ -0,0 +1,30 @@
    c226ae
    +/*
    c226ae
    + * Copyright (c) 2020, PostgreSQL Global Development Group
    c226ae
    + * See the LICENSE file in the project root for more information.
    c226ae
    + */
    c226ae
    +
    c226ae
    +package org.postgresql.xml;
    c226ae
    +
    c226ae
    +import org.xml.sax.SAXException;
    c226ae
    +import org.xml.sax.XMLReader;
    c226ae
    +
    c226ae
    +import javax.xml.parsers.DocumentBuilder;
    c226ae
    +import javax.xml.parsers.ParserConfigurationException;
    c226ae
    +import javax.xml.stream.XMLInputFactory;
    c226ae
    +import javax.xml.stream.XMLOutputFactory;
    c226ae
    +import javax.xml.transform.TransformerFactory;
    c226ae
    +import javax.xml.transform.sax.SAXTransformerFactory;
    c226ae
    +
    c226ae
    +public interface PGXmlFactoryFactory {
    c226ae
    +  DocumentBuilder newDocumentBuilder() throws ParserConfigurationException;
    c226ae
    +
    c226ae
    +  TransformerFactory newTransformerFactory();
    c226ae
    +
    c226ae
    +  SAXTransformerFactory newSAXTransformerFactory();
    c226ae
    +
    c226ae
    +  XMLInputFactory newXMLInputFactory();
    c226ae
    +
    c226ae
    +  XMLOutputFactory newXMLOutputFactory();
    c226ae
    +
    c226ae
    +  XMLReader createXMLReader() throws SAXException;
    c226ae
    +}
    c226ae
    \ No newline at end of file
    c226ae
    diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java
    c226ae
    new file mode 100644
    c226ae
    index 0000000..49e389c
    c226ae
    --- /dev/null
    c226ae
    +++ b/pgjdbc/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java
    c226ae
    @@ -0,0 +1,124 @@
    c226ae
    +/*
    c226ae
    + * Copyright (c) 2019, PostgreSQL Global Development Group
    c226ae
    + * See the LICENSE file in the project root for more information.
    c226ae
    + */
    c226ae
    +
    c226ae
    +package org.postgresql.jdbc;
    c226ae
    +
    c226ae
    +import static org.junit.Assert.assertEquals;
    c226ae
    +import static org.junit.Assert.assertNotNull;
    c226ae
    +import static org.junit.Assert.assertTrue;
    c226ae
    +import static org.junit.Assert.fail;
    c226ae
    +
    c226ae
    +import org.postgresql.PGProperty;
    c226ae
    +import org.postgresql.core.BaseConnection;
    c226ae
    +import org.postgresql.test.TestUtil;
    c226ae
    +import org.postgresql.test.jdbc2.BaseTest4;
    c226ae
    +
    c226ae
    +import org.junit.Before;
    c226ae
    +import org.junit.Test;
    c226ae
    +
    c226ae
    +import java.io.StringWriter;
    c226ae
    +import java.io.Writer;
    c226ae
    +import java.sql.Connection;
    c226ae
    +import java.sql.PreparedStatement;
    c226ae
    +import java.sql.ResultSet;
    c226ae
    +import java.sql.SQLException;
    c226ae
    +import java.sql.SQLXML;
    c226ae
    +import java.sql.Statement;
    c226ae
    +import java.util.Properties;
    c226ae
    +
    c226ae
    +import javax.xml.stream.XMLStreamException;
    c226ae
    +import javax.xml.stream.XMLStreamReader;
    c226ae
    +import javax.xml.transform.Source;
    c226ae
    +import javax.xml.transform.Transformer;
    c226ae
    +import javax.xml.transform.TransformerException;
    c226ae
    +import javax.xml.transform.TransformerFactory;
    c226ae
    +import javax.xml.transform.dom.DOMSource;
    c226ae
    +import javax.xml.transform.sax.SAXSource;
    c226ae
    +import javax.xml.transform.stax.StAXSource;
    c226ae
    +import javax.xml.transform.stream.StreamResult;
    c226ae
    +
    c226ae
    +public class PgSQLXMLTest extends BaseTest4 {
    c226ae
    +
    c226ae
    +  @Override
    c226ae
    +  @Before
    c226ae
    +  public void setUp() throws Exception {
    c226ae
    +    super.setUp();
    c226ae
    +    TestUtil.createTempTable(con, "xmltab", "x xml");
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Test
    c226ae
    +  public void setCharacterStream() throws Exception {
    c226ae
    +    String exmplar = "<x>value</x>";
    c226ae
    +    SQLXML pgSQLXML = con.createSQLXML();
    c226ae
    +    Writer writer = pgSQLXML.setCharacterStream();
    c226ae
    +    writer.write(exmplar);
    c226ae
    +    PreparedStatement preparedStatement = con.prepareStatement("insert into xmltab values (?)");
    c226ae
    +    preparedStatement.setSQLXML(1, pgSQLXML);
    c226ae
    +    preparedStatement.execute();
    c226ae
    +
    c226ae
    +    Statement statement = con.createStatement();
    c226ae
    +    ResultSet rs = statement.executeQuery("select * from xmltab");
    c226ae
    +    assertTrue(rs.next());
    c226ae
    +    SQLXML result = rs.getSQLXML(1);
    c226ae
    +    assertNotNull(result);
    c226ae
    +    assertEquals(exmplar, result.getString());
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  private static final String LICENSE_URL =
    c226ae
    +      PgSQLXMLTest.class.getClassLoader().getResource("META-INF/LICENSE").toString();
    c226ae
    +  private static final String XXE_EXAMPLE =
    c226ae
    +      "\n"
    c226ae
    +      + "]>"
    c226ae
    +      + "<foo>&xx;;</foo>";
    c226ae
    +
    c226ae
    +  @Test
    c226ae
    +  public void testLegacyXxe() throws Exception {
    c226ae
    +    Properties props = new Properties();
    c226ae
    +    props.setProperty(PGProperty.XML_FACTORY_FACTORY.getName(), "LEGACY_INSECURE");
    c226ae
    +    try (Connection conn = TestUtil.openDB(props)) {
    c226ae
    +      BaseConnection baseConn = conn.unwrap(BaseConnection.class);
    c226ae
    +      PgSQLXML xml = new PgSQLXML(baseConn, XXE_EXAMPLE);
    c226ae
    +      xml.getSource(null);
    c226ae
    +    }
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  private static String sourceToString(Source source) throws TransformerException {
    c226ae
    +    StringWriter sw = new StringWriter();
    c226ae
    +    Transformer transformer = TransformerFactory.newInstance().newTransformer();
    c226ae
    +    transformer.transform(source, new StreamResult(sw));
    c226ae
    +    return sw.toString();
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Test(expected = SQLException.class)
    c226ae
    +  public void testGetSourceXxeNull() throws Exception {
    c226ae
    +    PgSQLXML xml = new PgSQLXML(null, XXE_EXAMPLE);
    c226ae
    +      xml.getSource(null);
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Test(expected = SQLException.class)
    c226ae
    +  public void testGetSourceXxeDOMSource() throws Exception {
    c226ae
    +    PgSQLXML xml = new PgSQLXML(null, XXE_EXAMPLE);
    c226ae
    +      xml.getSource(DOMSource.class);
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Test(expected = TransformerException.class)
    c226ae
    +  public void testGetSourceXxeSAXSource() throws Exception {
    c226ae
    +    PgSQLXML xml = new PgSQLXML(null, XXE_EXAMPLE);
    c226ae
    +    SAXSource source = xml.getSource(SAXSource.class);
    c226ae
    +    sourceToString(source);
    c226ae
    +    
    c226ae
    +  }
    c226ae
    +
    c226ae
    +  @Test(expected = XMLStreamException.class)
    c226ae
    +  public void testGetSourceXxeStAXSource() throws Exception {
    c226ae
    +    PgSQLXML xml = new PgSQLXML(null, XXE_EXAMPLE);
    c226ae
    +    StAXSource source = xml.getSource(StAXSource.class);
    c226ae
    +    XMLStreamReader reader = source.getXMLStreamReader();
    c226ae
    +    // STAX will not throw XXE error until we actually read the element
    c226ae
    +    while (reader.hasNext()) {
    c226ae
    +      reader.next();
    c226ae
    +    }
    c226ae
    +  }
    c226ae
    +}
    c226ae
    diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
    c226ae
    index 6314d21..814288c 100644
    c226ae
    --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
    c226ae
    +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
    c226ae
    @@ -13,6 +13,7 @@ import org.postgresql.core.ParserTest;
    c226ae
     import org.postgresql.core.ReturningParserTest;
    c226ae
     import org.postgresql.core.v3.V3ParameterListTests;
    c226ae
     import org.postgresql.jdbc.DeepBatchedInsertStatementTest;
    c226ae
    +import org.postgresql.jdbc.PgSQLXMLTest;
    c226ae
     import org.postgresql.jdbc.PrimitiveArraySupportTest;
    c226ae
     import org.postgresql.test.core.JavaVersionTest;
    c226ae
     import org.postgresql.test.core.NativeQueryBindLengthTest;
    c226ae
    @@ -76,6 +77,7 @@ import org.junit.runners.Suite;
    c226ae
             TimestampTest.class,
    c226ae
             TimezoneTest.class,
    c226ae
             PGTimeTest.class,
    c226ae
    +        PgSQLXMLTest.class,
    c226ae
             PGTimestampTest.class,
    c226ae
             TimezoneCachingTest.class,
    c226ae
             ParserTest.class,
    c226ae
    -- 
    c226ae
    2.24.1
    c226ae