Skip to content

Commit

Permalink
Added MD5 based Log4j 2.x version detection, v2.9.1
Browse files Browse the repository at this point in the history
  • Loading branch information
xeraph committed Feb 3, 2022
1 parent 767cae0 commit e27c7ab
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 9 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
log4j2-scan is a single binary command-line tool for CVE-2021-44228 vulnerability scanning and mitigation patch. It also supports nested JAR file scanning and patch. It also detects CVE-2021-45046 (log4j 2.15.0), CVE-2021-45105 (log4j 2.16.0), CVE-2021-44832 (log4j 2.17.0), CVE-2021-4104, CVE-2019-17571, CVE-2017-5645, CVE-2020-9488, CVE-2022-23302, CVE-2022-23305, CVE-2022-23307 (log4j 1.x), and CVE-2021-42550 (logback 0.9-1.2.7) vulnerabilities.

### Download
* [log4j2-scan 2.9.0 (Windows x64, 7z)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.0/logpresso-log4j2-scan-2.9.0-win64.7z)
* [log4j2-scan 2.9.0 (Windows x64, zip)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.0/logpresso-log4j2-scan-2.9.0-win64.zip)
* [log4j2-scan 2.9.1 (Windows x64, 7z)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.1/logpresso-log4j2-scan-2.9.1-win64.7z)
* [log4j2-scan 2.9.1 (Windows x64, zip)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.1/logpresso-log4j2-scan-2.9.1-win64.zip)
* If you get `VCRUNTIME140.dll not found` error, install [Visual C++ Redistributable](https://docs.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170).
* If native executable doesn't work, use the JAR instead. 32bit is not supported.
* 7zip is available from www.7zip.org, and is open source and free.
* [log4j2-scan 2.9.0 (Linux x64)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.0/logpresso-log4j2-scan-2.9.0-linux.tar.gz)
* [log4j2-scan 2.9.0 (Linux aarch64)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.0/logpresso-log4j2-scan-2.9.0-linux-aarch64.tar.gz)
* [log4j2-scan 2.9.1 (Linux x64)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.1/logpresso-log4j2-scan-2.9.1-linux.tar.gz)
* [log4j2-scan 2.9.1 (Linux aarch64)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.1/logpresso-log4j2-scan-2.9.1-linux-aarch64.tar.gz)
* If native executable doesn't work, use the JAR instead. 32bit is not supported.
* [log4j2-scan 2.9.0 (Mac OS)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.0/logpresso-log4j2-scan-2.9.0-darwin.zip)
* [log4j2-scan 2.9.0 (Any OS, 620KB)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.0/logpresso-log4j2-scan-2.9.0.jar)
* [log4j2-scan 2.9.1 (Mac OS)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.1/logpresso-log4j2-scan-2.9.1-darwin.zip)
* [log4j2-scan 2.9.1 (Any OS, 620KB)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.9.1/logpresso-log4j2-scan-2.9.1.jar)

### Build
* [How to build Native Image](https://github.com/logpresso/CVE-2021-44228-Scanner/wiki/FAQ#how-to-build-native-image)
Expand All @@ -39,7 +39,7 @@ Just run log4j2-scan.exe or log4j2-scan with target directory path. The logpress

Usage
```
Logpresso CVE-2021-44228 Vulnerability Scanner 2.9.0 (2022-02-02)
Logpresso CVE-2021-44228 Vulnerability Scanner 2.9.1 (2022-02-02)
Usage: log4j2-scan [--scan-log4j1] [--fix] target_path1 target_path2
-f [config_file_path]
Expand Down Expand Up @@ -134,7 +134,7 @@ On Linux
```
On UNIX (AIX, Solaris, and so on)
```
java -jar logpresso-log4j2-scan-2.9.0.jar [--fix] target_path
java -jar logpresso-log4j2-scan-2.9.1.jar [--fix] target_path
```

If you add `--fix` option, this program will copy vulnerable original JAR file to .bak file, and create new JAR file without `org/apache/logging/log4j/core/lookup/JndiLookup.class` entry. All .bak files are archived into the single zip file which is named by `log4j2_scan_backup_yyyyMMdd_HHmmss.zip`, then deleted safely. In most environments, JNDI lookup feature will not be used. However, you must use this option at your own risk. You can easily restore original vulnerable JAR files using `--restore` option.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.logpresso</groupId>
<artifactId>log4j2-scanner</artifactId>
<version>2.9.0</version>
<version>2.9.1</version>
<packaging>jar</packaging>
<name>Logpresso Log4j2 Scanner</name>

Expand Down
26 changes: 26 additions & 0 deletions src/main/java/com/logpresso/scanner/Detector.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -244,13 +245,22 @@ private DetectResult scanStream(File jarFile, ZipFileIterator it, List<String> p
Set<String> shadedJndiLookupPaths = new TreeSet<String>();
Set<String> shadedJmsAppenderPaths = new TreeSet<String>();

// for log4j2 md5 detection
List<String> log4j2Md5Targets = VersionClassifier.getLog4j2Md5Entries();
Map<String, String> log4j2Md5Map = new HashMap<String, String>();

try {
while (true) {
ZipEntry entry = it.getNextEntry();
if (entry == null)
break;

InputStream is = it.getNextInputStream();

String md5EntryName = getMd5EntryName(log4j2Md5Targets, entry.getName());
if (md5EntryName != null)
log4j2Md5Map.put(md5EntryName, VersionClassifier.md5(is));

if (entry.getName().equals(LOG4J_CORE_POM_PROPS))
log4j2Version = loadLog4j2Version(is);

Expand Down Expand Up @@ -322,6 +332,10 @@ private DetectResult scanStream(File jarFile, ZipFileIterator it, List<String> p
}

log4j2Mitigated &= shadedJndiLookupPaths.isEmpty();

if (log4j2Version == null)
log4j2Version = VersionClassifier.classifyLog4j2Version(log4j2Md5Map);

if (log4j2Version != null) {
if (isVulnerableLog4j2(Version.parse(log4j2Version))) {
printDetectionForLog4j2(jarFile, pathChain, log4j2Version, log4j2Mitigated, false);
Expand Down Expand Up @@ -381,6 +395,18 @@ private DetectResult scanStream(File jarFile, ZipFileIterator it, List<String> p
}
}

private String getMd5EntryName(List<String> log4j2Md5Targets, String entryName) {
for (String s : log4j2Md5Targets) {
if (entryName.endsWith(s)) {
int p = entryName.lastIndexOf('/');
if (p > 0)
return entryName.substring(p + 1);
}
}

return null;
}

private boolean isVulnerableLogback(String logbackVersion, boolean foundJndiUtil, boolean foundEnvUtil) {
boolean logbackFound = false;
if (logbackVersion != null) {
Expand Down
111 changes: 111 additions & 0 deletions src/main/java/com/logpresso/scanner/VersionClassifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
Expand All @@ -19,6 +21,15 @@ public class VersionClassifier {
private static final char[] HEX_CODES = "0123456789abcdef".toCharArray();
private static final Map<String, String> log4j1Digests;

private static final Map<String, String> log4j2RollingFileManagerDigests;
private static final Map<String, String> log4j2AppenderControlDigests;
private static final Map<String, String> log4j2LoggerConfigDigests;
private static final Map<String, String> log4j2ThreadContextMapDigests;
private static final Map<String, String> log4j2OutputStreamManagerDigests;
private static final Map<String, String> log4j2AsyncLoggerDigests;
private static final Map<String, String> log4j2AsyncAppenderDigests;
private static final Map<String, String> log4j2InterpolatorDigests;

static {
log4j1Digests = new HashMap<String, String>();
log4j1Digests.put("906c9e0fd306e8b499b7f2dc84e2fef3", "1.1.3");
Expand All @@ -30,6 +41,106 @@ public class VersionClassifier {
log4j1Digests.put("ff67193faa9007c0afc57dbc04c983bb", "1.2.15");
log4j1Digests.put("bd5186a7747acdd133105873fbfefdb1", "1.2.16");

log4j2RollingFileManagerDigests = new HashMap<String, String>();
log4j2RollingFileManagerDigests.put("66c1b131a623bd368e6d8bebbe16ff0c", "2.0-alpha1");
log4j2RollingFileManagerDigests.put("ea91f7e905890c4748a97e09937d3b87", "2.0-alpha2");
log4j2RollingFileManagerDigests.put("d09d7e21eea3985436167edc8bb8683b", "2.0-beta1");
log4j2RollingFileManagerDigests.put("7353cce6841e400fd840887cba14d55d", "2.0-beta2");
log4j2RollingFileManagerDigests.put("3e3f74e5af5d0b22a5e38b877f500711", "2.0-beta3");
log4j2RollingFileManagerDigests.put("167dfbf8e4da0fccc1145f841ac3d035", "2.0-beta4");
log4j2RollingFileManagerDigests.put("d02e41ca06e35a28dbda2e8721142a72", "2.0-beta9");
log4j2RollingFileManagerDigests.put("8e8beef8634c0aff2e5d63fcc87c6661", "2.0-rc1");
log4j2RollingFileManagerDigests.put("7918c364db8a499eda4408bb9529fb0e", "2.0-rc2");
log4j2RollingFileManagerDigests.put("48ecb3c6a378c045482aa4ba3bd1afe5", "2.3");
log4j2RollingFileManagerDigests.put("fd9d433dc83d1aaf88548e713693d8e2", "2.3.1");
log4j2RollingFileManagerDigests.put("5e156e9b4ae0e291c0987d9caf3a3f5a", "2.3.2");
log4j2RollingFileManagerDigests.put("39db3f6132358787af20766bba757574", "2.4");
log4j2RollingFileManagerDigests.put("a2b1489d08fe47392db0267f42e96938", "2.4.1");
log4j2RollingFileManagerDigests.put("031c3ae10ee8fd24a9fa00366cc1e010", "2.5");
log4j2RollingFileManagerDigests.put("1580aa3de80d9ac181c2ae78b1eb777f", "2.6.2");
log4j2RollingFileManagerDigests.put("7027c29e04281037abfd22ef0b16c3af", "2.7");
log4j2RollingFileManagerDigests.put("63939a357e427fadb975287084d3dd0f", "2.8");
log4j2RollingFileManagerDigests.put("46b9a7341456824e15d9d6ad71323039", "2.8.1");
log4j2RollingFileManagerDigests.put("50ca27cb0b2cf02cc5ef97d3401d4fb5", "2.8.2");
log4j2RollingFileManagerDigests.put("7dc1c465224f386d404574d76432583c", "2.11.1");
log4j2RollingFileManagerDigests.put("4206094f030c94db57d75ec11b17b606", "2.11.2");
log4j2RollingFileManagerDigests.put("dc95679162dde24806281fce2609d84f", "2.12.3");
log4j2RollingFileManagerDigests.put("d71ec841dcaaa158adda0c326b8b30eb", "2.13.0");
log4j2RollingFileManagerDigests.put("4bef436fd4aadff166f3ccf49e920208", "2.13.1");
log4j2RollingFileManagerDigests.put("dd1fd2b1c1bbe6f7543e6fb6c2fd8a78", "2.14.0");
log4j2RollingFileManagerDigests.put("81b374404981e7739e514de150b16416", "2.14.1");

log4j2AppenderControlDigests = new HashMap<String, String>();
log4j2AppenderControlDigests.put("f2b2d44ebdb80db7d87e7d44e050f689", "2.12.4");
log4j2AppenderControlDigests.put("445b65145d2e9ccbdcd0238e20acc15a", "2.15.0");
log4j2AppenderControlDigests.put("c41c4e77e28a414e9512e889fe72c26e", "2.16.0");
log4j2AppenderControlDigests.put("e7cd10930f7efcf09c43fd580f476491", "2.17.0");
log4j2AppenderControlDigests.put("b1e72c1608d69230e852de53f411553c", "2.17.1");

log4j2LoggerConfigDigests = new HashMap<String, String>();
log4j2LoggerConfigDigests.put("17ad30e070723548423f8b01f892cea5", "2.0-beta5");
log4j2LoggerConfigDigests.put("5d6696388448e169cd29ee5426187bc5", "2.0-beta6");
log4j2LoggerConfigDigests.put("8353ff994104395cedf609330b0c13ed", "2.0-beta7");
log4j2LoggerConfigDigests.put("fd9c4cbc794aa8cc56e261f7f193ff17", "2.0-beta8");
log4j2LoggerConfigDigests.put("a73f488fd9770a6ba44b1944e879622d", "2.12.0");
log4j2LoggerConfigDigests.put("54da0558628cfabae6eeeabf9d284f8e", "2.12.1");

log4j2ThreadContextMapDigests = new HashMap<String, String>();
log4j2ThreadContextMapDigests.put("9d7a8b73a4c83493703805bcaa97d5f4", "2.13.2");
log4j2ThreadContextMapDigests.put("687baf0490162fc956f6365c7894ab1a", "2.13.3");

log4j2AsyncAppenderDigests = new HashMap<String, String>();
log4j2AsyncAppenderDigests.put("744dc79c1ce978a5ce6aee7acc63e7de", "2.10.0");
log4j2AsyncAppenderDigests.put("08ce0e2441b7a51a887489494da447ad", "2.11.0");

log4j2AsyncLoggerDigests = new HashMap<String, String>();
log4j2AsyncLoggerDigests.put("f1b4a01e8163a752e4bd969433d70481", "2.9.0");
log4j2AsyncLoggerDigests.put("10a184fde310fb171cd668b39a8f63fb", "2.9.1");

log4j2OutputStreamManagerDigests = new HashMap<String, String>();
log4j2OutputStreamManagerDigests.put("0d67bfd717ee9cc3b5b35d5083e6d01a", "2.6");
log4j2OutputStreamManagerDigests.put("c46438987ce8df640eab163bb20a3d83", "2.6.1");

log4j2InterpolatorDigests = new HashMap<String, String>();
log4j2InterpolatorDigests.put("763b4af13fd37d076122b2c9aff02790", "2.12.2");

}

public static List<String> getLog4j2Md5Entries() {
return Arrays.asList("/log4j/core/appender/rolling/RollingFileManager.class", "/log4j/core/config/LoggerConfig.class",
"/log4j/core/appender/AsyncAppender.class", "/log4j/core/lookup/Interpolator.class",
"/log4j/core/config/AppenderControl.class",
"/log4j/core/impl/ThreadContextDataInjector$ForCopyOnWriteThreadContextMap.class",
"/log4j/core/appender/OutputStreamManager.class", "/log4j/core/async/AsyncLogger.class");
}

@SuppressWarnings("unchecked")
public static String classifyLog4j2Version(Map<String, String> entryMd5Map) {

final String[] classFileNames = new String[] { "RollingFileManager.class", "LoggerConfig.class", "AppenderControl.class",
"ThreadContextDataInjector$ForCopyOnWriteThreadContextMap.class", "AsyncAppender.class",
"OutputStreamManager.class", "AsyncLogger.class", "Interpolator.class" };

final Map<String, String>[] digestMaps = new Map[] { log4j2RollingFileManagerDigests, log4j2LoggerConfigDigests,
log4j2AppenderControlDigests, log4j2ThreadContextMapDigests, log4j2AsyncAppenderDigests,
log4j2OutputStreamManagerDigests, log4j2AsyncLoggerDigests, log4j2InterpolatorDigests };

for (int i = 0; i < classFileNames.length; i++) {
String version = detectLog4j2Version(classFileNames[i], entryMd5Map, digestMaps[i]);
if (version != null)
return version;
}

return null;
}

private static String detectLog4j2Version(String classFileName, Map<String, String> entryMd5Map,
Map<String, String> digests) {
String md5 = entryMd5Map.get(classFileName);
if (md5 == null)
return null;

return digests.get(md5);
}

public static String classifyLog4j1Version(String categoryMd5, String mdcMd5, String ndcMd5) {
Expand Down

0 comments on commit e27c7ab

Please sign in to comment.