Skip to content

Commit 7c36b23

Browse files
Merge pull request #269 from AikidoSec/unsafe-patch-checker-update-current-dir-mention
Normalizes path to prevent path traversal with "/./"
2 parents 752e550 + 0cd2d7d commit 7c36b23

2 files changed

Lines changed: 52 additions & 7 deletions

File tree

agent_api/src/main/java/dev/aikido/agent_api/vulnerabilities/path_traversal/UnsafePathChecker.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
import java.util.ArrayList;
44
import java.util.Arrays;
55
import java.util.List;
6+
import java.util.regex.Pattern;
67

78
public final class UnsafePathChecker {
89
private UnsafePathChecker() {}
10+
11+
private static final Pattern CURRENT_DIR_PATTERN = Pattern.compile("/(\\./)+");
12+
913
private static final List<String> LINUX_ROOT_FOLDERS = Arrays.asList(
1014
"/bin/",
1115
"/boot/",
@@ -33,7 +37,7 @@ private UnsafePathChecker() {}
3337
);
3438

3539
public static boolean startsWithUnsafePath(String filePathRaw) {
36-
String filePath = ensureOneLeadingSlash(filePathRaw.toLowerCase());
40+
String filePath = normalizePath(filePathRaw.toLowerCase());
3741

3842
List<String> dangerousStartsList = new ArrayList<>(DANGEROUS_PATH_STARTS);
3943
dangerousStartsList.addAll(LINUX_ROOT_FOLDERS);
@@ -47,15 +51,24 @@ public static boolean startsWithUnsafePath(String filePathRaw) {
4751
}
4852

4953
public static boolean startsWithUnsafePath(String filePathRaw, String userInputRaw) {
50-
String filePath = ensureOneLeadingSlash(filePathRaw.toLowerCase());
51-
String userInput = ensureOneLeadingSlash(userInputRaw.toLowerCase());
54+
String filePath = normalizePath(filePathRaw.toLowerCase());
55+
String userInput = normalizePath(userInputRaw.toLowerCase());
5256
return startsWithUnsafePath(filePath) && filePath.startsWith(userInput);
5357
}
5458

55-
private static String ensureOneLeadingSlash(String path) {
56-
if (path.startsWith("/")) {
57-
return "/" + path.replaceAll("^/+", "");
59+
/**
60+
* Normalizes a path by removing /./ and removing consecutive slashes
61+
*/
62+
private static String normalizePath(String path) {
63+
if (path == null || path.isEmpty()) {
64+
return path;
5865
}
59-
return path;
66+
67+
// Matches /./ or /././ or /./././ etc. (one or more ./ sequences after a /)
68+
String normalized = CURRENT_DIR_PATTERN.matcher(path).replaceAll("/");
69+
70+
// Merge consecutive slashes since these don't change where you are in the path.
71+
normalized = normalized.replaceAll("/+", "/");
72+
return normalized;
6073
}
6174
}

agent_api/src/test/java/vulnerabilities/path_traversal/UnsafePathCheckerTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,36 @@ public void testMultipleSlashes() {
4848
assertTrue(UnsafePathChecker.startsWithUnsafePath("///etc/passwd"));
4949
assertFalse(UnsafePathChecker.startsWithUnsafePath("etc/passwd///../test.txt"));
5050
}
51+
52+
@Test
53+
public void testCurrentDirectoryReferences() {
54+
// /./ should be normalized to /
55+
assertTrue(UnsafePathChecker.startsWithUnsafePath("/./etc/passwd", "/./etc"));
56+
assertTrue(UnsafePathChecker.startsWithUnsafePath("/etc/./passwd", "/etc"));
57+
assertTrue(UnsafePathChecker.startsWithUnsafePath("/etc/./passwd", "/etc/./"));
58+
59+
assertTrue(UnsafePathChecker.startsWithUnsafePath("/./etc/passwd"));
60+
assertTrue(UnsafePathChecker.startsWithUnsafePath("/etc/./passwd"));
61+
assertTrue(UnsafePathChecker.startsWithUnsafePath("/./etc/./passwd"));
62+
63+
// Multiple /./ sequences
64+
assertTrue(UnsafePathChecker.startsWithUnsafePath("/././etc/passwd", "/././etc"));
65+
assertTrue(UnsafePathChecker.startsWithUnsafePath("/etc/././passwd"));
66+
}
67+
68+
@Test
69+
public void testPathNormalization() {
70+
// Paths with multiple slashes and /./ should be normalized and detected
71+
assertTrue(UnsafePathChecker.startsWithUnsafePath("//etc//passwd", "/etc"));
72+
assertTrue(UnsafePathChecker.startsWithUnsafePath("/./etc/./passwd", "/etc"));
73+
assertTrue(UnsafePathChecker.startsWithUnsafePath("/././etc/passwd", "/etc"));
74+
75+
// Paths without leading slash are not unsafe
76+
assertFalse(UnsafePathChecker.startsWithUnsafePath("etc/passwd", "etc"));
77+
assertFalse(UnsafePathChecker.startsWithUnsafePath("", ""));
78+
79+
// Combined slashes and dot: ///.///etc/passwd should normalize to /etc/passwd
80+
assertTrue(UnsafePathChecker.startsWithUnsafePath("///.///etc/passwd", "///.///etc"));
81+
assertTrue(UnsafePathChecker.startsWithUnsafePath("///.///etc/passwd"));
82+
}
5183
}

0 commit comments

Comments
 (0)