From 0603c0fbdfe028113431c65590b6e7e28929f6f6 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Fri, 23 Jun 2017 06:50:14 +0200 Subject: some small refactoring and code documentation --- .../filter/MoccaHttpServletRequestWrapper.java | 1 - .../egiz/bku/online/filter/StalSecurityFilter.java | 48 +------------ .../at/gv/egiz/mocca/id/DataURLServerServlet.java | 16 ++--- .../at/gv/egiz/bku/slxhtml/SLXHTMLValidator.java | 3 + utils/src/main/java/at/gv/egiz/dom/DOMUtils.java | 78 +++++++++++++++++++++- .../java/at/gv/egiz/slbinding/SLUnmarshaller.java | 52 +++++++-------- .../at/gv/egiz/slbinding/UnmarshallCXSRTest.java | 6 ++ 7 files changed, 114 insertions(+), 90 deletions(-) diff --git a/BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/MoccaHttpServletRequestWrapper.java b/BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/MoccaHttpServletRequestWrapper.java index 8901969d..d01f8128 100644 --- a/BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/MoccaHttpServletRequestWrapper.java +++ b/BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/MoccaHttpServletRequestWrapper.java @@ -5,7 +5,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; -import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; diff --git a/BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/StalSecurityFilter.java b/BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/StalSecurityFilter.java index 0e98cb79..356401b6 100644 --- a/BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/StalSecurityFilter.java +++ b/BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/StalSecurityFilter.java @@ -1,26 +1,20 @@ package at.gv.egiz.bku.online.filter; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLStreamException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.xml.sax.SAXException; + +import at.gv.egiz.dom.DOMUtils; public class StalSecurityFilter implements Filter { @@ -43,7 +37,7 @@ public class StalSecurityFilter implements Filter { if (stalHttpReq.isInputStreamAvailable()) { log.trace("Validate STAL request ... "); - validateStalRequest(stalHttpReq.getInputStream()); + DOMUtils.validateXMLAgainstXXEAndSSRFAttacks(stalHttpReq.getInputStream()); log.trace("Validate of STAL request completed"); } @@ -71,7 +65,6 @@ public class StalSecurityFilter implements Filter { @Override public void destroy() { - // TODO Auto-generated method stub } @@ -83,40 +76,5 @@ public class StalSecurityFilter implements Filter { log.error("Can not response with http error message"); } - - private void validateStalRequest(InputStream is) throws XMLStreamException, IOException { - - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - - try { - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - - } catch (ParserConfigurationException e) { - log.error("Can NOT set Xerces parser security features. -> XML parsing is possible insecure!!!! ", e); - - } - - try { - //validate input stream - dbf.newDocumentBuilder().parse(is); - - } catch (SAXException e) { - log.error("XML data validation FAILED with msg: " + e.getMessage(), e); - throw new XMLStreamException("XML data validation FAILED with msg: " + e.getMessage(), e); - - } catch (ParserConfigurationException e) { - log.error("XML data validation FAILED with msg: " + e.getMessage(), e); - throw new XMLStreamException("XML data validation FAILED with msg: " + e.getMessage(), e); - - } catch (IOException e) { - log.error("XML data validation FAILED with msg: " + e.getMessage(), e); - throw new XMLStreamException("XML data validation FAILED with msg: " + e.getMessage(), e); - - } - } - } diff --git a/BKUOnline/src/main/java/at/gv/egiz/mocca/id/DataURLServerServlet.java b/BKUOnline/src/main/java/at/gv/egiz/mocca/id/DataURLServerServlet.java index 37889ae5..d34ead45 100644 --- a/BKUOnline/src/main/java/at/gv/egiz/mocca/id/DataURLServerServlet.java +++ b/BKUOnline/src/main/java/at/gv/egiz/mocca/id/DataURLServerServlet.java @@ -65,6 +65,7 @@ import at.gv.egiz.bku.slcommands.impl.SLCommandImpl; import at.gv.egiz.bku.slexceptions.SLCommandException; import at.gv.egiz.bku.utils.DebugInputStream; import at.gv.egiz.bku.utils.StreamUtil; +import at.gv.egiz.dom.DOMUtils; import at.gv.egiz.org.apache.tomcat.util.http.AcceptLanguage; import at.gv.egiz.slbinding.SLUnmarshaller; @@ -152,18 +153,9 @@ public class DataURLServerServlet extends HttpServlet { "(see http://www.w3.org/TR/xmldsig-bestpractices/#be-aware-schema-normalization)", e); } - try { - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - - } catch (ParserConfigurationException e) { - log.error("Can NOT set SAX parser security features. -> XML parsing is possible insecure!!!! ", e); - - } - - + //set XML parser flags to prevent XXE, XEE and SSRF attacks + DOMUtils.setXMLParserFlagsAgainstXXEAndSSRFAttacks(dbf); + DocumentBuilder documentBuilder; try { documentBuilder = dbf.newDocumentBuilder(); diff --git a/BKUViewer/src/main/java/at/gv/egiz/bku/slxhtml/SLXHTMLValidator.java b/BKUViewer/src/main/java/at/gv/egiz/bku/slxhtml/SLXHTMLValidator.java index 95d2b78c..6fea75cb 100644 --- a/BKUViewer/src/main/java/at/gv/egiz/bku/slxhtml/SLXHTMLValidator.java +++ b/BKUViewer/src/main/java/at/gv/egiz/bku/slxhtml/SLXHTMLValidator.java @@ -139,6 +139,9 @@ public class SLXHTMLValidator implements at.gv.egiz.bku.viewer.Validator { spf.setValidating(true); spf.setXIncludeAware(false); + /* + * Set parser features to disallow external entities and external dtd load operations + */ try { spf.setFeature("http://xml.org/sax/features/external-general-entities", false); spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); diff --git a/utils/src/main/java/at/gv/egiz/dom/DOMUtils.java b/utils/src/main/java/at/gv/egiz/dom/DOMUtils.java index eae8f05e..2054021a 100644 --- a/utils/src/main/java/at/gv/egiz/dom/DOMUtils.java +++ b/utils/src/main/java/at/gv/egiz/dom/DOMUtils.java @@ -33,7 +33,8 @@ import java.io.StringWriter; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; @@ -47,7 +48,8 @@ import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.Text; import org.w3c.dom.bootstrap.DOMImplementationRegistry; -import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.DOMImplementationLS; +import org.xml.sax.SAXException; public final class DOMUtils { @@ -160,6 +162,76 @@ public final class DOMUtils { base64OutputStream.close(); return doc.createTextNode(outputStream.toString("ASCII")); - } + } + + /** + * Set XML parser features to {@link DocumentBuilderFactory} to prevent XXE, XEE and SSRF attacks + *
+ *
+ * These features are set by this method: + * + * + * + * @param dbf {@link DocumentBuilderFactory} on which the features should be registered + */ + public static void setXMLParserFlagsAgainstXXEAndSSRFAttacks(DocumentBuilderFactory dbf) { + try { + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + + } catch (ParserConfigurationException e) { + log.error("Can NOT set Xerces parser security features. -> XML parsing is possible insecure!!!! ", e); + + } + + } + + /** + * Parse an {@link InputStream} that contains a XML document into a {@link Document} + * This method set all features to prevent XXE, XEE and SSRF attacks + * + * These features are set by this method: + * + * + * @param is {@link InputStream} that contains a serialized XML document + * @return Deserialized {@link Document} from input XML + * @throws XMLStreamException XML parser has an error + * @throws IOException + */ + public static Document validateXMLAgainstXXEAndSSRFAttacks(InputStream is) throws XMLStreamException, IOException { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + setXMLParserFlagsAgainstXXEAndSSRFAttacks(dbf); + + try { + //validate input stream + return dbf.newDocumentBuilder().parse(is); + + } catch (SAXException e) { + log.error("XML data validation FAILED with msg: " + e.getMessage(), e); + throw new XMLStreamException("XML data validation FAILED with msg: " + e.getMessage(), e); + + } catch (ParserConfigurationException e) { + log.error("XML data validation FAILED with msg: " + e.getMessage(), e); + throw new XMLStreamException("XML data validation FAILED with msg: " + e.getMessage(), e); + + } catch (IOException e) { + log.error("XML data validation FAILED with msg: " + e.getMessage(), e); + throw new XMLStreamException("XML data validation FAILED with msg: " + e.getMessage(), e); + + } + + } } diff --git a/utils/src/main/java/at/gv/egiz/slbinding/SLUnmarshaller.java b/utils/src/main/java/at/gv/egiz/slbinding/SLUnmarshaller.java index 2d1808c8..489c36d8 100644 --- a/utils/src/main/java/at/gv/egiz/slbinding/SLUnmarshaller.java +++ b/utils/src/main/java/at/gv/egiz/slbinding/SLUnmarshaller.java @@ -57,6 +57,7 @@ import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; import at.gv.egiz.bku.utils.ClasspathURLStreamHandler; +import at.gv.egiz.dom.DOMUtils; import at.gv.egiz.validation.ReportingValidationEventHandler; public class SLUnmarshaller { @@ -241,43 +242,28 @@ public class SLUnmarshaller { * @throws JAXBException */ public Object unmarshal(StreamSource source) throws XMLStreamException, JAXBException { + Reader inputReader = source.getReader(); - ReportingValidationEventHandler validationEventHandler = new ReportingValidationEventHandler(); - Reader inputReader = source.getReader(); - - if (inputReader instanceof InputStreamReader) { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - - try { - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - - } catch (ParserConfigurationException e) { - log.error("Can NOT set Xerces parser security features. -> XML parsing is possible insecure!!!! ", e); - - } - + /* Validate XML against XXE, XEE, and SSRF + * + * This pre-validation step is required because com.sun.xml.stream.sjsxp-1.0.2 XML stream parser library does not + * support all XML parser features to prevent these types of attacks + */ + if (inputReader instanceof InputStreamReader) { try { //create copy of input stream InputStreamReader isReader = (InputStreamReader) inputReader; String encoding = isReader.getEncoding(); byte[] backup = IOUtils.toByteArray(isReader, encoding); - //validate input stream - dbf.newDocumentBuilder().parse(new ByteArrayInputStream(backup)); + //validate input stream + DOMUtils.validateXMLAgainstXXEAndSSRFAttacks(new ByteArrayInputStream(backup)); //create new inputStreamReader for reak processing inputReader = new InputStreamReader(new ByteArrayInputStream(backup), encoding); - } catch (SAXException e) { - log.error("XML data validation FAILED with msg: " + e.getMessage(), e); - throw new XMLStreamException("XML data validation FAILED with msg: " + e.getMessage(), e); - - - } catch (ParserConfigurationException e) { + } catch (XMLStreamException e) { log.error("XML data validation FAILED with msg: " + e.getMessage(), e); throw new XMLStreamException("XML data validation FAILED with msg: " + e.getMessage(), e); @@ -294,20 +280,28 @@ public Object unmarshal(StreamSource source) throws XMLStreamException, JAXBExce } - //parse XML with original functionality + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * parse XML with original functionality + * + * This code implements the the original mocca XML processing by using + * com.sun.xml.stream.sjsxp-1.0.2 XML stream parser library. Currently, this library is required to get full + * security-layer specific XML processing. However, there this lib does not fully support XXE, XEE and SSRF + * prevention mechanisms (e.g.: XMLInputFactory.SUPPORT_DTD flag is not used) + * + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + */ XMLInputFactory inputFactory = XMLInputFactory.newInstance(); //disallow DTD and external entities inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false); - - - + XMLEventReader eventReader = inputFactory.createXMLEventReader(inputReader); RedirectEventFilter redirectEventFilter = new RedirectEventFilter(); XMLEventReader filteredReader = inputFactory.createFilteredReader(eventReader, redirectEventFilter); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + ReportingValidationEventHandler validationEventHandler = new ReportingValidationEventHandler(); unmarshaller.setEventHandler(validationEventHandler); unmarshaller.setListener(new RedirectUnmarshallerListener(redirectEventFilter)); diff --git a/utils/src/test/java/at/gv/egiz/slbinding/UnmarshallCXSRTest.java b/utils/src/test/java/at/gv/egiz/slbinding/UnmarshallCXSRTest.java index 62a8d622..5f97be0f 100644 --- a/utils/src/test/java/at/gv/egiz/slbinding/UnmarshallCXSRTest.java +++ b/utils/src/test/java/at/gv/egiz/slbinding/UnmarshallCXSRTest.java @@ -77,6 +77,12 @@ public class UnmarshallCXSRTest { Object value = ((JAXBElement) object).getValue(); assertFalse(value.getClass().getName(), value instanceof CreateXMLSignatureResponseType); + /* If the parser has no exception and no CreateXMLSignatureResponseType than the test fails, because + * the tested XML document contains a CreateXMLSignatureResponseType and an XXE, SSRF attack vector. + * Consequently, the parser result has to be an error + */ + assertFalse(true); + } catch (XMLStreamException e) { assertTrue(e.getClass().getName(), e instanceof XMLStreamException); -- cgit v1.2.3