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