Skip to content

Commit

Permalink
Added --backup-ext, --backup-path, and --syslog-level options. v2.5.2
Browse files Browse the repository at this point in the history
  • Loading branch information
xeraph committed Dec 21, 2021
1 parent 8ac44b7 commit 1298f62
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 38 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-4104 (log4j 1.x), and CVE-2021-42550 (logback 0.9-1.2.7) vulnerabilities.

### Download
* [log4j2-scan 2.5.1 (Windows x64, 7z)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.1/logpresso-log4j2-scan-2.5.1-win64.7z)
* [log4j2-scan 2.5.1 (Windows x64, zip)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.1/logpresso-log4j2-scan-2.5.1-win64.zip)
* [log4j2-scan 2.5.2 (Windows x64, 7z)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.2/logpresso-log4j2-scan-2.5.2-win64.7z)
* [log4j2-scan 2.5.2 (Windows x64, zip)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.2/logpresso-log4j2-scan-2.5.2-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.5.1 (Linux x64)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.1/logpresso-log4j2-scan-2.5.1-linux.tar.gz)
* [log4j2-scan 2.5.1 (Linux aarch64)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.1/logpresso-log4j2-scan-2.5.1-linux-aarch64.tar.gz)
* [log4j2-scan 2.5.2 (Linux x64)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.2/logpresso-log4j2-scan-2.5.2-linux.tar.gz)
* [log4j2-scan 2.5.2 (Linux aarch64)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.2/logpresso-log4j2-scan-2.5.2-linux-aarch64.tar.gz)
* If native executable doesn't work, use the JAR instead. 32bit is not supported.
* [log4j2-scan 2.5.1 (Mac OS)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.1/logpresso-log4j2-scan-2.5.1-darwin.tar.gz)
* [log4j2-scan 2.5.1 (Any OS, 20KB)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.1/logpresso-log4j2-scan-2.5.1.jar)
* [log4j2-scan 2.5.2 (Mac OS)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.2/logpresso-log4j2-scan-2.5.2-darwin.tar.gz)
* [log4j2-scan 2.5.2 (Any OS, 20KB)](https://github.com/logpresso/CVE-2021-44228-Scanner/releases/download/v2.5.2/logpresso-log4j2-scan-2.5.2.jar)

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

Usage
```
Logpresso CVE-2021-44228 Vulnerability Scanner 2.5.1 (2021-12-21)
Logpresso CVE-2021-44228 Vulnerability Scanner 2.5.2 (2021-12-21)
Usage: log4j2-scan [--scan-log4j1] [--fix] target_path1 target_path2
-f [config_file_path]
Expand Down Expand Up @@ -93,7 +93,7 @@ On Linux
```
On UNIX (AIX, Solaris, and so on)
```
java -jar logpresso-log4j2-scan-2.5.1.jar [--fix] target_path
java -jar logpresso-log4j2-scan-2.5.2.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.5.1</version>
<version>2.5.2</version>
<packaging>jar</packaging>
<name>Logpresso Log4j2 Scanner</name>

Expand Down
66 changes: 58 additions & 8 deletions src/main/java/com/logpresso/scanner/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.TreeSet;

import com.logpresso.scanner.utils.IoUtils;
import com.logpresso.scanner.utils.ZipUtils;

public class Configuration {
private static final boolean isWindows = File.separatorChar == '\\';
Expand All @@ -36,10 +37,13 @@ public class Configuration {
private boolean oldExitCode = false;
private Charset zipCharset = null;

private File restorePath = null;
private File backupPath = null;
private String backupExt = "zip";
private String reportPath = null;
private String reportDir = null;
private InetSocketAddress udpSyslogAddr = null;
private SyslogLevel syslogLevel = SyslogLevel.INFO;

private String includeFilePath = null;
private Set<File> driveLetters = new TreeSet<File>();
Expand Down Expand Up @@ -70,6 +74,11 @@ public static void pringUsage() {
System.out.println("\tDo not prompt confirmation. Don't use this option unless you know what you are doing.");
System.out.println("--restore [backup_file_path]");
System.out.println("\tUnfix JAR files using zip archived file.");
System.out.println("--backup-path [zip_output_path]");
System.out.println("\tSpecify backup file path.");
System.out.println("--backup-ext [zip]");
System.out.println("\tSpecify backup file extension. zip by default.");
System.out.println("\tIf --backup-path is specified, this option is ignored.");
System.out.println("--all-drives");
System.out.println("\tScan all drives on Windows");
System.out.println("--drives c,d");
Expand All @@ -89,7 +98,13 @@ public static void pringUsage() {
System.out.println(
"\tExclude paths by file system type. nfs, nfs3, nfs4, cifs, tmpfs, devtmpfs, fuse.sshfs and iso9660 is ignored by default.");
System.out.println("--syslog-udp [host:port]");
System.out.println("\tSend alert to remote syslog host for vulnerable or potentially vulnerable binaries");
System.out.println("\tSend reports to remote syslog host.\n"
+ "\tSend vulnerable, potentially vulnerable, and mitigated reports by default.");
System.out.println("--syslog-level [level]");
System.out.println("\tSend reports only if report is higher or equal to specified level.");
System.out.println("\tSpecify alert for vulnerable and potentially vulnerable reports.");
System.out.println("\tSpecify info for vulnerable, potentially vulnerable, and mitigated reports.");
System.out.println("\tSpecify debug for vulnerable, potentially vulnerable, mitigated, and error reports.");
System.out.println("--report-csv");
System.out.println(
"\tGenerate log4j2_scan_report_yyyyMMdd_HHmmss.csv in working directory if not specified otherwise via --report-path [path]");
Expand Down Expand Up @@ -126,13 +141,28 @@ public static Configuration parseArguments(String[] args) throws Exception {
c.force = true;
} else if (args[i].equals("--restore")) {
verifyArgument(args, i, "Backup file path", "Specify backup file path.");
c.backupPath = new File(args[i + 1]);
if (!c.backupPath.exists())
throw new IllegalArgumentException("Backup file not found: " + c.backupPath.getAbsolutePath());
c.restorePath = new File(args[i + 1]);
if (!c.restorePath.exists())
throw new IllegalArgumentException("Backup file not found: " + c.restorePath.getAbsolutePath());

if (!c.restorePath.canRead())
throw new IllegalArgumentException(
"Cannot read backup file (no permission): " + c.restorePath.getAbsolutePath());

if (!c.backupPath.getName().toLowerCase().endsWith(".zip"))
throw new IllegalArgumentException("Backup file should be zip format: " + c.backupPath.getAbsolutePath());
if (!ZipUtils.isZipFile(c.restorePath))
throw new IllegalArgumentException("Backup file should be zip format: " + c.restorePath.getAbsolutePath());

i++;
} else if (args[i].equals("--backup-path")) {
verifyArgument(args, i, "Backup path", "Specify backup file path.");
c.backupPath = new File(args[i + 1]);
if (c.backupPath.exists())
throw new IllegalArgumentException("Backup file already exists: " + c.backupPath.getAbsolutePath());

i++;
} else if (args[i].equals("--backup-ext")) {
verifyArgument(args, i, "Backup extension", "Specify backup extension.");
c.backupExt = args[i + 1];
i++;
} else if (args[i].equals("--debug")) {
c.debug = true;
Expand Down Expand Up @@ -230,6 +260,14 @@ public static Configuration parseArguments(String[] args) throws Exception {
"Specify syslog host address and port. Use --syslog-udp [host:port] format.");
c.udpSyslogAddr = parseAddress(args[i + 1]);
i++;
} else if (args[i].equals("--syslog-level")) {
try {
verifyArgument(args, i, "Syslog level", "Specify syslog filter level: alert, info, or debug");
c.syslogLevel = SyslogLevel.valueOf(args[i + 1].toUpperCase());
i++;
} catch (Throwable t) {
throw new IllegalArgumentException("Invalid syslog level: " + args[i + 1]);
}
} else if (args[i].equals("--report-csv")) {
c.reportCsv = true;
} else if (args[i].equals("--report-json")) {
Expand Down Expand Up @@ -315,7 +353,7 @@ else if (!reportFile.isDirectory())
throw new IllegalArgumentException("Cannot specify both --all-drives and --drives options.");

if (!c.allDrives && c.driveLetters.isEmpty() && c.includeFilePath == null && c.targetPaths.isEmpty()
&& c.getBackupPath() == null)
&& c.getRestorePath() == null)
throw new IllegalArgumentException("Specify scan target path.");

if (c.includeFilePath != null && c.allDrives)
Expand All @@ -324,7 +362,7 @@ else if (!reportFile.isDirectory())
if (c.includeFilePath != null && !c.driveLetters.isEmpty())
throw new IllegalArgumentException("Cannot specify both --drives and -f options.");

if (c.getBackupPath() != null) {
if (c.getRestorePath() != null) {
// cannot use any other options
rejectInvalidOptionForRestore(c);
}
Expand Down Expand Up @@ -531,10 +569,18 @@ public boolean isOldExitCode() {
return oldExitCode;
}

public File getRestorePath() {
return restorePath;
}

public File getBackupPath() {
return backupPath;
}

public String getBackupExtension() {
return backupExt;
}

public String getReportPath() {
return reportPath;
}
Expand All @@ -547,6 +593,10 @@ public InetSocketAddress getUdpSyslogAddr() {
return udpSyslogAddr;
}

public SyslogLevel getSyslogLevel() {
return syslogLevel;
}

public String getIncludeFilePath() {
return includeFilePath;
}
Expand Down
11 changes: 7 additions & 4 deletions src/main/java/com/logpresso/scanner/Log4j2Scanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import com.logpresso.scanner.utils.ZipUtils;

public class Log4j2Scanner {
public static final String VERSION = "2.5.1";
public static final String VERSION = "2.5.2";
public static final String RELEASE_DATE = "2021-12-21";
public static final String BANNER = "Logpresso CVE-2021-44228 Vulnerability Scanner " + VERSION + " (" + RELEASE_DATE + ")";

Expand Down Expand Up @@ -77,8 +77,8 @@ public void run(String[] args) throws Exception {
}
}

if (config.getBackupPath() != null) {
restore(config.getBackupPath());
if (config.getRestorePath() != null) {
restore(config.getRestorePath());
} else {
scanAndFix();
}
Expand Down Expand Up @@ -383,7 +383,10 @@ private void fix() {

SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd_HHmmss");
String timestamp = df.format(new Date(metrics.getScanStartTime()));
File f = new File("log4j2_scan_backup_" + timestamp + ".zip");
File f = new File("log4j2_scan_backup_" + timestamp + "." + config.getBackupExtension());
if (config.getBackupPath() != null)
f = config.getBackupPath();

ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(new FileOutputStream(f));
Expand Down
55 changes: 38 additions & 17 deletions src/main/java/com/logpresso/scanner/ReportGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.io.Writer;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
Expand All @@ -21,12 +22,16 @@
import com.logpresso.scanner.utils.IoUtils;

public class ReportGenerator {
// See rfc5424
public static final String PRI_ALERT = "<129>";
public static final String PRI_WARN = "<132>";
public static final String PRI_NOTICE = "<133>";

public static void writeReportFile(Configuration config, Metrics metrics, Detector detector) {
Map<File, List<ReportEntry>> fileReports = detector.getFileReports();

if (config.getUdpSyslogAddr() != null)
sendSyslogs(config, fileReports);
sendSyslogs(config, fileReports, detector.getErrorReports());

if (!config.isReportCsv() && !config.isReportJson())
return;
Expand Down Expand Up @@ -61,40 +66,55 @@ public static void writeReportFile(Configuration config, Metrics metrics, Detect
}
}

private static void sendSyslogs(Configuration config, Map<File, List<ReportEntry>> fileReports) {
private static void sendSyslogs(Configuration config, Map<File, List<ReportEntry>> fileReports,
List<ReportEntry> errorReports) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
String mitigated = "<126>";
String critical = "<129>";
String warn = "<132>";

String hostname = getHostname(config.isDebug());
for (File file : fileReports.keySet()) {
for (ReportEntry entry : fileReports.get(file)) {
String syslog = entry.getJsonLine(hostname);
if (entry.getStatus() == Status.VULNERABLE)
syslog = critical + syslog;
syslog = PRI_ALERT + syslog;
else if (entry.getStatus() == Status.POTENTIALLY_VULNERABLE)
syslog = warn + syslog;
else if (entry.getStatus() == Status.MITIGATED)
syslog = mitigated + syslog;
syslog = PRI_WARN + syslog;
else if (entry.getStatus() == Status.MITIGATED
&& config.getSyslogLevel().ordinal() <= SyslogLevel.INFO.ordinal())
syslog = PRI_NOTICE + syslog;
else
continue;

byte[] b = syslog.getBytes("utf-8");
DatagramPacket pkt = new DatagramPacket(b, b.length);
pkt.setSocketAddress(config.getUdpSyslogAddr());
socket.send(pkt);
sendSyslogPacket(socket, config.getUdpSyslogAddr(), syslog);
}
}
} catch (IOException e) {
System.out.println("Error: Cannot send syslog to " + config.getUdpSyslogAddr() + " - " + e.getMessage());

if (config.getSyslogLevel() == SyslogLevel.DEBUG) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");
for (ReportEntry entry : errorReports) {
String escapedPath = JsonObject.escape(entry.getPath().getAbsolutePath());
String escapedError = entry.getError() != null ? JsonObject.escape(entry.getError()) : "";
String syslog = String.format(
"<135>{\"time\": \"%s\", \"hostname\": \"%s\", \"path\": \"%s\", \"error\": \"%s\"}",
df.format(entry.getReportTime()), hostname, escapedPath, escapedError);
sendSyslogPacket(socket, config.getUdpSyslogAddr(), syslog);
}
}
} catch (Throwable t) {
System.out.println("Error: Cannot send syslog to " + config.getUdpSyslogAddr() + " - " + t.getMessage());
if (config.isDebug())
e.printStackTrace();
t.printStackTrace();
} finally {
IoUtils.ensureClose(socket);
}
}

private static void sendSyslogPacket(DatagramSocket socket, InetSocketAddress remote, String syslog) throws IOException {
byte[] b = syslog.getBytes("utf-8");
DatagramPacket pkt = new DatagramPacket(b, b.length);
pkt.setSocketAddress(remote);
socket.send(pkt);

}

Expand All @@ -121,7 +141,8 @@ private static File generateReportFileName(Configuration config, Metrics metrics

private static void writeCsvReport(Configuration config, Map<File, List<ReportEntry>> fileReports, FileOutputStream csvStream)
throws IOException, UnsupportedEncodingException {
String header = String.format("\"Hostname\",\"Path\",\"Entry\",\"Product\",\"Version\",\"CVE\",\"Status\",\"Fixed\",\"Detected at\"%n");
String header = String
.format("\"Hostname\",\"Path\",\"Entry\",\"Product\",\"Version\",\"CVE\",\"Status\",\"Fixed\",\"Detected at\"%n");
csvStream.write(header.getBytes("utf-8"));

String hostname = getHostname(config.isDebug());
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/logpresso/scanner/SyslogLevel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.logpresso.scanner;

public enum SyslogLevel {
DEBUG, INFO, ALERT
}
12 changes: 12 additions & 0 deletions src/main/java/com/logpresso/scanner/utils/ZipUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@

public class ZipUtils {

public static boolean isZipFile(File f) {
ZipFile zipFile = null;
try {
zipFile = new ZipFile(f);
return true;
} catch (Throwable t) {
return false;
} finally {
IoUtils.ensureClose(zipFile);
}
}

public static boolean isScanTarget(String path, boolean scanZip) {
String loweredPath = path.toLowerCase();
if (scanZip && loweredPath.endsWith(".zip"))
Expand Down

0 comments on commit 1298f62

Please sign in to comment.