Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more
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;
Expand All @@ -30,6 +31,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more
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;

/**
Expand Down Expand Up @@ -152,10 +154,14 @@ public void extractAttachments(File dir) throws IOException {
}

// 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,24 @@ Licensed to the Apache Software Foundation (ASF) under one or more

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 {
Expand Down Expand Up @@ -81,4 +89,43 @@ void testExtractMessageBody_File() throws IOException {
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);
}
}