Skip to content

Commit

Permalink
PW-1589: Configurable behaviour for SafeXmlUtils (#170)
Browse files Browse the repository at this point in the history
* property to avoid features in safe xml utils WIP
* PW-1589-configurable-behaviour-for-SafeXmlUtils
* code enhancements
---------
Co-authored-by: ptorres-prowide <[email protected]>
  • Loading branch information
zubri authored Sep 20, 2023
1 parent 2470ba5 commit 2e27f7f
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 43 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Prowide Core - CHANGELOG

#### 9.3.18 - SNAPSHOT
* Added support for an optional `pw-swift-core.properties` to customize the behavior of the SafeXmlUtils class

#### 9.3.17 - July 2023
* (PW-1405) Trim original String payload when creating an AbstractSwiftMessage

Expand Down
70 changes: 70 additions & 0 deletions src/main/java/com/prowidesoftware/swift/utils/PropertyLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.prowidesoftware.swift.utils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
* Helper class to load properties from a file.
*
* @since 9.3.18
*/
class PropertyLoader {
private static final java.util.logging.Logger log =
java.util.logging.Logger.getLogger(PropertyLoader.class.getName());
static final String PROPERTIES_FILE = "pw-swift-core.properties";
private static Properties properties = null;

private PropertyLoader() {
// prevent instantiation
}

static Properties loadProperties() {
if (properties == null) {
properties = new Properties();
try (InputStream inputStream = PropertyLoader.class.getClassLoader().getResourceAsStream(PROPERTIES_FILE)) {
if (inputStream != null) {
properties.load(inputStream);
}
} catch (IOException e) {
log.log(java.util.logging.Level.WARNING, "Error loading properties from " + PROPERTIES_FILE, e);
}
}
return properties;
}

static String[] getPropertyArray(String key) {
Properties loadedProperties = loadProperties();
String propertyValue = loadedProperties.getProperty(key);

if (propertyValue != null) {
return propertyValue.split(",");
}

return new String[0];
}

static String getProperty(String key) {
Properties loadedProperties = loadProperties();
String propertyValue = loadedProperties.getProperty(key);

if (propertyValue != null) {
return propertyValue;
}

return null;
}
}
138 changes: 95 additions & 43 deletions src/main/java/com/prowidesoftware/swift/utils/SafeXmlUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.prowidesoftware.swift.utils;

import com.prowidesoftware.ProwideException;
import java.util.logging.Level;
import javax.xml.XMLConstants;
import javax.xml.parsers.*;
import javax.xml.stream.XMLInputFactory;
Expand All @@ -25,23 +26,33 @@
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.apache.commons.lang3.ArrayUtils;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;

/**
* Reusable safe XML document builder to prevent XXE
* https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
* This class is uses in many places in the library to create XML parsers and transformers, to switch off certain
* features that may be vulnerable to XXE attacks.
* <p>
* The features are implementation dependent, thus they might not be present in certain implementations of the XML apis.
* We have experience issues with many xerces and xalan versions. So when faced with an error because a feature is
* not present in your environment, the first choice should be to review the xml related dependencies, and to try to
* those that do not support the feature.
* <p>
* When the dependencies cannot be changed, you can ignore the error by adding a pw-swift-core.properties file in the
* application classpath with a safeXmlUtils.ignore=featureName,featureName,featureName property. This will prevent the
* indicated features to be applied.
*
* @since 8.0.5
*/
public class SafeXmlUtils {
@SuppressWarnings("unused")
private static final transient java.util.logging.Logger log =
private static final java.util.logging.Logger log =
java.util.logging.Logger.getLogger(SafeXmlUtils.class.getName());

// Suppress default constructor for noninstantiability
private static final String FEATURE_IGNORE_PROPERTY = "safeXmlUtils.ignore";

private SafeXmlUtils() {
throw new AssertionError();
}
Expand All @@ -68,29 +79,32 @@ public static DocumentBuilder documentBuilder(boolean namespaceAware) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

feature = XMLConstants.FEATURE_SECURE_PROCESSING;
dbf.setFeature(feature, true);
if (applyFeature(feature)) {
dbf.setFeature(feature, true);
}

// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities

// Using the SAXParserFactory's setFeature
feature = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(feature, false);
if (applyFeature(feature)) {
dbf.setFeature(feature, false);
}

// Xerces 2 only - http://xerces.apache.org/xerces-j/features.html#external-general-entities
feature = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(feature, true);
if (applyFeature(feature)) {
dbf.setFeature(feature, true);
}

// set parameter
dbf.setNamespaceAware(namespaceAware);

return dbf.newDocumentBuilder();

} catch (ParserConfigurationException e) {
throw new ProwideException(
"Error configuring the XML document builder. " + "The feature " + feature
+ " is probably not supported by your XML processor.",
e);
throw logAndCreateException(e, feature, DocumentBuilderFactory.class.getName());
}
}

Expand All @@ -117,18 +131,24 @@ public static XMLReader reader(boolean namespaceAware, Schema schema) throws Pro
SAXParserFactory spf = SAXParserFactory.newInstance();

feature = XMLConstants.FEATURE_SECURE_PROCESSING;
spf.setFeature(feature, true);
if (applyFeature(feature)) {
spf.setFeature(feature, true);
}

// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities

// Using the SAXParserFactory's setFeature
feature = "http://xml.org/sax/features/external-general-entities";
spf.setFeature(feature, false);
if (applyFeature(feature)) {
spf.setFeature(feature, false);
}

