Skip to content

Commit 9306d40

Browse files
saml: Safer DocumentBuilderFactory and ParserPool configuration
This implements safer DocumentBuilderFactory and ParserPool utilities to be used throughout the codebase to prevent potential XXE exploits. References: https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf Signed-off-by: Rohit Yadav <[email protected]> (cherry picked from commit 8e0e68ef368ebe2793ef80e2c3821eaecb47b593) Signed-off-by: Rohit Yadav <[email protected]>
1 parent 4b7d229 commit 9306d40

File tree

6 files changed

+67
-5
lines changed

6 files changed

+67
-5
lines changed

plugins/user-authenticators/saml2/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<dependency>
3232
<groupId>org.opensaml</groupId>
3333
<artifactId>opensaml</artifactId>
34+
<version>${cs.opensaml.version}</version>
3435
</dependency>
3536
<dependency>
3637
<groupId>org.apache.cloudstack</groupId>

plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.cloudstack.api.response.SAMLMetaDataResponse;
3131
import org.apache.cloudstack.saml.SAML2AuthManager;
3232
import org.apache.cloudstack.saml.SAMLProviderMetadata;
33+
import org.apache.cloudstack.utils.security.ParserUtils;
3334
import org.apache.log4j.Logger;
3435
import org.opensaml.Configuration;
3536
import org.opensaml.DefaultBootstrap;
@@ -239,7 +240,7 @@ public String authenticate(String command, Map<String, Object[]> params, HttpSes
239240

240241
StringWriter stringWriter = new StringWriter();
241242
try {
242-
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
243+
DocumentBuilderFactory factory = ParserUtils.getSaferDocumentBuilderFactory();
243244
DocumentBuilder builder = factory.newDocumentBuilder();
244245
Document document = builder.newDocument();
245246
Marshaller out = Configuration.getMarshallerFactory().getMarshaller(spEntityDescriptor);

plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
7979
import org.opensaml.xml.ConfigurationException;
8080
import org.opensaml.xml.XMLObject;
81-
import org.opensaml.xml.parse.BasicParserPool;
8281
import org.opensaml.xml.security.credential.UsageType;
8382
import org.opensaml.xml.security.keyinfo.KeyInfoHelper;
8483
import org.springframework.stereotype.Component;
@@ -389,7 +388,7 @@ private boolean setup() {
389388
}
390389
}
391390
_idpMetaDataProvider.setRequireValidMetadata(true);
392-
_idpMetaDataProvider.setParserPool(new BasicParserPool());
391+
_idpMetaDataProvider.setParserPool(SAMLUtils.getSaferParserPool());
393392
_idpMetaDataProvider.initialize();
394393
_timer.scheduleAtFixedRate(new MetadataRefreshTask(), 0, _refreshInterval * 1000);
395394

plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,15 @@
4242
import java.security.spec.InvalidKeySpecException;
4343
import java.security.spec.PKCS8EncodedKeySpec;
4444
import java.security.spec.X509EncodedKeySpec;
45+
import java.util.HashMap;
4546
import java.util.List;
47+
import java.util.Map;
4648
import java.util.zip.Deflater;
4749
import java.util.zip.DeflaterOutputStream;
4850

4951
import javax.servlet.http.Cookie;
5052
import javax.servlet.http.HttpServletResponse;
53+
import javax.xml.XMLConstants;
5154
import javax.xml.parsers.DocumentBuilder;
5255
import javax.xml.parsers.DocumentBuilderFactory;
5356
import javax.xml.parsers.ParserConfigurationException;
@@ -56,6 +59,7 @@
5659
import org.apache.cloudstack.api.ApiConstants;
5760
import org.apache.cloudstack.api.response.LoginCmdResponse;
5861
import org.apache.cloudstack.utils.security.CertUtils;
62+
import org.apache.cloudstack.utils.security.ParserUtils;
5963
import org.apache.log4j.Logger;
6064
import org.bouncycastle.operator.OperatorCreationException;
6165
import org.joda.time.DateTime;
@@ -88,6 +92,7 @@
8892
import org.opensaml.xml.io.Unmarshaller;
8993
import org.opensaml.xml.io.UnmarshallerFactory;
9094
import org.opensaml.xml.io.UnmarshallingException;
95+
import org.opensaml.xml.parse.BasicParserPool;
9196
import org.opensaml.xml.signature.SignatureConstants;
9297
import org.opensaml.xml.util.Base64;
9398
import org.opensaml.xml.util.XMLHelper;
@@ -227,7 +232,7 @@ public static String encodeSAMLRequest(XMLObject authnRequest)
227232
public static Response decodeSAMLResponse(String responseMessage)
228233
throws ConfigurationException, ParserConfigurationException,
229234
SAXException, IOException, UnmarshallingException {
230-
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
235+
DocumentBuilderFactory documentBuilderFactory = ParserUtils.getSaferDocumentBuilderFactory();
231236
documentBuilderFactory.setNamespaceAware(true);
232237
DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
233238
byte[] base64DecodedResponse = Base64.decode(responseMessage);
@@ -361,4 +366,19 @@ public static X509Certificate generateRandomX509Certificate(KeyPair keyPair) thr
361366
"CN=ApacheCloudStack", "CN=ApacheCloudStack",
362367
3, "SHA256WithRSA");
363368
}
369+
370+
public static BasicParserPool getSaferParserPool() {
371+
final Map<String, Boolean> features = new HashMap<>();
372+
features.put(XMLConstants.FEATURE_SECURE_PROCESSING, true);
373+
features.put("http://apache.org/xml/features/disallow-doctype-decl", true);
374+
features.put("http://xml.org/sax/features/external-general-entities", false);
375+
features.put("http://xml.org/sax/features/external-parameter-entities", false);
376+
features.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
377+
final BasicParserPool parserPool = new BasicParserPool();
378+
parserPool.setXincludeAware(false);
379+
parserPool.setIgnoreComments(true);
380+
parserPool.setExpandEntityReferences(false);
381+
parserPool.setBuilderFeatures(features);
382+
return parserPool;
383+
}
364384
}

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
<cs.mysql.version>8.0.19</cs.mysql.version>
160160
<cs.neethi.version>2.0.4</cs.neethi.version>
161161
<cs.nitro.version>10.1</cs.nitro.version>
162-
<cs.opensaml.version>2.6.4</cs.opensaml.version>
162+
<cs.opensaml.version>2.6.6</cs.opensaml.version>
163163
<cs.rados-java.version>0.6.0</cs.rados-java.version>
164164
<cs.reflections.version>0.9.12</cs.reflections.version>
165165
<cs.servicemix.version>3.4.4_1</cs.servicemix.version>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with 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,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.utils.security;
19+
20+
import javax.xml.XMLConstants;
21+
import javax.xml.parsers.DocumentBuilderFactory;
22+
import javax.xml.parsers.ParserConfigurationException;
23+
24+
public class ParserUtils {
25+
public static DocumentBuilderFactory getSaferDocumentBuilderFactory() throws ParserConfigurationException {
26+
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
27+
28+
// REDHAT https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
29+
// OWASP https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
30+
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
31+
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
32+
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
33+
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
34+
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
35+
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
36+
factory.setXIncludeAware(false);
37+
factory.setExpandEntityReferences(false);
38+
39+
return factory;
40+
}
41+
}

0 commit comments

Comments
 (0)