Commit 11fefb5999c29869d01e6c781ef347cfda64d84a

Authored by Alan Davis
1 parent 366f16b7bf
Exists in master

REPO-480 Platform XXE protection implements OWASP recommendations

- Initial patch (without pom.xml changes to pull in Alfresco 5.0.3 or
     classes that will be excluded just to call class.getName() on them).
... ... @@ -4,7 +4,7 @@
4 4 <parent>
5 5 <groupId>org.alfresco</groupId>
6 6 <artifactId>alfresco-super-pom</artifactId>
7   - <version>6</version>
  7 + <version>7</version>
8 8 </parent>
9 9  
10 10 <artifactId>alfresco-xmlfactory</artifactId>
... ...
src/main/java/org/alfresco/xmlfactory/FactoryHelper.java
1 1 /*
2   - * Copyright (C) 2005-2015 Alfresco Software Limited.
  2 + * Copyright (C) 2005-2016 Alfresco Software Limited.
3 3 *
4 4 * This file is part of Alfresco
5 5 *
... ... @@ -31,6 +31,12 @@ import javax.xml.parsers.SAXParserFactory;
31 31 import org.apache.commons.logging.Log;
32 32 import org.apache.commons.logging.LogFactory;
33 33  
  34 +import org.xml.sax.SAXNotRecognizedException;
  35 +import org.xml.sax.SAXNotSupportedException;
  36 +
  37 +/**
  38 + * The configuration is taken from https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Java
  39 + */
34 40 public class FactoryHelper
35 41 {
36 42 private static final Log logger = LogFactory.getLog(FactoryHelper.class);
... ... @@ -39,6 +45,7 @@ public class FactoryHelper
39 45 public final static String FEATURE_EXTERNAL_PARAMETER_ENTITIES = "http://xml.org/sax/features/external-parameter-entities";
40 46 public final static String FEATURE_USE_ENTITY_RESOLVER2 = "http://xml.org/sax/features/use-entity-resolver2";
41 47 public final static String FEATURE_LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
  48 + public final static String FEATURE_DISALLOW_DOCTYPE = "http://apache.org/xml/features/disallow-doctype-decl";
42 49  
43 50 public final static List<String> DEFAULT_FEATURES_TO_DISABLE = Collections.unmodifiableList(new ArrayList<String>(
44 51 Arrays.asList(FEATURE_EXTERNAL_GENERAL_ENTITIES,
... ... @@ -47,73 +54,135 @@ public class FactoryHelper
47 54 FEATURE_LOAD_EXTERNAL_DTD)));
48 55  
49 56 public final static List<String> DEFAULT_FEATURES_TO_ENABLE = Collections.unmodifiableList(new ArrayList<String>(
50   - Arrays.asList(XMLConstants.FEATURE_SECURE_PROCESSING)));
  57 + Arrays.asList(XMLConstants.FEATURE_SECURE_PROCESSING,
  58 + FEATURE_DISALLOW_DOCTYPE)));
  59 +
  60 + /* white list of classes that can use the parsers with no security restrictions */
  61 + public final static List<String> WHITE_LIST_CALLERS = Collections.unmodifiableList(new ArrayList<String>(
  62 + Arrays.asList(
  63 + "com.sun.xml.ws.transport.http.servlet.WSServletContextListener",
  64 + "org.springframework.beans.factory.xml.XmlBeanDefinitionReader",
  65 + "org.apache.myfaces.config.FacesConfigurator",
  66 + "org.hibernate.cfg.Configuration",
  67 + "org.alfresco.ibatis.HierarchicalXMLConfigBuilder",
  68 + "org.alfresco.repo.security.permissions.impl.model.PermissionModel",
  69 + "org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl"
  70 + )));
