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]

Reply via email to