Skip to content

Commit 14b62ac

Browse files
authored
Merge pull request from GHSA-37xm-4h3m-5w3v
* refactor: Clean up whitespace in existing PgSQLXMLTest * fix: Fix XXE vulnerability in PgSQLXML by disabling external access and doctypes Fixes XXE vulnerability by defaulting to disabling external access and doc types. The legacy insecure behavior can be restored via the new connection property xmlFactoryFactory with a value of LEGACY_INSECURE. Alternatively, a custom class name can be specified that implements org.postgresql.xml.PGXmlFactoryFactory and takes a no argument constructor. * fix: Add missing getter and setter for XML_FACTORY_FACTORY to BasicDataSource
1 parent 97e2e8f commit 14b62ac

11 files changed

+453
-29
lines changed

Diff for: pgjdbc/src/main/java/org/postgresql/PGProperty.java

+11
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,17 @@ public enum PGProperty {
661661
"false",
662662
"Use SPNEGO in SSPI authentication requests"),
663663

664+
/**
665+
* Factory class to instantiate factories for XML processing.
666+
* The default factory disables external entity processing.
667+
* Legacy behavior with external entity processing can be enabled by specifying a value of LEGACY_INSECURE.
668+
* Or specify a custom class that implements {@code org.postgresql.xml.PGXmlFactoryFactory}.
669+
*/
670+
XML_FACTORY_FACTORY(
671+
"xmlFactoryFactory",
672+
"",
673+
"Factory class to instantiate factories for XML processing"),
674+
664675
;
665676

666677
private final String name;

Diff for: pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java

+9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.postgresql.jdbc.FieldMetadata;
1111
import org.postgresql.jdbc.TimestampUtils;
1212
import org.postgresql.util.LruCache;
13+
import org.postgresql.xml.PGXmlFactoryFactory;
1314

1415
import java.sql.Connection;
1516
import java.sql.ResultSet;
@@ -212,4 +213,12 @@ CachedQuery createQuery(String sql, boolean escapeProcessing, boolean isParamete
212213
* @see PGProperty#READ_ONLY_MODE
213214
*/
214215
boolean hintReadOnly();
216+
217+
/**
218+
* Retrieve the factory to instantiate XML processing factories.
219+
*
220+
* @return The factory to use to instantiate XML processing factories
221+
* @throws SQLException if the class cannot be found or instantiated.
222+
*/
223+
PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException;
215224
}

Diff for: pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java

+8
Original file line numberDiff line numberDiff line change
@@ -1526,6 +1526,14 @@ public java.util.logging.Logger getParentLogger() {
15261526
return Logger.getLogger("org.postgresql");
15271527
}
15281528

