|
| 1 | +/* |
| 2 | + * This file is licensed to the public under the terms of the GNU Public License 3.0 |
| 3 | + * (aka GPLv3). |
| 4 | + * |
| 5 | + * To be clear, for the purposes of copyright law, any program ["The Importing Program"] that |
| 6 | + * imports this file (via Java's "import" mechanism or via Java reflection or via any |
| 7 | + * other software technique for importing or referencing functionality) is considered |
| 8 | + * a derivative work of this work, and must also comply with the conditions of the GPLv3 |
| 9 | + * license in The Importing Program's totality to be granted a copyright license to this work, |
| 10 | + * and must also use the same definition as defined here for what constitutes a derivative work |
| 11 | + * of itself. |
| 12 | + * |
| 13 | + */ |
1 | 14 | package com.mergebase.log4j; |
2 | 15 |
|
3 | 16 | import java.io.BufferedInputStream; |
|
14 | 27 | import java.util.Iterator; |
15 | 28 | import java.util.List; |
16 | 29 | import java.util.Locale; |
| 30 | +import java.util.Properties; |
17 | 31 | import java.util.zip.ZipEntry; |
18 | 32 | import java.util.zip.ZipInputStream; |
19 | 33 |
|
| 34 | +import static com.mergebase.log4j.VersionComparator.compare; |
| 35 | + |
20 | 36 | public class Log4JDetector { |
21 | 37 |
|
| 38 | + private static final String POM_PROPERTIES = "log4j-core/pom.properties".toLowerCase(Locale.ROOT); |
22 | 39 | private static final String FILE_OLD_LOG4J = "log4j/DailyRollingFileAppender.class".toLowerCase(Locale.ROOT); |
23 | 40 | private static final String FILE_LOG4J_1 = "core/LogEvent.class".toLowerCase(Locale.ROOT); |
24 | 41 | private static final String FILE_LOG4J_2 = "core/Appender.class".toLowerCase(Locale.ROOT); |
@@ -146,13 +163,20 @@ public int compare(File f1, File f2) { |
146 | 163 |
|
147 | 164 | /** |
148 | 165 | * @param fileName name to examine for type |
149 | | - * @return 0 == zip, 1 == class, -1 = who knows... |
| 166 | + * @return 0 == zip, 1 == class, 2 = log4j-core/pom.properties, -1 = who knows... |
150 | 167 | */ |
151 | 168 | private static int fileType(String fileName) { |
152 | 169 | int c = fileName.lastIndexOf('.'); |
153 | 170 | if (c >= 0) { |
154 | 171 | String suffix = fileName.substring(c + 1); |
155 | | - if ("class".equalsIgnoreCase(suffix)) { |
| 172 | + |
| 173 | + // Special logic for "log4j-core/pom.properties" last-resort version source. |
| 174 | + if ("properties".equalsIgnoreCase(suffix)) { |
| 175 | + String lower = fileName.toLowerCase(Locale.ROOT); |
| 176 | + if (lower.endsWith(POM_PROPERTIES)) { |
| 177 | + return 2; |
| 178 | + } |
| 179 | + } else if ("class".equalsIgnoreCase(suffix)) { |
156 | 180 | return 1; |
157 | 181 | } else if ("zip".equalsIgnoreCase(suffix) |
158 | 182 | || "jar".equalsIgnoreCase(suffix) |
@@ -206,6 +230,7 @@ private static void findLog4jRecursive( |
206 | 230 | boolean isLog4j2_15_override = false; |
207 | 231 | boolean isLog4j2_12_2 = false; |
208 | 232 | boolean isLog4j2_12_2_override = false; |
| 233 | + byte[] pomProperties = null; |
209 | 234 | ZipEntry ze; |
210 | 235 | while (true) { |
211 | 236 | try { |
@@ -233,9 +258,12 @@ private static void findLog4jRecursive( |
233 | 258 | int fileType = fileType(path); |
234 | 259 | boolean isSubZip = fileType == 0; |
235 | 260 | boolean isClassEntry = fileType == 1; |
| 261 | + boolean isPomProperties = fileType == 2; |
236 | 262 | boolean needClassBytes = false; |
237 | 263 |
|
238 | | - if (isClassEntry && pathLower.endsWith(FILE_LOG4J_VULNERABLE)) { |
| 264 | + if (isPomProperties) { |
| 265 | + needClassBytes = true; |
| 266 | + } else if (isClassEntry && pathLower.endsWith(FILE_LOG4J_VULNERABLE)) { |
239 | 267 | needClassBytes = true; |
240 | 268 | } else if (isClassEntry && pathLower.endsWith(FILE_LOG4J_SAFE_CONDITION1)) { |
241 | 269 | needClassBytes = true; |
@@ -288,12 +316,14 @@ public void close() { |
288 | 316 | findLog4jRecursive(fullPath, recursiveZipper); |
289 | 317 | } catch (Exception e) { |
290 | 318 | System.err.println(fullPath + " FAILED: " + e); |
291 | | - e.printStackTrace(System.out); |
| 319 | + e.printStackTrace(System.err); |
292 | 320 | } |
293 | 321 |
|
294 | 322 |
|
295 | 323 | } else { |
296 | | - if (pathLower.endsWith(FILE_OLD_LOG4J)) { |
| 324 | + if (pathLower.endsWith(POM_PROPERTIES)) { |
| 325 | + pomProperties = bytes; |
| 326 | + } else if (pathLower.endsWith(FILE_OLD_LOG4J)) { |
297 | 327 | isLog4J1_X = true; |
298 | 328 | } else if (pathLower.endsWith(FILE_LOG4J_1)) { |
299 | 329 | log4jProbe[0] = true; |
@@ -330,7 +360,42 @@ public void close() { |
330 | 360 | } |
331 | 361 | } |
332 | 362 |
|
| 363 | + |
333 | 364 | if (conditionsChecked) { |
| 365 | + if (!log4jProbe[0] || !log4jProbe[1] || !log4jProbe[2] || !log4jProbe[3] || !log4jProbe[4]) { |
| 366 | + if (pomProperties != null) { |
| 367 | + System.err.println("-- Warning: " + zipPath + " does not contain Log4J bytecode, but claims it does."); |
| 368 | + ByteArrayInputStream byteIn = new ByteArrayInputStream(pomProperties); |
| 369 | + Properties p = new Properties(); |
| 370 | + try { |
| 371 | + p.load(byteIn); |
| 372 | + String version = p.getProperty("version"); |
| 373 | + if (version != null) { |
| 374 | + boolean isLog4j2 = compare("2", version) <= 0; |
| 375 | + if (isLog4j2) { |
| 376 | + log4jProbe = new boolean[]{true, true, true, true, true}; |
| 377 | + hasJndiLookup = compare("2.0-beta9", version) <= 0; |
| 378 | + hasJndiManager = compare("2.1", version) <= 0; |
| 379 | + isLog4j2_10 = compare("2.10.0", version) <= 0; |
| 380 | + isLog4j2_12_2 = version.startsWith("2.12.") && compare("2.12.2", version) <= 0; |
| 381 | + if (isLog4j2_12_2) { |
| 382 | + isLog4j2_12_2_override = false; |
| 383 | + } |
| 384 | + isLog4j2_15 = version.startsWith("2.15."); |
| 385 | + isLog4j2_16 = version.startsWith("2.16."); |
| 386 | + isLog4j2_17 = compare("2.17.0", version) <= 0; |
| 387 | + if (isLog4j2_15 || isLog4j2_16 || isLog4j2_17) { |
| 388 | + isLog4j2_15_override = false; |
| 389 | + } |
| 390 | + } |
| 391 | + } |
| 392 | + } catch (IOException ioe) { |
| 393 | + // invalid properties file!?! |
| 394 | + } |
| 395 | + } |
| 396 | + } |
| 397 | + |
| 398 | + |
334 | 399 | boolean isLog4j = false; |
335 | 400 | boolean isLog4j_2_10_0 = false; |
336 | 401 | boolean isLog4j_2_12_2 = false; |
@@ -460,7 +525,7 @@ public void close() { |
460 | 525 | findLog4jRecursive(zip, myZipper); |
461 | 526 | } catch (Exception e) { |
462 | 527 | System.err.println("-- Problem: " + zipFile.getPath() + " FAILED: " + e); |
463 | | - e.printStackTrace(System.out); |
| 528 | + e.printStackTrace(System.err); |
464 | 529 | } finally { |
465 | 530 | myZipper.close(); |
466 | 531 | } |
|
0 commit comments