/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.security.xml; import com.liferay.portal.kernel.test.ReflectionTestUtil; import com.liferay.portal.kernel.test.rule.NewEnv; import com.liferay.portal.kernel.test.rule.NewEnv.JVMArgsLine; import com.liferay.portal.kernel.test.rule.NewEnvTestRule; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.util.PropsValues; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; import java.net.ConnectException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.stream.XMLEventReader; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.xml.sax.InputSource; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; /** * @author Tomas Polesovsky */ @JVMArgsLine("-Dattached=true -Xmx5m") @NewEnv(type = NewEnv.Type.JVM) public class SecureXMLFactoryProviderImplTest { @Before public void setUp() throws Exception { _secureXMLFactoryProvider = new SecureXMLFactoryProviderImpl(); _xmlBombBillionLaughsXML = readDependency( "xml-bomb-billion-laughs.xml"); _xmlBombQuadraticBlowupXML = readDependency( "xml-bomb-quadratic-blowup.xml"); _xxeGeneralEntitiesXML1 = readDependency("xxe-general-entities-1.xml"); _xxeGeneralEntitiesXML2 = readDependency("xxe-general-entities-2.xml"); _xxeParameterEntitiesXML1 = readDependency( "xxe-parameter-entities-1.xml"); _xxeParameterEntitiesXML2 = readDependency( "xxe-parameter-entities-2.xml"); } @Test public void testNewDocumentBuilderFactory() throws Throwable { XMLSecurityTest documentBuilderTest = new XMLSecurityTest() { @Override public void run(String xml) throws Exception { DocumentBuilderFactory documentBuilderFactory = _secureXMLFactoryProvider.newDocumentBuilderFactory(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); documentBuilder.parse(new ByteArrayInputStream(xml.getBytes())); } }; runXMLSecurityTest( documentBuilderTest, _xmlBombBillionLaughsXML, OutOfMemoryError.class, "Billion Laughs XML attack does not work.", SAXParseException.class, "Vulnerable to Billion Laughs XML attack."); runXMLSecurityTest( documentBuilderTest, _xmlBombQuadraticBlowupXML, OutOfMemoryError.class, "Quadratic Blowup XML attack does not work.", SAXParseException.class, "Vulnerable to Quadratic Blowup XML attack."); runXMLSecurityTest( documentBuilderTest, _xxeGeneralEntitiesXML1, ConnectException.class, "General Entities XXE attack using SYSTEM entity does not work.", SAXParseException.class, "Vulnerable to General Entities XXE attack using SYSTEM entity."); runXMLSecurityTest( documentBuilderTest, _xxeGeneralEntitiesXML2, ConnectException.class, "General Entities XXE attack using PUBLIC entity does not work.", SAXParseException.class, "Vulnerable to General Entities XXE attack using PUBLIC entity."); runXMLSecurityTest( documentBuilderTest, _xxeParameterEntitiesXML1, ConnectException.class, "Parameter Entities XXE using SYSTEM entity does not work.", SAXParseException.class, "Vulnerable to Parameter Entities XXE using SYSTEM entity."); runXMLSecurityTest( documentBuilderTest, _xxeParameterEntitiesXML2, ConnectException.class, "Parameter Entities XXE attack using PUBLIC entity does not work.", SAXParseException.class, "Vulnerable to Parameter Entities XXE attack using PUBLIC entity."); } @Test public void testNewXMLInputFactory() throws Throwable { XMLSecurityTest xmlInputFactoryTest = new XMLSecurityTest() { @Override public void run(String xml) throws Exception { XMLEventReader xmlEventReader = _secureXMLFactoryProvider.newXMLInputFactory(). createXMLEventReader(new StringReader(xml)); while (xmlEventReader.hasNext()) { xmlEventReader.next(); } } }; runXMLSecurityTest( xmlInputFactoryTest, _xmlBombBillionLaughsXML, OutOfMemoryError.class, "Billion Laughs XML attack does not work.", null, "Vulnerable to Billion Laughs XML attack."); runXMLSecurityTest( xmlInputFactoryTest, _xmlBombQuadraticBlowupXML, OutOfMemoryError.class, "Quadratic Blowup XML attack does not work.", null, "Vulnerable to Quadratic Blowup XML attack."); runXMLSecurityTest( xmlInputFactoryTest, _xxeGeneralEntitiesXML1, ConnectException.class, "General Entities XXE attack using SYSTEM entity does not work.", null, "Vulnerable to General Entities XXE attack using SYSTEM entity."); runXMLSecurityTest( xmlInputFactoryTest, _xxeGeneralEntitiesXML2, ConnectException.class, "General Entities XXE attack using PUBLIC entity does not work.", null, "Vulnerable to General Entities XXE attack using PUBLIC entity."); runXMLSecurityTest( xmlInputFactoryTest, _xxeParameterEntitiesXML1, ConnectException.class, "Parameter Entities XXE using SYSTEM entity does not work.", null, "Vulnerable to Parameter Entities XXE using SYSTEM entity."); runXMLSecurityTest( xmlInputFactoryTest, _xxeParameterEntitiesXML2, ConnectException.class, "Parameter Entities XXE attack using PUBLIC entity does not work.", null, "Vulnerable to Parameter Entities XXE attack using PUBLIC entity."); } @Test public void testNewXMLReader() throws Throwable { XMLSecurityTest xmlReaderTest = new XMLSecurityTest() { @Override public void run(String xml) throws Exception { XMLReader xmlReader = _secureXMLFactoryProvider.newXMLReader(); if (xmlReader instanceof StripDoctypeXMLReader) { xmlReader = ((StripDoctypeXMLReader)xmlReader).getXmlReader(); } xmlReader.setContentHandler( new DefaultHandler() { @Override public void characters( char[] ch, int start, int length) { _contentLenght += length; if (_contentLenght > (1024 * 1024 * 10)) { throw new OutOfMemoryError(); } } private int _contentLenght; }); xmlReader.parse(new InputSource(new StringReader(xml))); } }; runXMLSecurityTest( xmlReaderTest, _xmlBombBillionLaughsXML, OutOfMemoryError.class, "Billion Laughs XML attack does not work.", SAXParseException.class, "Vulnerable to Billion Laughs XML attack."); runXMLSecurityTest( xmlReaderTest, _xmlBombQuadraticBlowupXML, OutOfMemoryError.class, "Quadratic Blowup XML attack does not work.", SAXParseException.class, "Vulnerable to Quadratic Blowup XML attack."); runXMLSecurityTest( xmlReaderTest, _xxeGeneralEntitiesXML1, ConnectException.class, "General Entities XXE attack using SYSTEM entity does not work.", SAXParseException.class, "Vulnerable to General Entities XXE attack using SYSTEM entity."); runXMLSecurityTest( xmlReaderTest, _xxeGeneralEntitiesXML2, ConnectException.class, "General Entities XXE attack using PUBLIC entity does not work.", SAXParseException.class, "Vulnerable to General Entities XXE attack using PUBLIC entity."); runXMLSecurityTest( xmlReaderTest, _xxeParameterEntitiesXML1, ConnectException.class, "Parameter Entities XXE using SYSTEM entity does not work.", SAXParseException.class, "Vulnerable to Parameter Entities XXE using SYSTEM entity."); runXMLSecurityTest( xmlReaderTest, _xxeParameterEntitiesXML2, ConnectException.class, "Parameter Entities XXE attack using PUBLIC entity does not work.", SAXParseException.class, "Vulnerable to Parameter Entities XXE attack using PUBLIC entity."); } @Rule public final NewEnvTestRule newEnvTestRule = NewEnvTestRule.INSTANCE; protected static String readDependency(String name) throws IOException { return StringUtil.read( SecureXMLFactoryProviderImplTest.class.getResourceAsStream( "dependencies/" + name)); } protected void runXMLSecurityTest( XMLSecurityTest xmlSecurityTest, String xml, Class<? extends Throwable> expectedException, String failMessage) throws Throwable { try { xmlSecurityTest.run(xml); if (expectedException != null) { Assert.fail(failMessage); } } catch (Throwable t) { if (expectedException == null) { throw t; } Throwable cause = t; while (cause.getCause() != null) { cause = cause.getCause(); } Class<?> causeClass = cause.getClass(); if (!causeClass.isAssignableFrom(expectedException)) { throw t; } } } protected void runXMLSecurityTest( XMLSecurityTest xmlSecurityTest, String xml, Class<? extends Throwable> disableXMLSecurityExpectedException, String disableXMLSecurityFailMessage, Class<? extends Throwable> enableXMLSecurityExpectedException, String enableXMLSecurityFailMessage) throws Throwable { boolean xmlSecurityEnabled = ReflectionTestUtil.getFieldValue( PropsValues.class, "XML_SECURITY_ENABLED"); try { ReflectionTestUtil.setFieldValue( PropsValues.class, "XML_SECURITY_ENABLED", false); runXMLSecurityTest( xmlSecurityTest, xml, disableXMLSecurityExpectedException, disableXMLSecurityFailMessage); ReflectionTestUtil.setFieldValue( PropsValues.class, "XML_SECURITY_ENABLED", true); runXMLSecurityTest( xmlSecurityTest, xml, enableXMLSecurityExpectedException, enableXMLSecurityFailMessage); } finally { ReflectionTestUtil.setFieldValue( PropsValues.class, "XML_SECURITY_ENABLED", xmlSecurityEnabled); } } private static String _xmlBombBillionLaughsXML; private static String _xmlBombQuadraticBlowupXML; private static String _xxeGeneralEntitiesXML1; private static String _xxeGeneralEntitiesXML2; private static String _xxeParameterEntitiesXML1; private static String _xxeParameterEntitiesXML2; private SecureXMLFactoryProviderImpl _secureXMLFactoryProvider; private abstract class XMLSecurityTest { public abstract void run(String xml) throws Exception; } }