// Xerces 2 only - http://xerces.apache.org/xerces-j/features.html#external-general-entities
feature = "http://apache.org/xml/features/disallow-doctype-decl";
spf.setFeature(feature, true);
if (applyFeature(feature)) {
spf.setFeature(feature, true);
}

// set parameters
spf.setNamespaceAware(namespaceAware);
Expand All @@ -142,25 +162,30 @@ public static XMLReader reader(boolean namespaceAware, Schema schema) throws Pro
// Using the XMLReader's setFeature

feature = "http://apache.org/xml/features/disallow-doctype-decl";
reader.setFeature(feature, true);
if (applyFeature(feature)) {
spf.setFeature(feature, true);
}

// This may not be strictly required as DTDs shouldn't be allowed at all, per previous line.
feature = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
reader.setFeature(feature, false);
if (applyFeature(feature)) {
spf.setFeature(feature, false);
}

feature = "http://xml.org/sax/features/external-general-entities";
reader.setFeature(feature, false);
if (applyFeature(feature)) {
spf.setFeature(feature, false);
}

feature = "http://xml.org/sax/features/external-parameter-entities";
reader.setFeature(feature, false);
if (applyFeature(feature)) {
spf.setFeature(feature, false);
}

return reader;

} catch (ParserConfigurationException | SAXException e) {
throw new ProwideException(
"Error configuring the XML parser. " + "The feature " + feature
+ " is probably not supported by your XML processor.",
e);
throw logAndCreateException(e, feature, SAXParserFactory.class.getName());
}
}

Expand All @@ -173,10 +198,14 @@ public static XMLInputFactory inputFactory() {
XMLInputFactory xif = XMLInputFactory.newInstance();

// This disables DTDs entirely for that factory
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);

if (applyFeature(XMLInputFactory.SUPPORT_DTD)) {
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
}
// disable external entities
xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false);
String property = "javax.xml.stream.isSupportingExternalEntities";
if (applyFeature(property)) {
xif.setProperty(property, false);
}

return xif;
}
Expand All @@ -190,18 +219,19 @@ public static Transformer transformer() {
TransformerFactory tf = TransformerFactory.newInstance();

feature = XMLConstants.ACCESS_EXTERNAL_DTD;
tf.setAttribute(feature, "");
if (applyFeature(feature)) {
tf.setAttribute(feature, "");
}

feature = XMLConstants.ACCESS_EXTERNAL_STYLESHEET;
tf.setAttribute(feature, "");
if (applyFeature(feature)) {
tf.setAttribute(feature, "");
}

return tf.newTransformer();

} catch (TransformerConfigurationException e) {
throw new ProwideException(
"Error configuring the XML transformer factory. " + "The feature " + feature
+ " is probably not supported by your XML processor.",
e);
throw logAndCreateException(e, feature, Transformer.class.getName());
}
}

Expand All @@ -215,7 +245,9 @@ public static SchemaFactory schemaFactory() {

// https://stackoverflow.com/questions/58374278/org-xml-sax-saxnotrecognizedexception-property-http-javax-xml-xmlconstants-p
feature = XMLConstants.ACCESS_EXTERNAL_DTD;
factory.setProperty(feature, "");
if (applyFeature(feature)) {
factory.setProperty(feature, "");
}

// we keep this one for the moment because it is needed in MX xsys validation
// feature = XMLConstants.ACCESS_EXTERNAL_SCHEMA;
Expand All @@ -224,10 +256,7 @@ public static SchemaFactory schemaFactory() {
return factory;

} catch (SAXNotRecognizedException | SAXNotSupportedException e) {
throw new ProwideException(
"Error configuring the schema factory. " + "The feature " + feature
+ " is probably not supported by your XML processor.",
e);
throw logAndCreateException(e, feature, SchemaFactory.class.getName());
}
}

Expand All @@ -240,18 +269,41 @@ public static Validator validator(Schema schema) {
Validator validator = schema.newValidator();

feature = XMLConstants.ACCESS_EXTERNAL_DTD;
validator.setProperty(feature, "");
if (applyFeature(feature)) {
validator.setProperty(feature, "");
}

feature = XMLConstants.ACCESS_EXTERNAL_SCHEMA;
validator.setProperty(feature, "");
if (applyFeature(feature)) {
validator.setProperty(feature, "");
}

return validator;

} catch (SAXNotRecognizedException | SAXNotSupportedException e) {
throw new ProwideException(
"Error configuring the schema validator. " + "The feature " + feature
+ " is probably not supported by your XML processor.",
e);
throw logAndCreateException(e, feature, Validator.class.getName());
}
}

private static boolean applyFeature(final String feature) {
final String[] prop = PropertyLoader.getPropertyArray(FEATURE_IGNORE_PROPERTY);
return (!ArrayUtils.contains(prop, feature));
}

private static ProwideException logAndCreateException(Exception e, String feature, String className) {
log.log(
Level.SEVERE,
"Error configuring the " + className + ". The feature " + feature
+ " is not supported by your XML processor. Increase log level for details.");
log.log(
Level.FINER,
"To avoid the missing feature, try removing the xerces and xalan dependencies in your project. If that is not doable, you can ignore this error by adding a "
+ PropertyLoader.PROPERTIES_FILE + " in your application classpath with property "
+ FEATURE_IGNORE_PROPERTY + "=" + feature,
e);
return new ProwideException(
"Error configuring the " + className + ". The feature " + feature
+ " is not supported by your XML processor.",
e);
}
}

0 comments on commit 2e27f7f

Please sign in to comment.