2525import java .util .Comparator ;
2626import java .util .HashSet ;
2727import java .util .Iterator ;
28+ import java .util .LinkedHashMap ;
2829import java .util .List ;
2930import java .util .Locale ;
31+ import java .util .Map ;
3032import java .util .Properties ;
33+ import java .util .Set ;
34+ import java .util .TreeSet ;
3135import java .util .zip .ZipEntry ;
3236import java .util .zip .ZipInputStream ;
3337
@@ -68,22 +72,48 @@ public class Log4JDetector {
6872
6973 private static boolean verbose = false ;
7074 private static boolean debug = false ;
75+ private static boolean json = false ;
76+ private static Set <String > excludes = new TreeSet <String >();
7177 private static boolean foundHits = false ;
7278 private static boolean foundLog4j1 = false ;
7379
74- public static void main (String [] args ) {
80+ public static void main (String [] args ) throws IOException {
7581 List <String > argsList = new ArrayList <String >();
7682 Collections .addAll (argsList , args );
7783
7884 Iterator <String > it = argsList .iterator ();
85+ List <String > stdinLines = new ArrayList <String >();
7986 while (it .hasNext ()) {
80- final String argOrig = it .next ();
87+ final String argOrig = it .next (). trim () ;
8188 if ("--debug" .equals (argOrig )) {
8289 debug = true ;
8390 it .remove ();
8491 } else if ("--verbose" .equals (argOrig )) {
8592 verbose = true ;
8693 it .remove ();
94+ } else if ("--json" .equals (argOrig )) {
95+ json = true ;
96+ it .remove ();
97+ } else if (argOrig .startsWith ("--exclude=[" )) {
98+ int x = argOrig .indexOf ("]" );
99+ if (x > 0 ) {
100+ it .remove ();
101+ String json = argOrig .substring ("--exclude=" .length ());
102+ Object o = Java2Json .parse (json );
103+ if (o instanceof List ) {
104+ List <Object > list = (List ) o ;
105+ for (Object obj : list ) {
106+ if (obj != null ) {
107+ excludes .add (String .valueOf (obj ));
108+ }
109+ }
110+ }
111+ }
112+ } else if ("--stdin" .equals (argOrig )) {
113+ it .remove ();
114+ byte [] b = Bytes .streamToBytes (System .in );
115+ String s = new String (b , Bytes .UTF_8 );
116+ stdinLines = Strings .intoLines (s );
87117 } else {
88118 File f = new File (argOrig );
89119 if (!f .exists ()) {
@@ -92,28 +122,41 @@ public static void main(String[] args) {
92122 }
93123 }
94124 }
125+ argsList .addAll (stdinLines );
95126
96127 if (argsList .isEmpty ()) {
97128 System .out .println ();
98- System .out .println ("Usage: java -jar log4j-detector-2021.12.17.jar [--verbose] [paths to scan...]" );
129+ System .out .println ("Usage: java -jar log4j-detector-2021.12.20.jar [--verbose] [--json] [--stdin] [--exclude=X] [paths to scan...]" );
130+ System .out .println ();
131+ System .out .println (" --json - Output STDOUT results in JSON. (Errors/warning still emitted to STDERR)" );
132+ System .out .println (" --stdin - Parse STDIN for paths to explore." );
133+ System .out .println (" --exclude=X - Where X is a JSON list containing full paths to exclude. Must be valid JSON." );
134+ System .out .println ();
135+ System .out .println (" Example: --excludes=[\" /dev\" , \" /media\" , \" Z:\\ TEMP\" ]" );
99136 System .out .println ();
100137 System .out .println ("Exit codes: 0 = No vulnerable Log4J versions found." );
101138 System .out .println (" 1 = At least one legacy Log4J 1.x version found." );
102139 System .out .println (" 2 = At least one vulnerable Log4J 2.x version found." );
103140 System .out .println ();
104- System .out .println ("About - MergeBase log4j detector (version 2021.12.17 )" );
141+ System .out .println ("About - MergeBase log4j detector (version 2021.12.20 )" );
105142 System .out .println ("Docs - https://github.com/mergebase/log4j-detector " );
106143 System .out .println ("(C) Copyright 2021 Mergebase Software Inc. Licensed to you via GPLv3." );
107144 System .out .println ();
108145 System .exit (100 );
109146 }
110147
111- System .out .println ("-- github.com/mergebase/log4j-detector v2021.12.17 (by mergebase.com) analyzing paths (could take a while)." );
112- System .out .println ("-- Note: specify the '--verbose' flag to have every file examined printed to STDERR." );
148+ System .err .println ("-- github.com/mergebase/log4j-detector v2021.12.20 (by mergebase.com) analyzing paths (could take a while)." );
149+ System .err .println ("-- Note: specify the '--verbose' flag to have every file examined printed to STDERR." );
150+ if (json ) {
151+ System .out .println ("{\" hits\" :[" );
152+ }
113153 for (String arg : argsList ) {
114154 File dir = new File (arg );
115155 analyze (dir );
116156 }
157+ if (json ) {
158+ System .out .println ("{\" _THE_END_\" :true}]}" );
159+ }
117160 if (foundHits ) {
118161 System .exit (2 );
119162 } else if (foundLog4j1 ) {
@@ -422,10 +465,10 @@ public void close() {
422465 StringBuilder buf = new StringBuilder ();
423466 if (isLog4j ) {
424467 if (isLog4J1_X ) {
425- buf .append (zipPath ). append ( " contains Log4J-1.x AND Log4J-2.x _CRAZY_ " );
468+ buf .append (" contains Log4J-1.x AND Log4J-2.x _CRAZY_ " );
426469 foundLog4j1 = true ;
427470 } else {
428- buf .append (zipPath ). append ( " contains Log4J-2.x " );
471+ buf .append (" contains Log4J-2.x " );
429472 }
430473 if (isVulnerable ) {
431474 if (isLog4j_2_10_0 ) {
@@ -455,11 +498,11 @@ public void close() {
455498 if (!isSafe ) {
456499 foundHits = true ;
457500 }
458- System .out .println (buf );
501+ System .out .println (prepareOutput ( zipPath , buf ) );
459502 } else if (isLog4J1_X ) {
460- buf .append (zipPath ). append ( " contains Log4J-1.x <= 1.2.17 _OLD_" );
503+ buf .append (" contains Log4J-1.x <= 1.2.17 _OLD_" );
461504 foundLog4j1 = true ;
462- System .out .println (buf );
505+ System .out .println (prepareOutput ( zipPath , buf ) );
463506 }
464507 }
465508 } finally {
@@ -469,6 +512,24 @@ public void close() {
469512 }
470513 }
471514
515+ private static String prepareOutput (String zipPath , StringBuilder buf ) {
516+ if (json ) {
517+ String msg = buf .toString ().trim ();
518+ int x = msg .lastIndexOf (" _" );
519+ String status = "_UNKNOWN_" ;
520+ if (x >= 0 ) {
521+ status = msg .substring (x ).trim ();
522+ msg = msg .substring (0 , x ).trim ();
523+ }
524+ Map <String , String > m = new LinkedHashMap <String , String >();
525+ m .put (status , zipPath );
526+ m .put ("info" , msg );
527+ return Java2Json .format (m ) + "," ;
528+ } else {
529+ return zipPath + buf ;
530+ }
531+ }
532+
472533 private static boolean containsMatch (byte [] bytes , byte [] needle ) {
473534 int matched = Bytes .kmp (bytes , needle );
474535 return matched >= 0 ;
@@ -590,6 +651,19 @@ private static void analyze(File f) {
590651 // Hopefully this stops symlink cycles.
591652 // Using CRC-64 of path to save on memory (since we're storing *EVERY* path we come across).
592653 String path = f .getPath ();
654+ if (excludes .contains (path )) {
655+ System .err .println ("-- Info: Skipping [" + path + "] because --excludes mentions it." );
656+ return ;
657+ }
658+ File parent = f .getParentFile ();
659+ while (parent != null ) {
660+ String parentPath = parent .getPath ();
661+ if (excludes .contains (parentPath )) {
662+ System .err .println ("-- Info: Skipping [" + path + "] because --excludes mentions it." );
663+ return ;
664+ }
665+ parent = parent .getParentFile ();
666+ }
593667 long crc = CRC64 .hash (path );
594668 if (visited .contains (crc )) {
595669 return ;
@@ -626,8 +700,8 @@ private static void analyze(File f) {
626700 if (isLog4J_1_X ) {
627701 StringBuilder buf = new StringBuilder ();
628702 String grandParent = f .getParentFile ().getParent ();
629- buf .append (grandParent ). append ( " contains contains Log4J-1.x <= 1.2.17 _OLD_ :-|" );
630- System .out .println (buf );
703+ buf .append (" contains Log4J-1.x <= 1.2.17 _OLD_ :-|" );
704+ System .out .println (prepareOutput ( grandParent , buf ) );
631705 } else {
632706 maybe = currentPathLower .endsWith (FILE_LOG4J_1 );
633707 }
@@ -683,7 +757,7 @@ private static void analyze(File f) {
683757 }
684758 }
685759 StringBuilder buf = new StringBuilder ();
686- buf .append (f . getParentFile (). getParent ()). append ( " contains Log4J-2.x " );
760+ buf .append (" contains Log4J-2.x " );
687761 if (isVulnerable ) {
688762 if (isLog4J_2_10 ) {
689763 if (isLog4J_2_17 ) {
@@ -711,7 +785,7 @@ private static void analyze(File f) {
711785 } else {
712786 buf .append ("<= 2.0-beta8 _POTENTIALLY_SAFE_ (Did you remove JndiLookup.class?)" );
713787 }
714- System .out .println (buf );
788+ System .out .println (prepareOutput ( f . getParentFile (). getParent (), buf ) );
715789 }
716790 } else if (verbose ) {
717791 System .err .println ("-- Skipping " + f .getPath () + " - Not a zip/jar/war file." );
0 commit comments