Skip to content

Commit e6fddca

Browse files
committed
Disable external entities by default to prevent XXE injection attacks, re #6
XML Builder classes now explicitly enable or disable 'external-general-entities' and 'external-parameter-entities' features of the DocumentBuilderFactory when #create or #parse methods are used. To prevent XML External Entity (XXE) injection attacks, these features are disabled by default. They can only be enabled by passing a true boolean value to new versions of the #create and #parse methods that accept a flag for this feature.
1 parent 5d5caa5 commit e6fddca

File tree

6 files changed

+376
-15
lines changed

6 files changed

+376
-15
lines changed

Diff for: CHANGES.md

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
Release Notes for java-xmlbuilder
22
=================================
33

4+
Version 1.2 - Pending
5+
---------------------
6+
7+
Fixes:
8+
9+
* Prevent XML External Entity (XXE) injection attacks by disabling parsing of
10+
general and parameter external entities by default (#6). External entities
11+
are now only parsed if this feature is explicitly enabled by passing a boolean
12+
flag value to the #create and #parse methods.
13+
WARNING: This will break code that expects external entities to be parsed.
14+
415
Version 1.1 - 22 July 2014
516
--------------------------
617

Diff for: src/main/java/com/jamesmurty/utils/BaseXMLBuilder.java

+85-2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ public abstract class BaseXMLBuilder {
7676

7777
private static boolean isNamespaceAware = true;
7878

79+
/**
80+
* If true, the builder will raise an {@link XMLBuilderRuntimeException}
81+
* if external general and parameter entities cannot be explicitly enabled
82+
* or disabled.
83+
*/
84+
public static boolean failIfExternalEntityParsingCannotBeConfigured = true;
85+
7986
/**
8087
* Construct a new builder object that wraps the given XML document.
8188
* This constructor is for internal use only.
@@ -112,6 +119,78 @@ protected BaseXMLBuilder(Node myNode, Node parentNode) {
112119
}
113120
}
114121

122+
/**
123+
* Explicitly enable or disable the 'external-general-entities' and
124+
* 'external-parameter-entities' features of the underlying
125+
* DocumentBuilderFactory.
126+
*
127+
* TODO This is a naive approach that simply tries to apply all known
128+
* feature name/URL values in turn until one succeeds, or none do.
129+
*
130+
* @param factory
131+
* factory which will have external general and parameter entities enabled
132+
* or disabled.
133+
* @param enableExternalEntities
134+
* if true external entities will be explicitly enabled, otherwise they
135+
* will be explicitly disabled.
136+
*/
137+
protected static void enableOrDisableExternalEntityParsing(
138+
DocumentBuilderFactory factory, boolean enableExternalEntities)
139+
{
140+
// Feature list drawn from:
141+
// https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
142+
143+
/* Enable or disable external general entities */
144+
String[] externalGeneralEntitiesFeatures = {
145+
// General
146+
"http://xml.org/sax/features/external-general-entities",
147+
// Xerces 1
148+
"http://xerces.apache.org/xerces-j/features.html#external-general-entities",
149+
// Xerces 2
150+
"http://xerces.apache.org/xerces2-j/features.html#external-general-entities",
151+
};
152+
boolean success = false;
153+
for (String feature: externalGeneralEntitiesFeatures) {
154+
try {
155+
factory.setFeature(feature, enableExternalEntities);
156+
success = true;
157+
break;
158+
} catch (ParserConfigurationException e) {
159+
}
160+
}
161+
if (!success && failIfExternalEntityParsingCannotBeConfigured) {
162+
throw new XMLBuilderRuntimeException(
163+
new ParserConfigurationException(
164+
"Failed to set 'external-general-entities' feature to "
165+
+ enableExternalEntities));
166+
}
167+
168+
/* Enable or disable external parameter entities */
169+
String[] externalParameterEntitiesFeatures = {
170+
// General
171+
"http://xml.org/sax/features/external-parameter-entities",
172+
// Xerces 1
173+
"http://xerces.apache.org/xerces-j/features.html#external-parameter-entities",
174+
// Xerces 2
175+
"http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities",
176+
};
177+
success = false;
178+
for (String feature: externalParameterEntitiesFeatures) {
179+
try {
180+
factory.setFeature(feature, enableExternalEntities);
181+
success = true;
182+
break;
183+
} catch (ParserConfigurationException e) {
184+
}
185+
}
186+
if (!success && failIfExternalEntityParsingCannotBeConfigured) {
187+
throw new XMLBuilderRuntimeException(
188+
new ParserConfigurationException(
189+
"Failed to set 'external-parameter-entities' feature to "
190+
+ enableExternalEntities));
191+
}
192+
}
193+
115194
/**
116195
* Construct an XML Document with a default namespace with the given
117196
* root element.
@@ -126,11 +205,13 @@ protected BaseXMLBuilder(Node myNode, Node parentNode) {
126205
* @throws FactoryConfigurationError
127206
* @throws ParserConfigurationException
128207
*/
129-
protected static Document createDocumentImpl(String name, String namespaceURI)
208+
protected static Document createDocumentImpl(
209+
String name, String namespaceURI, boolean enableExternalEntities)
130210
throws ParserConfigurationException, FactoryConfigurationError
131211
{
132212
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
133213
factory.setNamespaceAware(isNamespaceAware);
214+
enableOrDisableExternalEntityParsing(factory, enableExternalEntities);
134215
DocumentBuilder builder = factory.newDocumentBuilder();
135216
Document document = builder.newDocument();
136217
Element rootElement = null;
@@ -157,11 +238,13 @@ protected static Document createDocumentImpl(String name, String namespaceURI)
157238
* @throws IOException
158239
* @throws SAXException
159240
*/
160-
protected static Document parseDocumentImpl(InputSource inputSource)
241+
protected static Document parseDocumentImpl(
242+
InputSource inputSource, boolean enableExternalEntities)
161243
throws ParserConfigurationException, SAXException, IOException
162244
{
163245
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
164246
factory.setNamespaceAware(isNamespaceAware);
247+
enableOrDisableExternalEntityParsing(factory, enableExternalEntities);
165248
DocumentBuilder builder = factory.newDocumentBuilder();
166249
Document document = builder.parse(inputSource);
167250
return document;

Diff for: src/main/java/com/jamesmurty/utils/XMLBuilder.java

+129-4
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,53 @@ protected XMLBuilder(Node myNode, Node parentNode) {
8989
super(myNode, parentNode);
9090
}
9191

92+
/**
93+
* Construct a builder for new XML document with a default namespace.
94+
* The document will be created with the given root element, and the builder
95+
* returned by this method will serve as the starting-point for any further
96+
* document additions.
97+
*
98+
* @param name
99+
* the name of the document's root element.
100+
* @param namespaceURI
101+
* default namespace URI for document, ignored if null or empty.
102+
* @param enableExternalEntities
103+
* enable external entities; beware of XML External Entity (XXE) injection.
104+
* @return
105+
* a builder node that can be used to add more nodes to the XML document.
106+
*
107+
* @throws FactoryConfigurationError
108+
* @throws ParserConfigurationException
109+
*/
110+
public static XMLBuilder create(String name, String namespaceURI,
111+
boolean enableExternalEntities)
112+
throws ParserConfigurationException, FactoryConfigurationError
113+
{
114+
return new XMLBuilder(
115+
createDocumentImpl(name, namespaceURI, enableExternalEntities));
116+
}
117+
118+
/**
119+
* Construct a builder for new XML document. The document will be created
120+
* with the given root element, and the builder returned by this method
121+
* will serve as the starting-point for any further document additions.
122+
*
123+
* @param name
124+
* the name of the document's root element.
125+
* @param enableExternalEntities
126+
* enable external entities; beware of XML External Entity (XXE) injection.
127+
* @return
128+
* a builder node that can be used to add more nodes to the XML document.
129+
*
130+
* @throws FactoryConfigurationError
131+
* @throws ParserConfigurationException
132+
*/
133+
public static XMLBuilder create(String name, boolean enableExternalEntities)
134+
throws ParserConfigurationException, FactoryConfigurationError
135+
{
136+
return create(name, null, enableExternalEntities);
137+
}
138+
92139
/**
93140
* Construct a builder for new XML document with a default namespace.
94141
* The document will be created with the given root element, and the builder
@@ -108,7 +155,7 @@ protected XMLBuilder(Node myNode, Node parentNode) {
108155
public static XMLBuilder create(String name, String namespaceURI)
109156
throws ParserConfigurationException, FactoryConfigurationError
110157
{
111-
return new XMLBuilder(createDocumentImpl(name, namespaceURI));
158+
return create(name, namespaceURI, false);
112159
}
113160

114161
/**
@@ -130,6 +177,84 @@ public static XMLBuilder create(String name)
130177
return create(name, null);
131178
}
132179

180+
/**
181+
* Construct a builder from an existing XML document. The provided XML
182+
* document will be parsed and an XMLBuilder object referencing the
183+
* document's root element will be returned.
184+
*
185+
* @param inputSource
186+
* an XML document input source that will be parsed into a DOM.
187+
* @param enableExternalEntities
188+
* enable external entities; beware of XML External Entity (XXE) injection.
189+
* @return
190+
* a builder node that can be used to add more nodes to the XML document.
191+
* @throws ParserConfigurationException
192+
*
193+
* @throws FactoryConfigurationError
194+
* @throws ParserConfigurationException
195+
* @throws IOException
196+
* @throws SAXException
197+
*/
198+
public static XMLBuilder parse(
199+
InputSource inputSource, boolean enableExternalEntities)
200+
throws ParserConfigurationException, SAXException, IOException
201+
{
202+
return new XMLBuilder(
203+
parseDocumentImpl(inputSource, enableExternalEntities));
204+
}
205+
206+
/**
207+
* Construct a builder from an existing XML document string.
208+
* The provided XML document will be parsed and an XMLBuilder
209+
* object referencing the document's root element will be returned.
210+
*
211+
* @param xmlString
212+
* an XML document string that will be parsed into a DOM.
213+
* @param enableExternalEntities
214+
* enable external entities; beware of XML External Entity (XXE) injection.
215+
* @return
216+
* a builder node that can be used to add more nodes to the XML document.
217+
*
218+
* @throws ParserConfigurationException
219+
* @throws FactoryConfigurationError
220+
* @throws ParserConfigurationException
221+
* @throws IOException
222+
* @throws SAXException
223+
*/
224+
public static XMLBuilder parse(
225+
String xmlString, boolean enableExternalEntities)
226+
throws ParserConfigurationException, SAXException, IOException
227+
{
228+
return XMLBuilder.parse(
229+
new InputSource(new StringReader(xmlString)),
230+
enableExternalEntities);
231+
}
232+
233+
/**
234+
* Construct a builder from an existing XML document file.
235+
* The provided XML document will be parsed and an XMLBuilder
236+
* object referencing the document's root element will be returned.
237+
*
238+
* @param xmlFile
239+
* an XML document file that will be parsed into a DOM.
240+
* @param enableExternalEntities
241+
* enable external entities; beware of XML External Entity (XXE) injection.
242+
* @return
243+
* a builder node that can be used to add more nodes to the XML document.
244+
*
245+
* @throws ParserConfigurationException
246+
* @throws FactoryConfigurationError
247+
* @throws ParserConfigurationException
248+
* @throws IOException
249+
* @throws SAXException
250+
*/
251+
public static XMLBuilder parse(File xmlFile, boolean enableExternalEntities)
252+
throws ParserConfigurationException, SAXException, IOException
253+
{
254+
return XMLBuilder.parse(
255+
new InputSource(new FileReader(xmlFile)), enableExternalEntities);
256+
}
257+
133258
/**
134259
* Construct a builder from an existing XML document. The provided XML
135260
* document will be parsed and an XMLBuilder object referencing the
@@ -149,7 +274,7 @@ public static XMLBuilder create(String name)
149274
public static XMLBuilder parse(InputSource inputSource)
150275
throws ParserConfigurationException, SAXException, IOException
151276
{
152-
return new XMLBuilder(parseDocumentImpl(inputSource));
277+
return XMLBuilder.parse(inputSource, false);
153278
}
154279

155280
/**
@@ -171,7 +296,7 @@ public static XMLBuilder parse(InputSource inputSource)
171296
public static XMLBuilder parse(String xmlString)
172297
throws ParserConfigurationException, SAXException, IOException
173298
{
174-
return XMLBuilder.parse(new InputSource(new StringReader(xmlString)));
299+
return XMLBuilder.parse(xmlString, false);
175300
}
176301

177302
/**
@@ -193,7 +318,7 @@ public static XMLBuilder parse(String xmlString)
193318
public static XMLBuilder parse(File xmlFile)
194319
throws ParserConfigurationException, SAXException, IOException
195320
{
196-
return XMLBuilder.parse(new InputSource(new FileReader(xmlFile)));
321+
return XMLBuilder.parse(xmlFile, false);
197322
}
198323

199324
@Override

0 commit comments

Comments
 (0)