Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions core/src/main/java/org/eclipse/angus/mail/handlers/text_xml.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
import jakarta.mail.internet.ContentType;
import jakarta.mail.internet.ParseException;

import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
Expand Down Expand Up @@ -86,8 +88,16 @@ public void writeTo(Object obj, String mimeType, OutputStream os)
}

try {
Transformer transformer =
TransformerFactory.newInstance().newTransformer();
TransformerFactory tf = TransformerFactory.newInstance();
// a DataSource/Source can carry attacker controlled XML, so don't
// let the identity transform resolve external entities or DTDs
try {
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (TransformerConfigurationException ignored) {
}
setAttribute(tf, XMLConstants.ACCESS_EXTERNAL_DTD, "");
setAttribute(tf, XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
Transformer transformer = tf.newTransformer();
StreamResult result = new StreamResult(os);
if (obj instanceof DataSource) {
// Streaming transform applies only to
Expand All @@ -107,6 +117,15 @@ public void writeTo(Object obj, String mimeType, OutputStream os)
}
}

private static void setAttribute(TransformerFactory tf, String name,
Object value) {
try {
tf.setAttribute(name, value);
} catch (IllegalArgumentException ignored) {
// attribute not supported by this implementation
}
}

private boolean isXmlType(String type) {
try {
ContentType ct = new ContentType(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
Expand Down Expand Up @@ -104,6 +108,32 @@ public void testSourceToStream() throws Exception {
assertTrue(sc.contains(xml.trim()));
}

// an external entity in the source must not be resolved into the output
@Test
public void testExternalEntityNotResolved() throws Exception {
File secret = File.createTempFile("textxml", ".txt");
secret.deleteOnExit();
try (Writer w = new FileWriter(secret)) {
w.write("TOPSECRET");
}
String doc = "<?xml version=\"1.0\"?>\n" +
"<!DOCTYPE r [ <!ENTITY x SYSTEM \"" +
secret.toURI() + "\"> ]>\n<r>&x;</r>";
DataContentHandler dch = new text_xml();
DataSource ds = new ByteArrayDataSource(
doc.getBytes(StandardCharsets.US_ASCII), "text/xml");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
dch.writeTo(ds, "text/xml", bos);
} catch (IOException expected) {
// entity resolution disabled, transform refuses the document
return;
}
String out = new String(bos.toByteArray(), StandardCharsets.US_ASCII);
assertFalse("external entity was resolved: " + out,
out.contains("TOPSECRET"));
}

/**
* Read a stream into a String.
*/
Expand Down