Skip to content

Commit 9e2c7be

Browse files
aloprestomcgilman
authored andcommitted
- Added XmlUtils class. - Added unit test. - Added XXE test resource. - Refactored JAXB unmarshalling globally to prevent XXE attacks. - Refactored duplicated/legacy code. - Cleaned up commented code. - Switched from FileInputStream back to StreamSource in AuthorizerFactoryBean. - This closes #2134
1 parent 9a8e6b2 commit 9e2c7be

File tree

22 files changed

+396
-220
lines changed

22 files changed

+396
-220
lines changed

nifi-commons/nifi-security-utils/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,18 @@
6060
<artifactId>nifi-properties</artifactId>
6161
</dependency>
6262
</dependencies>
63+
<build>
64+
<plugins>
65+
<plugin>
66+
<groupId>org.apache.rat</groupId>
67+
<artifactId>apache-rat-plugin</artifactId>
68+
<configuration>
69+
<excludes combine.children="append">
70+
<exclude>src/test/resources/xxe_template.xml</exclude>
71+
</excludes>
72+
</configuration>
73+
</plugin>
74+
</plugins>
75+
</build>
6376
</project>
6477

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.nifi.security.xml;
18+
19+
import javax.xml.stream.XMLInputFactory;
20+
import javax.xml.stream.XMLStreamException;
21+
import javax.xml.stream.XMLStreamReader;
22+
import javax.xml.transform.stream.StreamSource;
23+
import java.io.InputStream;
24+
25+
public class XmlUtils {
26+
27+
public static XMLStreamReader createSafeReader(InputStream inputStream) throws XMLStreamException {
28+
if (inputStream == null) {
29+
throw new IllegalArgumentException("The provided input stream cannot be null");
30+
}
31+
return createSafeReader(new StreamSource(inputStream));
32+
}
33+
34+
public static XMLStreamReader createSafeReader(StreamSource source) throws XMLStreamException {
35+
if (source == null) {
36+
throw new IllegalArgumentException("The provided source cannot be null");
37+
}
38+
39+
XMLInputFactory xif = XMLInputFactory.newFactory();
40+
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
41+
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
42+
return xif.createXMLStreamReader(source);
43+
}
44+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.nifi.security.xml
18+
19+
import org.junit.After
20+
import org.junit.Before
21+
import org.junit.BeforeClass
22+
import org.junit.Test
23+
import org.junit.runner.RunWith
24+
import org.junit.runners.JUnit4
25+
import org.slf4j.Logger
26+
import org.slf4j.LoggerFactory
27+
28+
import javax.xml.bind.JAXBContext
29+
import javax.xml.bind.UnmarshalException
30+
import javax.xml.bind.Unmarshaller
31+
import javax.xml.bind.annotation.XmlAccessType
32+
import javax.xml.bind.annotation.XmlAccessorType
33+
import javax.xml.bind.annotation.XmlAttribute
34+
import javax.xml.bind.annotation.XmlRootElement
35+
import javax.xml.stream.XMLStreamReader
36+
37+
import static groovy.test.GroovyAssert.shouldFail
38+
39+
@RunWith(JUnit4.class)
40+
class XmlUtilsTest {
41+
private static final Logger logger = LoggerFactory.getLogger(XmlUtilsTest.class)
42+
43+
@BeforeClass
44+
static void setUpOnce() throws Exception {
45+
logger.metaClass.methodMissing = { String name, args ->
46+
logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
47+
}
48+
}
49+
50+
@Before
51+
void setUp() throws Exception {
52+
53+
}
54+
55+
@After
56+
void tearDown() throws Exception {
57+
58+
}
59+
60+
@Test
61+
void testShouldHandleXXEInUnmarshal() {
62+
// Arrange
63+
final String XXE_TEMPLATE_FILEPATH = "src/test/resources/xxe_template.xml"
64+
InputStream templateStream = new File(XXE_TEMPLATE_FILEPATH).newInputStream()
65+
66+
JAXBContext context = JAXBContext.newInstance(XmlObject.class)
67+
68+
// Act
69+
def msg = shouldFail(UnmarshalException) {
70+
Unmarshaller unmarshaller = context.createUnmarshaller()
71+
XMLStreamReader xsr = XmlUtils.createSafeReader(templateStream)
72+
def parsed = unmarshaller.unmarshal(xsr, XmlObject.class)
73+
logger.info("Unmarshalled ${parsed.toString()}")
74+
}
75+
76+
// Assert
77+
logger.expected(msg)
78+
assert msg =~ "XMLStreamException: ParseError "
79+
}
80+
}
81+
82+
@XmlAccessorType( XmlAccessType.NONE )
83+
@XmlRootElement(name = "object")
84+
class XmlObject {
85+
@XmlAttribute
86+
String name
87+
88+
@XmlAttribute
89+
String description
90+
91+
@XmlAttribute
92+
String groupId
93+
94+
@XmlAttribute
95+
String timestamp
96+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><!DOCTYPE object [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
2+
<object>
3+
<name>&xxe;</name>
4+
<description>Arbitrary XML that has an XXE attack present.</description>
5+
<groupId>3a204982-015e-1000-eaa2-19d352ec8394</groupId>
6+
<timestamp>09/05/2017 14:51:01 PDT</timestamp>
7+
</object>

nifi-commons/nifi-site-to-site-client/src/main/java/org/apache/nifi/remote/cluster/ClusterNodeInformation.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
import java.io.InputStream;
2020
import java.io.OutputStream;
2121
import java.util.Collection;
22-
2322
import javax.xml.bind.JAXBContext;
2423
import javax.xml.bind.JAXBException;
2524
import javax.xml.bind.Marshaller;
2625
import javax.xml.bind.Unmarshaller;
2726
import javax.xml.bind.annotation.XmlRootElement;
2827
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
28+
import javax.xml.stream.XMLStreamException;
29+
import javax.xml.stream.XMLStreamReader;
30+
import org.apache.nifi.security.xml.XmlUtils;
2931

3032
@XmlRootElement
3133
public class ClusterNodeInformation {
@@ -61,7 +63,12 @@ public void marshal(final OutputStream os) throws JAXBException {
6163
}
6264

6365
public static ClusterNodeInformation unmarshal(final InputStream is) throws JAXBException {
64-
final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
65-
return (ClusterNodeInformation) unmarshaller.unmarshal(is);
66+
try {
67+
final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
68+
final XMLStreamReader xsr = XmlUtils.createSafeReader(is);
69+
return (ClusterNodeInformation) unmarshaller.unmarshal(xsr);
70+
} catch (XMLStreamException e) {
71+
throw new JAXBException("Error unmarshalling the cluster node information", e);
72+
}
6673
}
6774
}

nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@
8787
<groupId>org.apache.nifi</groupId>
8888
<artifactId>nifi-framework-authorization</artifactId>
8989
</dependency>
90+
<dependency>
91+
<groupId>org.apache.nifi</groupId>
92+
<artifactId>nifi-security-utils</artifactId>
93+
</dependency>
9094
</dependencies>
9195

9296
</project>

nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@
1616
*/
1717
package org.apache.nifi.authorization;
1818

19+
import java.io.File;
20+
import java.lang.reflect.Constructor;
21+
import java.lang.reflect.Field;
22+
import java.lang.reflect.InvocationTargetException;
23+
import java.lang.reflect.Method;
24+
import java.net.URL;
25+
import java.net.URLClassLoader;
26+
import java.util.HashMap;
27+
import java.util.List;
28+
import java.util.Map;
29+
import javax.xml.XMLConstants;
30+
import javax.xml.bind.JAXBContext;
31+
import javax.xml.bind.JAXBElement;
32+
import javax.xml.bind.JAXBException;
33+
import javax.xml.bind.Unmarshaller;
34+
import javax.xml.stream.XMLStreamReader;
35+
import javax.xml.transform.stream.StreamSource;
36+
import javax.xml.validation.Schema;
37+
import javax.xml.validation.SchemaFactory;
1938
import org.apache.commons.lang3.StringUtils;
2039
import org.apache.nifi.authorization.annotation.AuthorizerContext;
2140
import org.apache.nifi.authorization.exception.AuthorizationAccessException;
@@ -25,6 +44,7 @@
2544
import org.apache.nifi.authorization.generated.Property;
2645
import org.apache.nifi.bundle.Bundle;
2746
import org.apache.nifi.nar.ExtensionManager;
47+
import org.apache.nifi.security.xml.XmlUtils;
2848
import org.apache.nifi.util.NiFiProperties;
2949
import org.apache.nifi.util.file.classloader.ClassLoaderUtils;
3050
import org.slf4j.Logger;
@@ -33,25 +53,6 @@
3353
import org.springframework.beans.factory.FactoryBean;
3454
import org.xml.sax.SAXException;
3555

36-
import javax.xml.XMLConstants;
37-
import javax.xml.bind.JAXBContext;
38-
import javax.xml.bind.JAXBElement;
39-
import javax.xml.bind.JAXBException;
40-
import javax.xml.bind.Unmarshaller;
41-
import javax.xml.transform.stream.StreamSource;
42-
import javax.xml.validation.Schema;
43-
import javax.xml.validation.SchemaFactory;
44-
import java.io.File;
45-
import java.lang.reflect.Constructor;
46-
import java.lang.reflect.Field;
47-
import java.lang.reflect.InvocationTargetException;
48-
import java.lang.reflect.Method;
49-
import java.net.URL;
50-
import java.net.URLClassLoader;
51-
import java.util.HashMap;
52-
import java.util.List;
53-
import java.util.Map;
54-
5556
/**
5657
* Factory bean for loading the configured authorizer.
5758
*/
@@ -168,9 +169,10 @@ private Authorizers loadAuthorizersConfiguration() throws Exception {
168169
final Schema schema = schemaFactory.newSchema(Authorizers.class.getResource(AUTHORIZERS_XSD));
169170

170171
// attempt to unmarshal
172+
final XMLStreamReader xsr = XmlUtils.createSafeReader(new StreamSource(authorizersConfigurationFile));
171173
final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
172174
unmarshaller.setSchema(schema);
173-
final JAXBElement<Authorizers> element = unmarshaller.unmarshal(new StreamSource(authorizersConfigurationFile), Authorizers.class);
175+
final JAXBElement<Authorizers> element = unmarshaller.unmarshal(xsr, Authorizers.class);
174176
return element.getValue();
175177
} catch (SAXException | JAXBException e) {
176178
throw new Exception("Unable to load the authorizer configuration file at: " + authorizersConfigurationFile.getAbsolutePath(), e);

0 commit comments

Comments
 (0)