From f6812f151878c48fc62fb0f40b8926de5a93a7bd Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Mon, 27 Jul 2026 13:04:19 +0000
Subject: [PATCH] Fix java/zipslip CodeQL alerts by validating archive entry paths (#761)

---
 opendj-server-legacy/src/test/java/org/opends/server/util/TestStaticUtils.java |   71 +++++++++++++++++++++++++++++++++++
 1 files changed, 70 insertions(+), 1 deletions(-)

diff --git a/opendj-server-legacy/src/test/java/org/opends/server/util/TestStaticUtils.java b/opendj-server-legacy/src/test/java/org/opends/server/util/TestStaticUtils.java
index d64edc6..bff1ef4 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/util/TestStaticUtils.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/util/TestStaticUtils.java
@@ -13,7 +13,7 @@
  *
  * Copyright 2006-2009 Sun Microsystems, Inc.
  * Portions Copyright 2013-2016 ForgeRock AS.
- * Portions Copyrighted 2026 3A Systems, LLC.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.util;
 
@@ -22,6 +22,7 @@
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
@@ -34,8 +35,11 @@
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
 import java.text.ParseException;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 import org.forgerock.opendj.ldap.ByteString;
 import org.opends.server.TestCaseUtils;
@@ -1100,4 +1104,69 @@
       Assert.assertTrue(StaticUtils.isSelfConnection(socket));
     }
   }
+
+  /**
+   * {@link StaticUtils#extractZipArchive} must reject archive entries which
+   * resolve outside of the target directory (zip slip) and must not write any
+   * file outside of it.
+   */
+  @Test
+  public void testExtractZipArchiveRejectsZipSlipEntries() throws Exception
+  {
+    File tempDir = TestCaseUtils.createTemporaryDirectory("zipslip");
+    File zipFile = new File(tempDir, "malicious.zip");
+    try (ZipOutputStream zipOutput = new ZipOutputStream(new FileOutputStream(zipFile)))
+    {
+      zipOutput.putNextEntry(new ZipEntry("../evil.txt"));
+      zipOutput.write(StaticUtils.getBytes("evil"));
+      zipOutput.closeEntry();
+    }
+    File targetDirectory = new File(tempDir, "target");
+    Assert.assertTrue(targetDirectory.mkdirs());
+
+    try
+    {
+      StaticUtils.extractZipArchive(zipFile, targetDirectory,
+          Collections.<String> emptyList(), Collections.<String> emptyList());
+      Assert.fail("Expected extractZipArchive to reject a zip entry escaping the target directory");
+    }
+    catch (IOException expected)
+    {
+      // expected
+    }
+    Assert.assertFalse(new File(tempDir, "evil.txt").exists());
+  }
+
+  /**
+   * {@link StaticUtils#extractZipArchive} must accept a relative target
+   * directory: EmbeddedDirectoryServer passes the parent of a relative server
+   * root, e.g. {@code .} for a server root of {@code ./opendj}.
+   */
+  @Test
+  public void testExtractZipArchiveSupportsRelativeTargetDirectory() throws Exception
+  {
+    File tempDir = TestCaseUtils.createTemporaryDirectory("zipslip-relative");
+    File zipFile = new File(tempDir, "archive.zip");
+    String rootName = "zipslip-relative-extract-" + System.nanoTime();
+    try (ZipOutputStream zipOutput = new ZipOutputStream(new FileOutputStream(zipFile)))
+    {
+      zipOutput.putNextEntry(new ZipEntry(rootName + "/"));
+      zipOutput.closeEntry();
+      zipOutput.putNextEntry(new ZipEntry(rootName + "/README"));
+      zipOutput.write(StaticUtils.getBytes("content"));
+      zipOutput.closeEntry();
+    }
+
+    File extractedRoot = new File(rootName);
+    try
+    {
+      StaticUtils.extractZipArchive(zipFile, new File("."),
+          Collections.<String> emptyList(), Collections.<String> emptyList());
+      Assert.assertTrue(new File(extractedRoot, "README").exists());
+    }
+    finally
+    {
+      StaticUtils.recursiveDelete(extractedRoot);
+    }
+  }
 }

--
Gitblit v1.10.0