51 71  
52 72 public static void configureFactory(DocumentBuilderFactory factory,
53 73 List<String> featuresToEnable, List<String> featuresToDisable)
54 74 {
55   - try
  75 + if (!isCallInWhiteList())
56 76 {
57 77 if (featuresToEnable != null)
58 78 {
59 79 for (String featureToEnable : featuresToEnable)
60 80 {
61   - factory.setFeature(featureToEnable, true);
  81 + setFeature(factory, featureToEnable, true);
62 82 }
63 83 }
64 84 if (featuresToDisable != null)
65 85 {
66 86 for (String featureToDisable : featuresToDisable)
67 87 {
68   - factory.setFeature(featureToDisable, false);
  88 + setFeature(factory, featureToDisable, false);
69 89 }
70 90 }
  91 + applyAdditionalFeatures(factory);
71 92 }
72   - catch (ParserConfigurationException e)
  93 + }
  94 +
  95 + private static boolean isCallInWhiteList()
  96 + {
  97 + StackTraceElement[] currentStackTrace = (new Exception()).getStackTrace();
  98 + for (int i = 0; i < currentStackTrace.length; i++)
73 99 {
74   - //If we get any other exception then we've failed to configure the parser factory as required.
75   - //Return the factory as is.
76   - if (logger.isWarnEnabled())
  100 + for (String className : WHITE_LIST_CALLERS)
77 101 {
78   - logger.warn("Failed to configure DocumentBuilderFactory securely.", e);
  102 + if (currentStackTrace[i].getClassName().equals(className))
  103 + {
  104 + logger.debug("Found " + className + " in white list.");
  105 + return true;
  106 + }
79 107 }
80 108 }
  109 + return false;
81 110 }
82 111  
83   - public static void configureFactory(SAXParserFactory factory,
84   - List<String> featuresToEnable, List<String> featuresToDisable)
  112 + private static void applyAdditionalFeatures(DocumentBuilderFactory factory)
85 113 {
86 114 try
87 115 {
88   - if (featuresToEnable != null)
89   - {
90   - for (String featureToEnable : featuresToEnable)
91   - {
92   - factory.setFeature(featureToEnable, true);
  116 + factory.setXIncludeAware(false);
  117 + factory.setExpandEntityReferences(false);
  118 + }
  119 + catch (Exception e)
  120 + {
  121 + logConfigurationFailure(factory.getClass().getName(), e);
  122 + }
  123 + }
  124 +
  125 + private static void setFeature(DocumentBuilderFactory factory, String feature, boolean enable)
  126 + {
  127 + try
  128 + {
  129 + factory.setFeature(feature, enable);
  130 + }
  131 + catch (ParserConfigurationException pce)
  132 + {
  133 + logConfigurationFailure(factory.getClass().getName(), feature, pce);
  134 + }
  135 + }
  136 +
  137 + private static void setFeature(SAXParserFactory factory, String feature, boolean enable)
  138 + {
  139 + try
  140 + {
  141 + factory.setFeature(feature, enable);
  142 + }
  143 + catch (ParserConfigurationException pce)
  144 + {
  145 + logConfigurationFailure(factory.getClass().getName(), feature, pce);
  146 + }
  147 + catch (SAXNotRecognizedException nre)
  148 + {
  149 + logConfigurationFailure(factory.getClass().getName(), feature, nre);
  150 + }
  151 + catch (SAXNotSupportedException nse)
  152 + {
  153 + logConfigurationFailure(factory.getClass().getName(), feature, nse);
  154 + }
  155 + }
  156 +
  157 + private static void logConfigurationFailure(String factoryName, String feature, Exception e)
  158 + {
  159 + if (logger.isWarnEnabled())
  160 + {
  161 + logger.warn("Failed to configure " + factoryName + " with feature: " + feature, e);
  162 + }
  163 + }
  164 +
  165 + private static void logConfigurationFailure(String factoryName, Exception e)
  166 + {
  167 + if (logger.isWarnEnabled())
  168 + {
  169 + logger.warn("Failed to configure " + factoryName, e);
  170 + }
  171 + }
  172 +
  173 + public static void configureFactory(SAXParserFactory factory,
  174 + List<String> featuresToEnable, List<String> featuresToDisable) {
  175 + if (!isCallInWhiteList()) {
  176 + if (featuresToEnable != null) {
  177 + for (String featureToEnable : featuresToEnable) {
  178 + setFeature(factory, featureToEnable, true);
93 179 }
94 180 }
95   - if (featuresToDisable != null)
96   - {
97   - for (String featureToDisable : featuresToDisable)
98   - {
99   - factory.setFeature(featureToDisable, false);
  181 + if (featuresToDisable != null) {
  182 + for (String featureToDisable : featuresToDisable) {
  183 + setFeature(factory, featureToDisable, false);
100 184 }
101 185 }
102 186 }
103   - catch (RuntimeException rte)
104   - {
105   - //If any runtime exception occurs then simply rethrow it
106   - throw rte;
107   - }
108   - catch (Exception e)
109   - {
110   - //If we get any other exception then we've failed to configure the parser factory as required.
111   - //Return the factory as is.
112   - if (logger.isWarnEnabled())
113   - {
114   - logger.warn("Failed to configure SAXParserFactory securely.", e);
115   - }
116   - }
117 187 }
118   -
119 188 }
... ...