From 345a8534ff39cc9550cbacabe2b3fffe20293508 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Thu, 22 Jun 2017 14:26:15 +0200 Subject: implement a workaround to fix XXE and SSRF problems in an old XMLStreamParser implementation of a third party library --- .../filter/MoccaHttpServletRequestWrapper.java | 72 ++++++++++++ .../egiz/bku/online/filter/StalSecurityFilter.java | 122 +++++++++++++++++++++ .../at/gv/egiz/mocca/id/DataURLServerServlet.java | 13 ++- BKUOnline/src/main/webapp/WEB-INF/web.xml | 8 ++ .../at/gv/egiz/bku/slxhtml/SLXHTMLValidator.java | 11 ++ .../egiz/bku/binding/HTTPBindingProcessorImpl.java | 3 +- .../egiz/bku/slcommands/SLCommandFactoryTest.java | 5 +- utils/pom.xml | 6 + .../java/at/gv/egiz/slbinding/SLUnmarshaller.java | 70 ++++++++++-- .../at/gv/egiz/slbinding/UnmarshallCXSRTest.java | 26 ++++- .../CreateXMLSignatureResponse_with_Attacke.xml | 25 +++++ 11 files changed, 349 insertions(+), 12 deletions(-) create mode 100644 BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/MoccaHttpServletRequestWrapper.java create mode 100644 BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/StalSecurityFilter.java create mode 100644 utils/src/test/resources/at/gv/egiz/slbinding/CreateXMLSignatureResponse_with_Attacke.xml 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 new file mode 100644 index 00000000..8901969d --- /dev/null +++ b/BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/MoccaHttpServletRequestWrapper.java @@ -0,0 +1,72 @@ +package at.gv.egiz.bku.online.filter; + +import java.io.BufferedReader; +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; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import at.gv.egiz.bku.binding.HttpUtil; + + +public class MoccaHttpServletRequestWrapper extends HttpServletRequestWrapper { + + private static Logger log = LoggerFactory.getLogger(MoccaHttpServletRequestWrapper.class); + + private final byte[] body; + private final String charset; + + public MoccaHttpServletRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + + String ct = request.getHeader(HttpUtil.HTTP_HEADER_CONTENT_TYPE.toLowerCase()); + charset = HttpUtil.getCharset(ct, true); + + byte[] result = null; + try { + result = IOUtils.toByteArray(request.getReader(), charset); + + } catch (IOException e) { + log.error("Can not copy input stream!!!!!", e); + throw new IOException("Can not copy input stream!!!!!", e); + + } finally { + body = result; + + } + } + + public boolean isInputStreamAvailable() { + return (body != null && body.length > 0); + + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream(), charset)); + + } + + @Override + public ServletInputStream getInputStream() throws IOException { + final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); + return new ServletInputStream() { + + @Override + public int read() throws IOException { + return byteArrayInputStream.read(); + } + + }; + + } +} 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 new file mode 100644 index 00000000..0e98cb79 --- /dev/null +++ b/BKUOnline/src/main/java/at/gv/egiz/bku/online/filter/StalSecurityFilter.java @@ -0,0 +1,122 @@ +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; + + +public class StalSecurityFilter implements Filter { + + private static Logger log = LoggerFactory.getLogger(StalSecurityFilter.class); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + log.info("Initialize STAL Service security filter"); + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + if (request instanceof HttpServletRequest) { + try { + MoccaHttpServletRequestWrapper stalHttpReq = new MoccaHttpServletRequestWrapper((HttpServletRequest) request); + + if (stalHttpReq.isInputStreamAvailable()) { + log.trace("Validate STAL request ... "); + validateStalRequest(stalHttpReq.getInputStream()); + log.trace("Validate of STAL request completed"); + + } + + chain.doFilter(stalHttpReq, response); + + } catch (XMLStreamException e) { + log.error("XML data validation FAILED with msg: " + e.getMessage(), e); + sendErrorToResponse(e, response); + + } catch (IOException e) { + log.error("Can not process InputStream from STAL request"); + sendErrorToResponse(e, response); + + } + + } else { + log.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + log.warn("STAL request is processed WITHOUT security checks!!!!"); + log.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + chain.doFilter(request, response); + + } + } + + @Override + public void destroy() { + // TODO Auto-generated method stub + + } + + private void sendErrorToResponse(Exception e, ServletResponse response) throws IOException { + if (response instanceof HttpServletResponse) { + ((HttpServletResponse)response).sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); + + } else + 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 7dd2cd22..37889ae5 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 @@ -135,7 +135,6 @@ public class DataURLServerServlet extends HttpServlet { } SLUnmarshaller slUnmarshaller = new SLUnmarshaller(); - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setSchema(slUnmarshaller.getSlSchema()); @@ -153,6 +152,18 @@ 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); + + } + + DocumentBuilder documentBuilder; try { documentBuilder = dbf.newDocumentBuilder(); diff --git a/BKUOnline/src/main/webapp/WEB-INF/web.xml b/BKUOnline/src/main/webapp/WEB-INF/web.xml index 5033cc5e..5779fc97 100644 --- a/BKUOnline/src/main/webapp/WEB-INF/web.xml +++ b/BKUOnline/src/main/webapp/WEB-INF/web.xml @@ -175,6 +175,14 @@ RequestIdFilter at.gv.egiz.bku.online.webapp.TransactionIdFilter + + StalSecurityFilter + at.gv.egiz.bku.online.filter.StalSecurityFilter + + + StalSecurityFilter + STALService + RequestIdFilter HTTPSecurityLayerServlet 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 fe48eefa..95d2b78c 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,16 @@ public class SLXHTMLValidator implements at.gv.egiz.bku.viewer.Validator { spf.setValidating(true); spf.setXIncludeAware(false); + try { + spf.setFeature("http://xml.org/sax/features/external-general-entities", false); + spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + + } catch (Exception e) { + log.error("Can NOT set SAX parser security features. -> XML parsing is possible insecure!!!! ", e); + + } + SAXParser parser; try { parser = spf.newSAXParser(); @@ -150,6 +160,7 @@ public class SLXHTMLValidator implements at.gv.egiz.bku.viewer.Validator { throw new RuntimeException("Failed to create SLXHTML parser.", e); } + InputSource source; if (charset != null) { source = new InputSource(new InputStreamReader(is, charset)); diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/HTTPBindingProcessorImpl.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/HTTPBindingProcessorImpl.java index 8891cce7..0c637d72 100644 --- a/bkucommon/src/main/java/at/gv/egiz/bku/binding/HTTPBindingProcessorImpl.java +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/HTTPBindingProcessorImpl.java @@ -26,6 +26,7 @@ package at.gv.egiz.bku.binding; import iaik.utils.Base64InputStream; +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -737,7 +738,7 @@ public class HTTPBindingProcessorImpl extends AbstractBindingProcessor implement protected void assignXMLRequest(InputStream is, String charset) throws IOException, SLException { - Reader r = new InputStreamReader(is, charset); + Reader r = new InputStreamReader(new BufferedInputStream(is), charset); StreamSource source = new StreamSource(r); slCommand = slCommandFactory.createSLCommand(source); log.info("XMLRequest={}. Created new command: {}.", diff --git a/bkucommon/src/test/java/at/gv/egiz/bku/slcommands/SLCommandFactoryTest.java b/bkucommon/src/test/java/at/gv/egiz/bku/slcommands/SLCommandFactoryTest.java index eda3e4e8..cfe5a130 100644 --- a/bkucommon/src/test/java/at/gv/egiz/bku/slcommands/SLCommandFactoryTest.java +++ b/bkucommon/src/test/java/at/gv/egiz/bku/slcommands/SLCommandFactoryTest.java @@ -26,6 +26,7 @@ package at.gv.egiz.bku.slcommands; import static org.junit.Assert.assertTrue; +import java.io.BufferedReader; import java.io.Reader; import java.io.StringReader; @@ -83,10 +84,10 @@ public class SLCommandFactoryTest { @Test(expected=SLRequestException.class) public void createMalformedCommand() throws SLCommandException, SLRuntimeException, SLRequestException, SLVersionException { - Reader requestReader = new StringReader( + Reader requestReader = new BufferedReader(new StringReader( "" + "missplacedContent" + - ""); + "")); StreamSource source = new StreamSource(requestReader); factory.createSLCommand(source); diff --git a/utils/pom.xml b/utils/pom.xml index 5a41e98e..95d7f655 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -37,6 +37,12 @@ com.sun.xml.stream 1.0.2 + + + commons-io + commons-io + 2.5 + 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 de1b2ddf..2d1808c8 100644 --- a/utils/src/main/java/at/gv/egiz/slbinding/SLUnmarshaller.java +++ b/utils/src/main/java/at/gv/egiz/slbinding/SLUnmarshaller.java @@ -25,7 +25,10 @@ package at.gv.egiz.slbinding; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; import java.net.URL; import java.util.Arrays; import java.util.Collection; @@ -38,6 +41,8 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.UnmarshalException; import javax.xml.bind.Unmarshaller; import javax.xml.bind.ValidationEvent; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; @@ -46,6 +51,7 @@ import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; @@ -237,17 +243,67 @@ public class SLUnmarshaller { public Object unmarshal(StreamSource source) throws XMLStreamException, JAXBException { ReportingValidationEventHandler validationEventHandler = new ReportingValidationEventHandler(); -// System.setProperty("javax.xml.stream.XMLInputFactory", "com.sun.xml.stream.ZephyrParserFactory"); -// System.setProperty("com.sun.xml.stream.ZephyrParserFactory", "com.sun.xml.stream.ZephyrParserFactory"); -// XMLInputFactory inputFactory = XMLInputFactory.newInstance("com.sun.xml.stream.ZephyrParserFactory", null); - + 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); + + } + + 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)); + + //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) { + 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); + + } + + } else { + log.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + log.error("Reader is not of type InputStreamReader -> can not make a copy of the InputStream --> extended XML validation is not possible!!! "); + log.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + } + + //parse XML with original functionality 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(source.getReader()); + + + XMLEventReader eventReader = inputFactory.createXMLEventReader(inputReader); RedirectEventFilter redirectEventFilter = new RedirectEventFilter(); XMLEventReader filteredReader = inputFactory.createFilteredReader(eventReader, redirectEventFilter); @@ -255,8 +311,8 @@ public Object unmarshal(StreamSource source) throws XMLStreamException, JAXBExce unmarshaller.setEventHandler(validationEventHandler); unmarshaller.setListener(new RedirectUnmarshallerListener(redirectEventFilter)); - unmarshaller.setSchema(slSchema); - + unmarshaller.setSchema(slSchema); + Object object; try { log.trace("Before unmarshal()."); 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 99c11cbe..62a8d622 100644 --- a/utils/src/test/java/at/gv/egiz/slbinding/UnmarshallCXSRTest.java +++ b/utils/src/test/java/at/gv/egiz/slbinding/UnmarshallCXSRTest.java @@ -25,6 +25,7 @@ package at.gv.egiz.slbinding; +import java.io.BufferedInputStream; import java.io.InputStream; import java.io.InputStreamReader; @@ -49,7 +50,7 @@ public class UnmarshallCXSRTest { assertNotNull(s); SLUnmarshaller unmarshaller = new SLUnmarshaller(); - Object object = unmarshaller.unmarshal(new StreamSource(new InputStreamReader(s))); + Object object = unmarshaller.unmarshal(new StreamSource(new InputStreamReader(new BufferedInputStream(s)))); assertTrue(object.getClass().getName(), object instanceof JAXBElement); @@ -59,4 +60,27 @@ public class UnmarshallCXSRTest { } + @Test + public void testUnmarshalCreateXMLSignatureResponseWithDocTypeXXEOrSSRF() throws JAXBException { + + ClassLoader cl = UnmarshallCXSRTest.class.getClassLoader(); + InputStream s = cl.getResourceAsStream("at/gv/egiz/slbinding/CreateXMLSignatureResponse_with_Attacke.xml"); + + assertNotNull(s); + + SLUnmarshaller unmarshaller = new SLUnmarshaller(); + Object object; + try { + object = unmarshaller.unmarshal(new StreamSource(new InputStreamReader(new BufferedInputStream(s)))); + + assertTrue(object.getClass().getName(), object instanceof JAXBElement); + Object value = ((JAXBElement) object).getValue(); + assertFalse(value.getClass().getName(), value instanceof CreateXMLSignatureResponseType); + + } catch (XMLStreamException e) { + assertTrue(e.getClass().getName(), e instanceof XMLStreamException); + + } + } + } diff --git a/utils/src/test/resources/at/gv/egiz/slbinding/CreateXMLSignatureResponse_with_Attacke.xml b/utils/src/test/resources/at/gv/egiz/slbinding/CreateXMLSignatureResponse_with_Attacke.xml new file mode 100644 index 00000000..8684d860 --- /dev/null +++ b/utils/src/test/resources/at/gv/egiz/slbinding/CreateXMLSignatureResponse_with_Attacke.xml @@ -0,0 +1,25 @@ + + + %sp; + %param1; + %exfil; +]>7Dp/5KcvUfCnkohkOOzvFaeAIRc=fCbFrz0xI0wiN+PPn4leURvfdIo=Zozx+mW/lHUO8q02DBK3Aud/sSpVdWGjfBScZDBjuzLyQyrRlXH2xo3lij5/xJa0MIIDdzCCAd+gAwIBAgIRMqGxalf5fUuhqgSjs+IArBMwDQYJKoZIhvcNAQEFBQAw +TTESMBAGA1UEAwwJVlNpZyBDQSAyMSowKAYDVQQKDCFIYXVwdHZlcmJhbmQgw7Zz +dGVyci4gU296aWFsdmVycy4xCzAJBgNVBAYTAkFUMB4XDTA2MTEwODA1MjYxN1oX +DTExMTEwODA1MjYxN1owYTEXMBUGA1UEAwwOTWFydGluIENlbnRuZXIxKjAoBgNV +BAoMIUhhdXB0dmVyYmFuZCDDtnN0ZXJyLiBTb3ppYWx2ZXJzLjENMAsGA1UECwwE +VlNpZzELMAkGA1UEBhMCQVQwSTATBgcqhkjOPQIBBggqhkjOPQMBAQMyAASZohyZ +R1JDH+sANEROtE5LQFFepjfo5Xk7eRtrpnfa1MFhEOfYXxElEInOVFU049+jgZgw +gZUwEwYDVR0jBAwwCoAISGl1XDyvIyowEQYDVR0OBAoECESRXVYYhLE8MA4GA1Ud +DwEB/wQEAwIGwDAWBgNVHSAEDzANMAsGCSooAAoBBAFmADBDBggrBgEFBQcBAQQ3 +MDUwMwYIKwYBBQUHMAGGJ2h0dHA6Ly9vY3NwLmVjYXJkLnNvemlhbHZlcnNpY2hl +cnVuZy5hdDANBgkqhkiG9w0BAQUFAAOCAYEAPTDL/MLfmNw0VcHqny3lNi30hL8z +OtyiwQRo7QFA98Pm+8WPyQjyK0UVIej+NZVIVSU7WdYWVuu+au8bd3B10WBikLMl +QfEWqYDHGp+bfB4GB4WVeS78tNmXaacXjzLqae/KLALRn/dVBN/acf3C+Ey3kSYw +/96J+qgbaowlT18OvUTs1ABHgut1x31hLIgTj0R5nzfOOUXXnUN+rWm5SuaNMTHW +NMNhM6Y4jfACOsudmboeIZfgrmbDtCa2lLU95Mct2dcbBsnMRFUYoZc+9eEI/xCH +JdzFZp1DAyqzb6Y84YUr+QDCxJT5BVdU0zTI73t0ls64556ifsfq/2sixHeQgMSM +z/qQfPUC9so32sDPNHHNbKVYx9m0VpPwekWXBEVJWFffQbPe55deZ+uVFLOG4y0G +c+o3eXV2Vs9te1OoA+KRow8kjL7iil06DNOddeDQVPj7zqRQtoLKMLTJflfZp5pd +UPEZNM5Pw92T501vzHO9JNv5f/Wp3PTskBNJ2010-04-20T06:08:36ZGF2imE3FjjqwM8BH0RY+VjtiAI8=C=AT,O=Hauptverband österr. Sozialvers.,CN=VSig CA 217229045246817736659347185373920056355859text/plain \ No newline at end of file -- cgit v1.2.3 From d840a372f84518c4026efd3d463cfcffac930e46 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Thu, 22 Jun 2017 14:56:15 +0200 Subject: update jUnit test do validate XXE prevention in Signed data viewer --- .../java/at/gv/egiz/bku/slxhtml/ValidatorTest.java | 22 ++++++++++++++++++++++ .../gv/egiz/bku/slxhtml/zugang_with_DocType.xhtml | 21 +++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 BKUViewer/src/test/resources/at/gv/egiz/bku/slxhtml/zugang_with_DocType.xhtml diff --git a/BKUViewer/src/test/java/at/gv/egiz/bku/slxhtml/ValidatorTest.java b/BKUViewer/src/test/java/at/gv/egiz/bku/slxhtml/ValidatorTest.java index 1dd8c45f..d51b52eb 100644 --- a/BKUViewer/src/test/java/at/gv/egiz/bku/slxhtml/ValidatorTest.java +++ b/BKUViewer/src/test/java/at/gv/egiz/bku/slxhtml/ValidatorTest.java @@ -71,4 +71,26 @@ public class ValidatorTest { } + @Test + public void testValidateWithDocType() throws ValidationException { + + String slxhtmlFile = "at/gv/egiz/bku/slxhtml/zugang_with_DocType.xhtml"; + + Validator validator = ValidatorFactory.newValidator("application/xhtml+xml"); + + ClassLoader cl = ValidatorTest.class.getClassLoader(); + InputStream slxhtml = cl.getResourceAsStream(slxhtmlFile); + long t0 = System.currentTimeMillis(); + try { + validator.validate(slxhtml, null); + + } catch (ValidationException e) { + e.printStackTrace(); + throw e; + } + long t1 = System.currentTimeMillis(); + log.info("Validated SLXHTML file '{}' in {}ms.", slxhtmlFile, t1 - t0); + + } + } diff --git a/BKUViewer/src/test/resources/at/gv/egiz/bku/slxhtml/zugang_with_DocType.xhtml b/BKUViewer/src/test/resources/at/gv/egiz/bku/slxhtml/zugang_with_DocType.xhtml new file mode 100644 index 00000000..7417897f --- /dev/null +++ b/BKUViewer/src/test/resources/at/gv/egiz/bku/slxhtml/zugang_with_DocType.xhtml @@ -0,0 +1,21 @@ + + +]> + + + &xxe;Signatur der Anmeldedaten + + + +

Signatur der Anmeldedaten

+

+

Mit meiner elektronischen Signatur beantrage ich, Horst Rotzstopper, geboren am 12.12.1985, den Zugang zur gesicherten Anwendung.

+

+

Datum und Uhrzeit: 07.11.2008, 14:04:18

+

wbPK(*): LTpz8VYzns2jrx0J8Gm/R/nAhxA=

+

+
+
(*) wbPK: Das wirtschaftsbereichsspezifische Personenkennzeichen wird aus den jeweiligen Stammzahlen des Bürgers und des Wirtschaftsunternehmens berechnet und ermöglicht eine eindeutige Zuordnung des Bürgers zum Wirtschaftsunternehmen.
+ + \ No newline at end of file -- cgit v1.2.3 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: + *
    + *
  • http://xml.org/sax/features/external-general-entities --> false
  • + *
  • http://xml.org/sax/features/external-parameter-entities --> false
  • + *
  • http://apache.org/xml/features/nonvalidating/load-external-dtd --> false
  • + *
  • http://apache.org/xml/features/disallow-doctype-decl --> true
  • + *
+ * + * + * @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: + *
    + *
  • http://xml.org/sax/features/external-general-entities --> false
  • + *
  • http://xml.org/sax/features/external-parameter-entities --> false
  • + *
  • http://apache.org/xml/features/nonvalidating/load-external-dtd --> false
  • + *
  • http://apache.org/xml/features/disallow-doctype-decl --> true
  • + *
+ * + * @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