From 237ac01acc29a32cf566bb37cda1a4e5aede3c81 Mon Sep 17 00:00:00 2001 From: digi-scrypt Date: Fri, 5 Jun 2026 20:40:56 +0530 Subject: [PATCH] contain assay file reads within the isa-tab directory --- .../tika/parser/isatab/ISArchiveParser.java | 19 ++++++++-- .../parser/isatab/ISArchiveParserTest.java | 36 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/isatab/ISArchiveParser.java b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/isatab/ISArchiveParser.java index 85baa72d03b..2ca0c3ae136 100644 --- a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/isatab/ISArchiveParser.java +++ b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/isatab/ISArchiveParser.java @@ -138,11 +138,26 @@ private void parseAssay(XHTMLContentHandler xhtml, Metadata metadata, ParseConte xhtml.startElement("div"); xhtml.element("h3", "ASSAY " + assayFileName); // location starts with "/C:" on windows, can't use Paths.get() - try (InputStream stream = TikaInputStream.get(new File(this.location + assayFileName).toPath())) - { + File assayFile = resolveWithinLocation(assayFileName); + try (InputStream stream = TikaInputStream.get(assayFile.toPath())) { ISATabUtils.parseAssay(stream, xhtml, metadata, context); } xhtml.endElement("div"); } } + + /** + * Resolves an assay file name taken from the (untrusted) investigation file against + * the ISA-Tab location, rejecting names that escape that directory via "../" or a + * symlink. + */ + private File resolveWithinLocation(String fileName) throws IOException, TikaException { + File candidate = new File(this.location + fileName); + String locationCanonical = new File(this.location).getCanonicalPath(); + if (!candidate.getCanonicalPath().startsWith(locationCanonical + File.separator)) { + throw new TikaException( + "assay file name '" + fileName + "' resolves outside the ISA-Tab directory"); + } + return candidate; + } } diff --git a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/isatab/ISArchiveParserTest.java b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/isatab/ISArchiveParserTest.java index 0ca03259026..d447bc3c53c 100644 --- a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/isatab/ISArchiveParserTest.java +++ b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/isatab/ISArchiveParserTest.java @@ -17,11 +17,19 @@ package org.apache.tika.parser.isatab; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.xml.sax.ContentHandler; import org.apache.tika.TikaTest; +import org.apache.tika.exception.TikaException; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.TikaCoreProperties; @@ -68,4 +76,32 @@ public void testParseArchive() throws Exception { assertEquals("Stephen", metadata.get("Investigation Person First Name"), "Invalid Investigation Person First Name"); } + + @Test + public void testAssayPathTraversal(@TempDir Path root) throws Exception { + Path secret = root.resolve("secret.txt"); + Files.write(secret, "SUPERSECRETTOKEN12345".getBytes(StandardCharsets.UTF_8)); + + Path isaDir = Files.createDirectory(root.resolve("isa")); + Files.write(isaDir.resolve("i_test.txt"), + ("STUDY\n" + + "Study File Name\t\"s_test.txt\"\n" + + "Study Assay File Name\t\"../secret.txt\"\n") + .getBytes(StandardCharsets.UTF_8)); + Path study = isaDir.resolve("s_test.txt"); + Files.write(study, "\"Source Name\"\n\"culture1\"\n".getBytes(StandardCharsets.UTF_8)); + + Parser parser = new ISArchiveParser(isaDir.toString()); + ContentHandler handler = new BodyContentHandler(); + Metadata metadata = new Metadata(); + metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, "s_test.txt"); + ParseContext context = new ParseContext(); + + try (TikaInputStream tis = TikaInputStream.get(study)) { + assertThrows(TikaException.class, + () -> parser.parse(tis, handler, metadata, context)); + } + assertFalse(handler.toString().contains("SUPERSECRETTOKEN12345"), + "assay reader escaped the ISA-Tab directory"); + } }