|
28 | 28 | import org.labkey.api.settings.LookAndFeelProperties; |
29 | 29 | import org.xml.sax.SAXException; |
30 | 30 |
|
| 31 | +import javax.xml.XMLConstants; |
31 | 32 | import javax.xml.parsers.DocumentBuilderFactory; |
32 | 33 | import javax.xml.parsers.ParserConfigurationException; |
33 | 34 | import javax.xml.parsers.SAXParserFactory; |
@@ -121,36 +122,78 @@ public static void addComment(XmlTokenSource doc, String comment) |
121 | 122 | cursor.dispose(); |
122 | 123 | } |
123 | 124 |
|
124 | | - /** XML parsing factories preconfigured to prevent XML external entity references (XXE) */ |
| 125 | + /** |
| 126 | + * XML parsing factories preconfigured to prevent XML external entity references (XXE). |
| 127 | + * These are static and are unfortunately mutable. We could switch to a factory pattern to create |
| 128 | + * freshly configured factories. |
| 129 | + */ |
125 | 130 | public static final SAXParserFactory SAX_PARSER_FACTORY; |
| 131 | + public static final SAXParserFactory SAX_PARSER_FACTORY_ALLOWING_DOCTYPE; |
126 | 132 | public static final XMLInputFactory XML_INPUT_FACTORY; |
127 | 133 | public static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY; |
| 134 | + public static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY_ALLOWING_DOCTYPE; |
128 | 135 |
|
129 | 136 | static |
130 | 137 | { |
| 138 | + //noinspection XMLInputFactory |
131 | 139 | XML_INPUT_FACTORY = XMLInputFactory.newInstance(); |
132 | 140 | XML_INPUT_FACTORY.setProperty(XMLInputFactory.SUPPORT_DTD, false); |
133 | 141 | XML_INPUT_FACTORY.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); |
134 | 142 |
|
135 | | - SAX_PARSER_FACTORY = SAXParserFactory.newInstance(); |
136 | 143 | try |
137 | 144 | { |
138 | | - SAX_PARSER_FACTORY.setNamespaceAware(true); |
139 | | - SAX_PARSER_FACTORY.setFeature("http://xml.org/sax/features/validation", false); |
140 | | - SAX_PARSER_FACTORY.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); |
141 | | - SAX_PARSER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); |
142 | | - |
143 | | - DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); |
144 | | - DOCUMENT_BUILDER_FACTORY.setNamespaceAware(true); |
145 | | - DOCUMENT_BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); |
146 | | - DOCUMENT_BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-general-entities", false); |
147 | | - DOCUMENT_BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-parameter-entities", false); |
148 | | - DOCUMENT_BUILDER_FACTORY.setXIncludeAware(false); |
149 | | - DOCUMENT_BUILDER_FACTORY.setExpandEntityReferences(false); |
| 145 | + SAX_PARSER_FACTORY = saxParserFactory(false); |
| 146 | + SAX_PARSER_FACTORY_ALLOWING_DOCTYPE = saxParserFactory(true); |
| 147 | + |
| 148 | + DOCUMENT_BUILDER_FACTORY = documentBuilderFactory(false); |
| 149 | + // Use the ALLOWING_DOCTYPE variant when parsing XML that contains a <!DOCTYPE> declaration (e.g. NCBI's eSummary responses) |
| 150 | + DOCUMENT_BUILDER_FACTORY_ALLOWING_DOCTYPE = documentBuilderFactory(true); |
150 | 151 | } |
151 | 152 | catch (ParserConfigurationException | SAXException e) |
152 | 153 | { |
153 | 154 | throw UnexpectedException.wrap(e); |
154 | 155 | } |
155 | 156 | } |
| 157 | + |
| 158 | + private static SAXParserFactory saxParserFactory(boolean allowDocType) throws SAXException, ParserConfigurationException |
| 159 | + { |
| 160 | + //noinspection XMLInputFactory |
| 161 | + SAXParserFactory result = SAXParserFactory.newInstance(); |
| 162 | + result.setNamespaceAware(true); |
| 163 | + result.setFeature("http://xml.org/sax/features/validation", false); |
| 164 | + result.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); |
| 165 | + |
| 166 | + // Disable features that could lead to XXE or other vulnerabilities |
| 167 | + // Keep in sync with ModuleArchive.nameFromModuleXML() |
| 168 | + if (!allowDocType) |
| 169 | + { |
| 170 | + result.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); |
| 171 | + } |
| 172 | + result.setFeature("http://xml.org/sax/features/external-general-entities", false); |
| 173 | + result.setFeature("http://xml.org/sax/features/external-parameter-entities", false); |
| 174 | + result.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); |
| 175 | + return result; |
| 176 | + } |
| 177 | + |
| 178 | + private static DocumentBuilderFactory documentBuilderFactory(boolean allowDocType) throws ParserConfigurationException |
| 179 | + { |
| 180 | + //noinspection XMLInputFactory |
| 181 | + DocumentBuilderFactory result = DocumentBuilderFactory.newInstance(); |
| 182 | + result.setNamespaceAware(true); |
| 183 | + |
| 184 | + // Disable features that could lead to XXE or other vulnerabilities. |
| 185 | + // When allowDocType is true the DOCTYPE declaration is permitted. External entity |
| 186 | + // resolution remains disabled, so XXE protection is still in effect. |
| 187 | + if (!allowDocType) |
| 188 | + { |
| 189 | + result.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); |
| 190 | + } |
| 191 | + result.setFeature("http://xml.org/sax/features/external-general-entities", false); |
| 192 | + result.setFeature("http://xml.org/sax/features/external-parameter-entities", false); |
| 193 | + result.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); |
| 194 | + result.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); |
| 195 | + result.setXIncludeAware(false); |
| 196 | + result.setExpandEntityReferences(false); |
| 197 | + return result; |
| 198 | + } |
156 | 199 | } |
0 commit comments