1529+
public String getXmlFactoryFactory() {
1530+
return PGProperty.XML_FACTORY_FACTORY.get(properties);
1531+
}
1532+
1533+
public void setXmlFactoryFactory(String xmlFactoryFactory) {
1534+
PGProperty.XML_FACTORY_FACTORY.set(properties, xmlFactoryFactory);
1535+
}
1536+
15291537
/*
15301538
* Alias methods below, these are to help with ease-of-use with other database tools / frameworks
15311539
* which expect normal java bean getters / setters to exist for the property names.

Diff for: pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java

+40
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
import org.postgresql.util.PGobject;
3838
import org.postgresql.util.PSQLException;
3939
import org.postgresql.util.PSQLState;
40+
import org.postgresql.xml.DefaultPGXmlFactoryFactory;
41+
import org.postgresql.xml.LegacyInsecurePGXmlFactoryFactory;
42+
import org.postgresql.xml.PGXmlFactoryFactory;
4043

4144
import java.io.IOException;
4245
import java.sql.Array;
@@ -156,6 +159,9 @@ private enum ReadOnlyBehavior {
156159

157160
private final LruCache<FieldMetadata.Key, FieldMetadata> fieldMetadataCache;
158161

162+
private final String xmlFactoryFactoryClass;
163+
private PGXmlFactoryFactory xmlFactoryFactory;
164+
159165
final CachedQuery borrowQuery(String sql) throws SQLException {
160166
return queryExecutor.borrowQuery(sql);
161167
}
@@ -311,6 +317,8 @@ public TimeZone get() {
311317
false);
312318

313319
replicationConnection = PGProperty.REPLICATION.get(info) != null;
320+
321+
xmlFactoryFactoryClass = PGProperty.XML_FACTORY_FACTORY.get(info);
314322
}
315323

316324
private static ReadOnlyBehavior getReadOnlyBehavior(String property) {
@@ -1823,4 +1831,36 @@ public final String getParameterStatus(String parameterName) {
18231831
return queryExecutor.getParameterStatus(parameterName);
18241832
}
18251833

1834+
@Override
1835+
public PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException {
1836+
if (xmlFactoryFactory == null) {
1837+
if (xmlFactoryFactoryClass == null || xmlFactoryFactoryClass.equals("")) {
1838+
xmlFactoryFactory = DefaultPGXmlFactoryFactory.INSTANCE;
1839+
} else if (xmlFactoryFactoryClass.equals("LEGACY_INSECURE")) {
1840+
xmlFactoryFactory = LegacyInsecurePGXmlFactoryFactory.INSTANCE;
1841+
} else {
1842+
Class<?> clazz;
1843+
try {
1844+
clazz = Class.forName(xmlFactoryFactoryClass);
1845+
} catch (ClassNotFoundException ex) {
1846+
throw new PSQLException(
1847+
GT.tr("Could not instantiate xmlFactoryFactory: {0}", xmlFactoryFactoryClass),
1848+
PSQLState.INVALID_PARAMETER_VALUE, ex);
1849+
}
1850+
if (!clazz.isAssignableFrom(PGXmlFactoryFactory.class)) {
1851+
throw new PSQLException(
1852+
GT.tr("Connection property xmlFactoryFactory must implement PGXmlFactoryFactory: {0}", xmlFactoryFactoryClass),
1853+
PSQLState.INVALID_PARAMETER_VALUE);
1854+
}
1855+
try {
1856+
xmlFactoryFactory = (PGXmlFactoryFactory) clazz.newInstance();
1857+
} catch (Exception ex) {
1858+
throw new PSQLException(
1859+
GT.tr("Could not instantiate xmlFactoryFactory: {0}", xmlFactoryFactoryClass),
1860+
PSQLState.INVALID_PARAMETER_VALUE, ex);
1861+
}
1862+
}
1863+
}
1864+
return xmlFactoryFactory;
1865+
}
18261866
}

Diff for: pgjdbc/src/main/java/org/postgresql/jdbc/PgSQLXML.java

+17-26
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
import org.postgresql.util.GT;
1010
import org.postgresql.util.PSQLException;
1111
import org.postgresql.util.PSQLState;
12+
import org.postgresql.xml.DefaultPGXmlFactoryFactory;
13+
import org.postgresql.xml.PGXmlFactoryFactory;
1214

13-
import org.xml.sax.ErrorHandler;
1415
import org.xml.sax.InputSource;
15-
import org.xml.sax.SAXParseException;
16+
import org.xml.sax.XMLReader;
1617

1718
import java.io.ByteArrayInputStream;
1819
import java.io.ByteArrayOutputStream;
@@ -27,7 +28,6 @@
2728
import java.sql.SQLXML;
2829

2930
import javax.xml.parsers.DocumentBuilder;
30-
import javax.xml.parsers.DocumentBuilderFactory;
3131
import javax.xml.stream.XMLInputFactory;
3232
import javax.xml.stream.XMLOutputFactory;
3333
import javax.xml.stream.XMLStreamException;
@@ -77,6 +77,13 @@ private PgSQLXML(BaseConnection conn, String data, boolean initialized) {
7777
this.freed = false;
7878
}
7979

80+
private PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException {
81+
if (conn != null) {
82+
return conn.getXmlFactoryFactory();
83+
}
84+
return DefaultPGXmlFactoryFactory.INSTANCE;
85+
}
86+
8087
@Override
8188
public synchronized void free() {
8289
freed = true;
@@ -132,18 +139,17 @@ public synchronized <T extends Source> T getSource(Class<T> sourceClass) throws
132139

133140
try {
134141
if (sourceClass == null || DOMSource.class.equals(sourceClass)) {
135-
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
136-
DocumentBuilder builder = factory.newDocumentBuilder();
137-
builder.setErrorHandler(new NonPrintingErrorHandler());
142+
DocumentBuilder builder = getXmlFactoryFactory().newDocumentBuilder();
138143
InputSource input = new InputSource(new StringReader(data));
139144
return (T) new DOMSource(builder.parse(input));
140145
} else if (SAXSource.class.equals(sourceClass)) {
146+
XMLReader reader = getXmlFactoryFactory().createXMLReader();
141147
InputSource is = new InputSource(new StringReader(data));
142-
return (T) new SAXSource(is);
148+
return (T) new SAXSource(reader, is);
143149
} else if (StreamSource.class.equals(sourceClass)) {
144150
return (T) new StreamSource(new StringReader(data));
145151
} else if (StAXSource.class.equals(sourceClass)) {
146-
XMLInputFactory xif = XMLInputFactory.newInstance();
152+
XMLInputFactory xif = getXmlFactoryFactory().newXMLInputFactory();
147153
XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(data));
148154
return (T) new StAXSource(xsr);
149155
}
@@ -191,8 +197,7 @@ public synchronized <T extends Result> T setResult(Class<T> resultClass) throws
191197
return (T) domResult;
192198
} else if (SAXResult.class.equals(resultClass)) {
193199
try {
194-
SAXTransformerFactory transformerFactory =
195-
(SAXTransformerFactory) SAXTransformerFactory.newInstance();
200+
SAXTransformerFactory transformerFactory = getXmlFactoryFactory().newSAXTransformerFactory();
196201
TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
197202
stringWriter = new StringWriter();
198203
transformerHandler.setResult(new StreamResult(stringWriter));
@@ -209,7 +214,7 @@ public synchronized <T extends Result> T setResult(Class<T> resultClass) throws
209214
} else if (StAXResult.class.equals(resultClass)) {
210215
stringWriter = new StringWriter();
211216
try {
212-
XMLOutputFactory xof = XMLOutputFactory.newInstance();
217+
XMLOutputFactory xof = getXmlFactoryFactory().newXMLOutputFactory();
213218
XMLStreamWriter xsw = xof.createXMLStreamWriter(stringWriter);
214219
active = true;
215220
return (T) new StAXResult(xsw);
@@ -272,7 +277,7 @@ private void ensureInitialized() throws SQLException {
272277
// and use the identify transform to get it into a
273278
// friendlier result format.
274279
try {
275-
TransformerFactory factory = TransformerFactory.newInstance();
280+
TransformerFactory factory = getXmlFactoryFactory().newTransformerFactory();
276281
Transformer transformer = factory.newTransformer();
277282
DOMSource domSource = new DOMSource(domResult.getNode());
278283
StringWriter stringWriter = new StringWriter();
@@ -298,18 +303,4 @@ private void initialize() throws SQLException {
298303
}
299304
initialized = true;
300305
}
301-
302-
// Don't clutter System.err with errors the user can't silence.
303-
// If something bad really happens an exception will be thrown.
304-
static class NonPrintingErrorHandler implements ErrorHandler {
305-
public void error(SAXParseException e) {
306-
}
307-
308-
public void fatalError(SAXParseException e) {
309-
}
310-
311-
public void warning(SAXParseException e) {
312-
}
313-
}
314-
315306
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright (c) 2020, PostgreSQL Global Development Group
3+
* See the LICENSE file in the project root for more information.
4+
*/
5+
6+
package org.postgresql.xml;
7+
8+
import org.xml.sax.SAXException;
9+
import org.xml.sax.XMLReader;
10+
import org.xml.sax.helpers.XMLReaderFactory;
11+
12+
import javax.xml.XMLConstants;
13+
import javax.xml.parsers.DocumentBuilder;
14+
import javax.xml.parsers.DocumentBuilderFactory;
15+
import javax.xml.parsers.ParserConfigurationException;
16+
import javax.xml.stream.XMLInputFactory;
17+
import javax.xml.stream.XMLOutputFactory;
18+
import javax.xml.transform.TransformerFactory;
19+
import javax.xml.transform.sax.SAXTransformerFactory;
20+
21+
/**
22+
* Default implementation of PGXmlFactoryFactory that configures each factory per OWASP recommendations.
23+
*
24+
* @see <a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html">https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html</a>
25+
*/
26+
public class DefaultPGXmlFactoryFactory implements PGXmlFactoryFactory {
27+
public static final DefaultPGXmlFactoryFactory INSTANCE = new DefaultPGXmlFactoryFactory();
28+
29+
private DefaultPGXmlFactoryFactory() {
30+
}
31+
32+
private DocumentBuilderFactory getDocumentBuilderFactory() {
33+
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
34+
setFactoryProperties(factory);
35+
factory.setXIncludeAware(false);
36+
factory.setExpandEntityReferences(false);
37+
return factory;
38+
}
39+
40+
@Override
41+
public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
42+
DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder();
43+
builder.setEntityResolver(EmptyStringEntityResolver.INSTANCE);
44+
builder.setErrorHandler(NullErrorHandler.INSTANCE);
45+
return builder;
46+
}
47+
48+
@Override
49+
public TransformerFactory newTransformerFactory() {
50+
TransformerFactory factory = TransformerFactory.newInstance();
51+
setFactoryProperties(factory);
52+
return factory;
53+
}
54+
55+
@Override
56+
public SAXTransformerFactory newSAXTransformerFactory() {
57+
SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
58+
setFactoryProperties(factory);
59+
return factory;
60+
}
61+
62+
@Override
63+
public XMLInputFactory newXMLInputFactory() {
64+
XMLInputFactory factory = XMLInputFactory.newInstance();
65+
setPropertyQuietly(factory, XMLInputFactory.SUPPORT_DTD, false);
66+
setPropertyQuietly(factory, XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
67+
return factory;
68+
}
69+
70+
@Override
71+
public XMLOutputFactory newXMLOutputFactory() {
72+
XMLOutputFactory factory = XMLOutputFactory.newInstance();
73+
return factory;
74+
}
75+
76+
@Override
77+
public XMLReader createXMLReader() throws SAXException {
78+
XMLReader factory = XMLReaderFactory.createXMLReader();
79+
setFeatureQuietly(factory, "http://apache.org/xml/features/disallow-doctype-decl", true);
80+
setFeatureQuietly(factory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
81+
setFeatureQuietly(factory, "http://xml.org/sax/features/external-general-entities", false);
82+
setFeatureQuietly(factory, "http://xml.org/sax/features/external-parameter-entities", false);
83+
factory.setErrorHandler(NullErrorHandler.INSTANCE);
84+
return factory;
85+
}
86+
87+
private static void setFeatureQuietly(Object factory, String name, boolean value) {
88+
try {
89+
if (factory instanceof DocumentBuilderFactory) {
90+
((DocumentBuilderFactory) factory).setFeature(name, value);
91+
} else if (factory instanceof TransformerFactory) {
92+
((TransformerFactory) factory).setFeature(name, value);
93+
} else if (factory instanceof XMLReader) {
94+
((XMLReader) factory).setFeature(name, value);
95+
} else {
96+
throw new Error("Invalid factory class: " + factory.getClass());
97+
}
98+
return;
99+
} catch (Exception ignore) {
100+
}
101+
}
102+
103+
private static void setAttributeQuietly(Object factory, String name, Object value) {
104+
try {
105+
if (factory instanceof DocumentBuilderFactory) {
106+
((DocumentBuilderFactory) factory).setAttribute(name, value);
107+
} else if (factory instanceof TransformerFactory) {
108+
((TransformerFactory) factory).setAttribute(name, value);
109+
} else {
110+
throw new Error("Invalid factory class: " + factory.getClass());
111+
}
112+
} catch (Exception ignore) {
113+
}
114+
}
115+
116+
private static void setFactoryProperties(Object factory) {
117+
setFeatureQuietly(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
118+
setFeatureQuietly(factory, "http://apache.org/xml/features/disallow-doctype-decl", true);
119+
setFeatureQuietly(factory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
120+
setFeatureQuietly(factory, "http://xml.org/sax/features/external-general-entities", false);
121+
setFeatureQuietly(factory, "http://xml.org/sax/features/external-parameter-entities", false);
122+
// Values from XMLConstants inlined for JDK 1.6 compatibility
123+
setAttributeQuietly(factory, "http://javax.xml.XMLConstants/property/accessExternalDTD", "");
124+
setAttributeQuietly(factory, "http://javax.xml.XMLConstants/property/accessExternalSchema", "");
125+
setAttributeQuietly(factory, "http://javax.xml.XMLConstants/property/accessExternalStylesheet", "");
126+
}
127+
128+
private static void setPropertyQuietly(Object factory, String name, Object value) {
129+
try {
130+
if (factory instanceof XMLReader) {
131+
((XMLReader) factory).setProperty(name, value);
132+
} else if (factory instanceof XMLInputFactory) {
133+
((XMLInputFactory) factory).setProperty(name, value);
134+
} else {
135+
throw new Error("Invalid factory class: " + factory.getClass());
136+
}
137+
} catch (Exception ignore) {
138+
}
139+
}
140+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright (c) 2020, PostgreSQL Global Development Group
3+
* See the LICENSE file in the project root for more information.
4+
*/
5+
6+
package org.postgresql.xml;
7+
8+
import org.xml.sax.EntityResolver;
9+
import org.xml.sax.InputSource;
10+
import org.xml.sax.SAXException;
11+
12+
import java.io.IOException;
13+
import java.io.StringReader;
14+
15+
public class EmptyStringEntityResolver implements EntityResolver {
16+
public static final EmptyStringEntityResolver INSTANCE = new EmptyStringEntityResolver();
17+
18+
@Override
19+
public InputSource resolveEntity(String publicId, String systemId)
20+
throws SAXException, IOException {
21+
return new InputSource(new StringReader(""));
22+
}
23+
}

0 commit comments

Comments
 (0)