25
25
import java .util .Comparator ;
26
26
import java .util .HashSet ;
27
27
import java .util .Iterator ;
28
+ import java .util .LinkedHashMap ;
28
29
import java .util .List ;
29
30
import java .util .Locale ;
31
+ import java .util .Map ;
30
32
import java .util .Properties ;
33
+ import java .util .Set ;
34
+ import java .util .TreeSet ;
31
35
import java .util .zip .ZipEntry ;
32
36
import java .util .zip .ZipInputStream ;
33
37
@@ -68,22 +72,48 @@ public class Log4JDetector {
68
72
69
73
private static boolean verbose = false ;
70
74
private static boolean debug = false ;
75
+ private static boolean json = false ;
76
+ private static Set <String > excludes = new TreeSet <String >();
71
77
private static boolean foundHits = false ;
72
78
private static boolean foundLog4j1 = false ;
73
79
74
- public static void main (String [] args ) {
80
+ public static void main (String [] args ) throws IOException {
75
81
List <String > argsList = new ArrayList <String >();
76
82
Collections .addAll (argsList , args );
77
83
78
84
Iterator <String > it = argsList .iterator ();
85
+ List <String > stdinLines = new ArrayList <String >();
79
86
while (it .hasNext ()) {
80
- final String argOrig = it .next ();
87
+ final String argOrig = it .next (). trim () ;
81
88
if ("--debug" .equals (argOrig )) {
82
89
debug = true ;
83
90
it .remove ();
84
91
} else if ("--verbose" .equals (argOrig )) {
85
92
verbose = true ;
86
93
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 );
87
117
} else {
88
118
File f = new File (argOrig );
89
119
if (!f .exists ()) {
@@ -92,28 +122,41 @@ public static void main(String[] args) {
92
122
}
93
123
}
94
124
}
125
+ argsList .addAll (stdinLines );
95
126
96
127
if (argsList .isEmpty ()) {
97
128
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\" ]" );
99
136
System .out .println ();
100
137
System .out .println ("Exit codes: 0 = No vulnerable Log4J versions found." );
101
138
System .out .println (" 1 = At least one legacy Log4J 1.x version found." );
102
139
System .out .println (" 2 = At least one vulnerable Log4J 2.x version found." );
103
140
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 )" );
105
142
System .out .println ("Docs - https://github.com/mergebase/log4j-detector " );
106
143
System .out .println ("(C) Copyright 2021 Mergebase Software Inc. Licensed to you via GPLv3." );
107
144
System .out .println ();
108
145
System .exit (100 );
109
146
}
110
147
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
+ }
113
153
for (String arg : argsList ) {
114
154
File dir = new File (arg );
115
155
analyze (dir );
116
156
}
157
+ if (json ) {
158
+ System .out .println ("{\" _THE_END_\" :true}]}" );
159
+ }
117
160
if (foundHits ) {
118
161
System .exit (2 );
119
162
} else if (foundLog4j1 ) {
@@ -422,10 +465,10 @@ public void close() {
422
465
StringBuilder buf = new StringBuilder ();
423
466
if (isLog4j ) {
424
467
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_ " );
426
469
foundLog4j1 = true ;
427
470
} else {
428
- buf .append (zipPath ). append ( " contains Log4J-2.x " );
471
+ buf .append (" contains Log4J-2.x " );
429
472
}
430
473
if (isVulnerable ) {
431
474
if (isLog4j_2_10_0 ) {
@@ -455,11 +498,11 @@ public void close() {
455
498
if (!isSafe ) {
456
499
foundHits = true ;
457
500
}
458
- System .out .println (buf );
501
+ System .out .println (prepareOutput ( zipPath , buf ) );
459
502
} 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_" );
461
504
foundLog4j1 = true ;
462
- System .out .println (buf );
505
+ System .out .println (prepareOutput ( zipPath , buf ) );
463
506
}
464
507
}
465
508
} finally {
@@ -469,6 +512,24 @@ public void close() {
469
512
}
470
513
}
471
514
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
+
472
533
private static boolean containsMatch (byte [] bytes , byte [] needle ) {
473
534
int matched = Bytes .kmp (bytes , needle );
474
535
return matched >= 0 ;
@@ -590,6 +651,19 @@ private static void analyze(File f) {
590
651
// Hopefully this stops symlink cycles.
591
652
// Using CRC-64 of path to save on memory (since we're storing *EVERY* path we come across).
592
653
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
+ }
593
667
long crc = CRC64 .hash (path );
594
668
if (visited .contains (crc )) {
595
669
return ;
@@ -626,8 +700,8 @@ private static void analyze(File f) {
626
700
if (isLog4J_1_X ) {
627
701
StringBuilder buf = new StringBuilder ();
628
702
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 ) );
631
705
} else {
632
706
maybe = currentPathLower .endsWith (FILE_LOG4J_1 );
633
707
}
@@ -683,7 +757,7 @@ private static void analyze(File f) {
683
757
}
684
758
}
685
759
StringBuilder buf = new StringBuilder ();
686
- buf .append (f . getParentFile (). getParent ()). append ( " contains Log4J-2.x " );
760
+ buf .append (" contains Log4J-2.x " );
687
761
if (isVulnerable ) {
688
762
if (isLog4J_2_10 ) {
689
763
if (isLog4J_2_17 ) {
@@ -711,7 +785,7 @@ private static void analyze(File f) {
711
785
} else {
712
786
buf .append ("<= 2.0-beta8 _POTENTIALLY_SAFE_ (Did you remove JndiLookup.class?)" );
713
787
}
714
- System .out .println (buf );
788
+ System .out .println (prepareOutput ( f . getParentFile (). getParent (), buf ) );
715
789
}
716
790
} else if (verbose ) {
717
791
System .err .println ("-- Skipping " + f .getPath () + " - Not a zip/jar/war file." );
0 commit comments