This is an automated email from the ASF dual-hosted git repository.
pjfanning pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/poi.git
The following commit(s) were added to refs/heads/trunk by this push:
new d43c238a83 Reject path traversal filenames during HMEF attachment
extraction (#1066)
d43c238a83 is described below
commit d43c238a8368b60bdc5b1384fa0fba1ac16c62fe
Author: jmestwa-coder <[email protected]>
AuthorDate: Sat May 16 01:53:58 2026 +0530
Reject path traversal filenames during HMEF attachment extraction (#1066)
---
.../poi/hmef/extractor/HMEFContentsExtractor.java | 8 +++-
.../hmef/extractor/TestHMEFContentsExtractor.java | 47 ++++++++++++++++++++++
2 files changed, 54 insertions(+), 1 deletion(-)
diff --git
a/poi-scratchpad/src/main/java/org/apache/poi/hmef/extractor/HMEFContentsExtractor.java
b/poi-scratchpad/src/main/java/org/apache/poi/hmef/extractor/HMEFContentsExtractor.java
index 8e01252c64..65e7612da8 100644
---
a/poi-scratchpad/src/main/java/org/apache/poi/hmef/extractor/HMEFContentsExtractor.java
+++
b/poi-scratchpad/src/main/java/org/apache/poi/hmef/extractor/HMEFContentsExtractor.java
@@ -22,6 +22,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
+import java.nio.file.Path;
import org.apache.poi.hmef.Attachment;
import org.apache.poi.hmef.HMEFMessage;
@@ -30,6 +31,7 @@ import org.apache.poi.hmef.attribute.MAPIRtfAttribute;
import org.apache.poi.hmef.attribute.MAPIStringAttribute;
import org.apache.poi.hsmf.datatypes.MAPIProperty;
import org.apache.poi.hsmf.datatypes.Types;
+import org.apache.poi.util.IOUtils;
import org.apache.poi.util.StringUtil;
/**
@@ -152,10 +154,14 @@ public final class HMEFContentsExtractor {
}
// Save it
- File file = new File(dir, filename);
+ File file = getOutputFile(dir, filename);
try (OutputStream fout = Files.newOutputStream(file.toPath())) {
fout.write(att.getContents());
}
}
}
+
+ private static File getOutputFile(File dir, String filename) throws
IOException {
+ return IOUtils.newFile(dir, filename);
+ }
}
diff --git
a/poi-scratchpad/src/test/java/org/apache/poi/hmef/extractor/TestHMEFContentsExtractor.java
b/poi-scratchpad/src/test/java/org/apache/poi/hmef/extractor/TestHMEFContentsExtractor.java
index 9f41088668..8a82a041bd 100644
---
a/poi-scratchpad/src/test/java/org/apache/poi/hmef/extractor/TestHMEFContentsExtractor.java
+++
b/poi-scratchpad/src/test/java/org/apache/poi/hmef/extractor/TestHMEFContentsExtractor.java
@@ -19,16 +19,24 @@ package org.apache.poi.hmef.extractor;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.poi.POIDataSamples;
+import org.apache.poi.hmef.HMEFMessage;
+import org.apache.poi.hmef.attribute.TNEFProperty;
+import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.TempFile;
+import org.apache.poi.util.StringUtil;
import org.junit.jupiter.api.Test;
public class TestHMEFContentsExtractor {
@@ -81,4 +89,43 @@ public class TestHMEFContentsExtractor {
extractor.extractMessageBody(rtf);
assertTrue(rtf.length() > 0, "RTF message body is empty");
}
+
+ @Test
+ void testExtractAttachmentsRejectsPathTraversal() throws IOException {
+ File outputDirectory =
TempFile.createTempDirectory("hmef-attachments");
+ File escapedFile = new File(
+ outputDirectory.getParentFile(), outputDirectory.getName() +
"-escaped.txt");
+ if (escapedFile.exists()) {
+ assertTrue(escapedFile.delete());
+ }
+ assertFalse(escapedFile.exists());
+
+ HMEFContentsExtractor extractor = new HMEFContentsExtractor(
+ new HMEFMessage(new
ByteArrayInputStream(createTnefWithAttachment(
+ ".." + File.separator + escapedFile.getName(),
"contents"))));
+
+ assertThrows(IOException.class, () ->
extractor.extractAttachments(outputDirectory));
+ assertFalse(escapedFile.exists());
+ assertTrue(outputDirectory.delete());
+ }
+
+ private static byte[] createTnefWithAttachment(String filename, String
contents) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ LittleEndian.putInt(HMEFMessage.HEADER_SIGNATURE, out);
+ LittleEndian.putUShort(0, out);
+ writeAttribute(out, TNEFProperty.ID_ATTACHRENDERDATA.id,
TNEFProperty.TYPE_BYTE, new byte[0]);
+ writeAttribute(out, TNEFProperty.ID_ATTACHTITLE.id,
TNEFProperty.TYPE_STRING,
+ (filename + "\0").getBytes(StringUtil.UTF8));
+ writeAttribute(out, TNEFProperty.ID_ATTACHDATA.id,
TNEFProperty.TYPE_BYTE, contents.getBytes(StringUtil.UTF8));
+ return out.toByteArray();
+ }
+
+ private static void writeAttribute(ByteArrayOutputStream out, int id, int
type, byte[] data) throws IOException {
+ out.write(TNEFProperty.LEVEL_ATTACHMENT);
+ LittleEndian.putUShort(id, out);
+ LittleEndian.putUShort(type, out);
+ LittleEndian.putInt(data.length, out);
+ out.write(data, 0, data.length);
+ LittleEndian.putUShort(0, out);
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]