diff --git a/.gitignore b/.gitignore
index eb5a316..c507849 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
target
+.idea
diff --git a/README.md b/README.md
index 470ddf5..a8c6d70 100644
--- a/README.md
+++ b/README.md
@@ -88,14 +88,18 @@ old-hits/log4j-core-2.0-beta2.jar contains Log4J-2.x <= 2.0-beta8 _POTENTIALLY
```
java -jar log4j-detector-2021.12.29.jar
-Usage: java -jar log4j-detector-2021.12.29.jar [--verbose] [--json] [--stdin] [--exclude=X] [paths to scan...]
+Usage: java -jar log4j-detector-2021.12.29.jar [--verbose] [--json] [--stdin] [--ignoreSymLinks] [--ignoreReparsePoints] [--exclude=X] [paths to
+scan...]
- --json - Output STDOUT results in JSON. (Errors/warning still emitted to STDERR)
- --stdin - Read STDIN for paths to explore (one path per line)
- --exclude=X - Where X is a JSON list containing full paths to exclude. Must be valid JSON.
+ --json - Output STDOUT results in JSON. (Errors/warning still emitted to STDERR)
+ --stdin - Read STDIN for paths to explore (one path per line)
+ --exclude=X - Where X is a JSON list containing full paths to exclude. Must be valid JSON.
- Example: --exclude='["/dev", "/media", "Z:\TEMP"]'
+ Example: --exclude='["/dev", "/media", "Z:\TEMP"]'
+ --ignoreSymLinks - Use this to ignore symlinks. If not specified, symlinks are followed.
+ --ignoreReparsePoints - Use this only on Windows to ignore Reparse Points. If not specified, Reparse Points are followed. This option is
+ experimental and only legal if --ignoreSymLinks was set.
Exit codes: 0 = No vulnerable Log4J versions found.
1 = At least one legacy Log4J 1.x version found.
2 = At least one vulnerable Log4J version found.
diff --git a/pom.xml b/pom.xml
index dac724d..d5dcd3d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,8 +16,8 @@
UTF-8
- 1.6
- 1.6
+ 1.7
+ 1.7
diff --git a/src/main/java/com/mergebase/log4j/Log4JDetector.java b/src/main/java/com/mergebase/log4j/Log4JDetector.java
index 764e149..6e8e111 100644
--- a/src/main/java/com/mergebase/log4j/Log4JDetector.java
+++ b/src/main/java/com/mergebase/log4j/Log4JDetector.java
@@ -19,6 +19,11 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.attribute.DosFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -81,9 +86,14 @@ public class Log4JDetector {
// This occurs in "DataSourceConnectionSource.class" in 2.17.1 and friends.
private static final byte[] IS_CVE_2021_44832_SAFE = Bytes.fromString("JNDI must be enabled by setting log4j2.enableJndiJdbc=true");
+ private static final String SWITCH_IGNORE_SYMLINKS = "--ignoreSymLinks";
+ private static final String SWITCH_IGNORE_REPARSE_POINTS = "--ignoreReparsePoints";
+
private static boolean verbose = false;
private static boolean debug = false;
private static boolean json = false;
+ private static boolean ignoreSymLinks = false;
+ private static boolean ignoreReparsePoints = false;
private static Set excludes = new TreeSet();
private static boolean foundHits = false;
private static boolean foundLog4j1 = false;
@@ -92,6 +102,8 @@ public class Log4JDetector {
private static String currentPath = null;
private static boolean printFullPaths = false;
+ private static Method methodIsReparsePoint = null;
+
public static void main(String[] args) throws IOException {
currentDir = canonicalize(new File("."));
currentPath = currentDir.getPath();
@@ -132,6 +144,12 @@ public static void main(String[] args) throws IOException {
byte[] b = Bytes.streamToBytes(System.in);
String s = new String(b, Bytes.UTF_8);
stdinLines = Strings.intoLines(s);
+ } else if (SWITCH_IGNORE_SYMLINKS.equals(argOrig)) {
+ ignoreSymLinks = true;
+ it.remove();
+ } else if (SWITCH_IGNORE_REPARSE_POINTS.equals(argOrig)) {
+ ignoreReparsePoints = true;
+ it.remove();
} else {
File f;
if (argOrig.length() == 2 && ':' == argOrig.charAt(1) && Character.isLetter(argOrig.charAt(0))) {
@@ -145,30 +163,45 @@ public static void main(String[] args) throws IOException {
}
}
}
+
+ if(!ignoreSymLinks && ignoreReparsePoints) {
+ // Only the addition of ignoreReparsePoints is allowed as the implementation of Reparse Points is
+ // considered experimental; this keeps unexpected results to a minimum
+ System.err.println("Illegal option mix: " + SWITCH_IGNORE_REPARSE_POINTS + " is only legal if " + SWITCH_IGNORE_SYMLINKS + "has been " +
+ "specified as well.");
+ System.exit(105);
+ }
+
argsList.addAll(stdinLines);
if (argsList.isEmpty()) {
System.out.println();
- System.out.println("Usage: java -jar log4j-detector-2021.12.29.jar [--verbose] [--json] [--stdin] [--exclude=X] [paths to scan...]");
+ System.out.println("Usage: java -jar log4j-detector-.jar [--verbose] [--json] " +
+ "[--stdin] [--exclude=X] [--ignoreSymLinks] [--ignoreReparsePoints] [paths to scan...]");
+ System.out.println();
+ System.out.println(" --json - Output STDOUT results in JSON. (Errors/warning still emitted to STDERR)");
+ System.out.println(" --stdin - Parse STDIN for paths to explore.");
+ System.out.println(" --exclude=X - Where X is a JSON list containing full paths to exclude. Must be valid JSON.");
System.out.println();
- System.out.println(" --json - Output STDOUT results in JSON. (Errors/warning still emitted to STDERR)");
- System.out.println(" --stdin - Parse STDIN for paths to explore.");
- System.out.println(" --exclude=X - Where X is a JSON list containing full paths to exclude. Must be valid JSON.");
+ System.out.println(" Example: --exclude='[\"/dev\", \"/media\", \"Z:\\TEMP\"]' ");
System.out.println();
- System.out.println(" Example: --exclude='[\"/dev\", \"/media\", \"Z:\\TEMP\"]' ");
+ System.out.println(" --ignoreSymLinks - Use this to ignore symlinks. If not specified, symlinks are followed");
+ System.out.println(" --ignoreReparsePoints - Use this only on Windows to ignore Reparse Points. If not specified, Reparse Points are " +
+ "followed. This option is experimental and only legal if --ignoreSymLinks was set.");
System.out.println();
System.out.println("Exit codes: 0 = No vulnerable Log4J versions found.");
System.out.println(" 1 = At least one legacy Log4J 1.x version found.");
System.out.println(" 2 = At least one vulnerable Log4J 2.x version found.");
System.out.println();
- System.out.println("About - MergeBase log4j detector (version 2021.12.29)");
+ System.out.println("About - MergeBase log4j detector (version )");
System.out.println("Docs - https://github.com/mergebase/log4j-detector ");
System.out.println("(C) Copyright 2021 Mergebase Software Inc. Licensed to you via GPLv3.");
System.out.println();
System.exit(100);
}
- System.err.println("-- github.com/mergebase/log4j-detector v2021.12.29 (by mergebase.com) analyzing paths (could take a while).");
+ System.err.println("-- github.com/mergebase/log4j-detector (by mergebase.com) analyzing " +
+ "paths (could take a while).");
System.err.println("-- Note: specify the '--verbose' flag to have every file examined printed to STDERR.");
if (json) {
System.out.println("{\"hits\":[");
@@ -753,6 +786,78 @@ private static void analyze(File f) {
visited.add(crc);
}
+ if(ignoreSymLinks && Files.isSymbolicLink(f.toPath())){
+ System.err.println("-- Info: Skipping symlink [" + path + "] because --ignoreSymLinks is specified.");
+ return;
+ } else if(ignoreSymLinks && ignoreReparsePoints){
+ // ignoreReparsePoints is only legal on Windows so we can assume to be on Windows here
+ // The following is Windows specific to exclude Reparse Points as they are not considered symlinks by
+ // Files.isSymbolicLink
+ try {
+ // Code adapted from Stackoverflow: https://stackoverflow.com/a/29647840
+ DosFileAttributes attrs = Files.readAttributes(f.toPath(), DosFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
+ if(methodIsReparsePoint == null) {
+ // attrs should be an instance of sun.nio.fs.WindowsFileAttributes on Windows, which has a
+ // method isReparsePoint
+ // The WindowsFileAttributes class is part of the jdk since 1.7 and still is in all Java 11 JVMs
+ // (Oracle, Open JDK, Zulu that have been tested). It is not by default importable (seems to be
+ // a Maven constraint)
+ // Don't want to import anyway as this would break every call, not only if this code snipped is
+ // executed
+ try {
+ methodIsReparsePoint = attrs.getClass().getDeclaredMethod("isReparsePoint");
+ methodIsReparsePoint.setAccessible(true);
+ } catch ( Exception e) {
+
+ // JVM seems to not support Reparse Points - might be --illegaƶ-access=permit needs to be set
+ System.err.println("-- Problem: Cannot determine Reparse Points in current setup. You can try" +
+ " one of the following: (1) Omit --ignoreReparsePoints (2) Try another JVM (3) set " +
+ "--illegal-access=permit JVM Option (4) do further analysis.");
+
+ System.err.println("-- Java Home: <" + System.getProperty("java.home") + ">.");
+ System.err.println("-- Java Vendor: <" + System.getProperty("java.vendor") + ">.");
+ System.err.println("-- Java Version: <" + System.getProperty("java.version") + ">.");
+ System.err.println("-- OS Arch: <" + System.getProperty("os.arch") + ">.");
+ System.err.println("-- OS Name: <" + System.getProperty("os.name") + ">.");
+ System.err.println("-- OS Version: <" + System.getProperty("os.version") + ">.");
+ System.err.println("-- Security Manager: <" + System.getSecurityManager() + ">.");
+ System.err.println("-- Aborting due to Exception <" + e.getMessage () + ">, " +
+ "Stack Trace follows:");
+ e.printStackTrace();
+ System.exit(105);
+ }
+
+ }
+ if(attrs != null) {
+ boolean isReparsePoint = (boolean) methodIsReparsePoint.invoke(attrs);
+ if(isReparsePoint) {
+ // Not symlink but Reparse Point should be a Junction or some old Windows Reparse Point not
+ // considered a symlink
+ System.err.println("-- Info: Skipping Junction/ReparsePoint [" + path + "] because " +
+ "--ignoreReparsePoints is specified.");
+ return;
+ }
+ } else {
+ // Don't ignore it but give out an info
+ System.err.println("-- Info: File Attributes could not be read, so no ReparsePoint possible for <" +
+ path + ">. Continuing the scan for this path.");
+ }
+ } catch (AccessDeniedException e) {
+ // Something is generally wrong, aborting here
+ System.err.println("-- Problem: Missing permissions for path: <" + f.getPath() + ">. " +
+ "Stack Trace follows:");
+ e.printStackTrace();
+ return;
+ } catch ( Exception e) {
+ // Something is generally wrong, aborting here
+ System.err.println("-- Problem: Aborting due to unexpected Exception <" + e.getMessage () + ">, " +
+ "Stack Trace follows:");
+ e.printStackTrace();
+ System.exit(106);
+ }
+
+ }
+
if (f.isDirectory()) {
if (!f.canRead()) {
System.err.println("-- Problem: no permission to read directory - " + f.getPath());