1. 前言

  1. log4j 出现了重大漏洞,黑客可以通过ldap的注入漏洞而篡改后台代码,因而全球的技术部门几乎在同一时间进行着log4j的升级。我司一个老产品用的是log4j 1.2.15的版本,虽然并不受这次漏洞的影响但领导还是要求升级到最新的log4j 2.1.17
  2. 产品架构采用jersey作为web框架,再部署到tomcat作为容器启动
  3. 直接替换jar,移除log4j 1.2.15版本的jarlog4j-1.2.15.jar,增加新版本jar:
    log4j-1.2-api-2.17.1.jar,
    log4j-api-2.17.1.jar,
    log4j-core-2.17.1.jar
    没错,log4j 2.x做了架构上的解耦因此有3个jar,而原来的1.x版本只有一个jar。这次的漏洞出现在core包里面,所以1.x本身不受这次漏洞的影响。
  4. 做完替换工作,尝试启动失败,抛出异常java.lang.ArrayIndexOutOfBoundsException
com.sun.jersey.api.core.ScanningResourceConfig localhost-startStop-1 SEVERE: catch scan exception:  (java.lang.ArrayIndexOutOfBoundsException: 52264)
  java.lang.ArrayIndexOutOfBoundsException: 52264
      at org.objectweb.asm.ClassReader.readClass(Unknown Source)
      at org.objectweb.asm.ClassReader.accept(Unknown Source)
      at org.objectweb.asm.ClassReader.accept(Unknown Source)
      at com.sun.jersey.spi.scanning.AnnotationScannerListener.onProcess(AnnotationScannerListener.java:133)
      at com.sun.jersey.core.spi.scanning.JarFileScanner.scan(JarFileScanner.java:97)
      at com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner$1.f(WebAppResourcesScanner.java:94)
      at com.sun.jersey.core.util.Closing.f(Closing.java:71)
      at com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner.scan(WebAppResourcesScanner.java:92)
      at com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner.scan(WebAppResourcesScanner.java:79)
      at com.sun.jersey.api.core.ScanningResourceConfig.init(ScanningResourceConfig.java:35)
      at com.sun.jersey.api.core.servlet.WebAppResourceConfig.init(WebAppResourceConfig.java:102)
      at com.sun.jersey.api.core.servlet.WebAppResourceConfig.<init>(WebAppResourceConfig.java:89)
      at com.sun.jersey.api.core.servlet.WebAppResourceConfig.<init>(WebAppResourceConfig.java:74)
      at com.sun.jersey.spi.container.servlet.WebComponent.getWebAppResourceConfig(WebComponent.java:672)
      at com.sun.jersey.spi.container.servlet.ServletContainer.getDefaultResourceConfig(ServletContainer.java:414)
      at com.sun.jersey.spi.container.servlet.ServletContainer.getDefaultResourceConfig(ServletContainer.java:581)
      at com.sun.jersey.spi.container.servlet.WebServletConfig.getDefaultResourceConfig(WebServletConfig.java:87)
      at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:703)
      at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:678)
      at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:203)
      at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:373)
      at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:556)
      at javax.servlet.GenericServlet.init(GenericServlet.java:158)
      at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1231)
      at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1144)
      at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1031)
      at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4997)
      at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5289)
      at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
      at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:725)
      at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:701)
      at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717)
      at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1101)
      at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1813)
      at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
      at java.util.concurrent.FutureTask.run(FutureTask.java:266)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
      at java.lang.Thread.run(Thread.java:745)

2. 解决

  • 本着有问题先google的态度去搜索了一番。Stack Overflow上有人给出的答案是因为:log4j-core-2.17.1.jar\META-INF\versions\9下的内容不能兼容导致,可以直接从jar内删除这部分代码。
  • 按照此方法尝试后依然后异常抛出,于是想自己看下源码解决吧。
  • 通过异常栈信息可以定位到抛异常的代码类WebAppResourcesScanner
at com.sun.jersey.core.spi.scanning.JarFileScanner.scan(JarFileScanner.java:97)
at com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner$1.f(WebAppResourcesScanner.java:94)
at com.sun.jersey.core.util.Closing.f(Closing.java:71)
at com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner.scan(WebAppResourcesScanner.java:92)
at com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner.scan(WebAppResourcesScanner.java:79)
  • 找到jar中对应的类反编译查看scan方法。
private void scan(String root, final ScannerListener cfl) {
        Set<String> resourcePaths = this.sc.getResourcePaths(root);
        if (resourcePaths != null) {
            Iterator i$ = resourcePaths.iterator();

            while(i$.hasNext()) {
                final String resourcePath = (String)i$.next();
                if (resourcePath.endsWith("/")) {
                    this.scan(resourcePath, cfl);
                } else if (resourcePath.endsWith(".jar")) {
                    try {
                        (new Closing(this.sc.getResourceAsStream(resourcePath))).f(new Closure() {
                            public void f(InputStream in) throws IOException {
                                JarFileScanner.scan(in, "", cfl);
                            }
                        });
                    } catch (IOException var9) {
                        throw new ScannerException("IO error scanning jar " + resourcePath, var9);
                    } 
                } else if (cfl.onAccept(resourcePath)) {
                    try {
                        (new Closing(this.sc.getResourceAsStream(resourcePath))).f(new Closure() {
                            public void f(InputStream in) throws IOException {
                                cfl.onProcess(resourcePath, in);
                            }
                        });
                    } catch (IOException var7) {
                        throw new ScannerException("IO error scanning resource " + resourcePath, var7);
                    } catch (Exception var8) {
                        this.LOGGER.log(Level.WARNING, "Failed to scan path: " + resourcePath, var8);
                    }
                }
            }
        }

    }
  • 可以大概看到这部分的代码逻辑就是扫面传入的路径中所有的jar文件读取需要的配置,结合类名和代码基本可以判断出是为了加载web source的一些资源信息,而抛出异常的代码应该是在这部分JarFileScanner.scan(in, "", cfl)
try {
	(new Closing(this.sc.getResourceAsStream(resourcePath))).f(new Closure() {
	    public void f(InputStream in) throws IOException {
	        JarFileScanner.scan(in, "", cfl);
	    }
	});
} catch (IOException var9) {
	throw new ScannerException("IO error scanning jar " + resourcePath, var9);
}
  • 可以看到这里只catch (IOException var9),并没有catch到我们遇到的java.lang.ArrayIndexOutOfBoundsException,这应该就是不兼容导致程序无法正常运行的直接原因。
  • 于是我追加了异常捕获来处理异常。
try {
    (new Closing(this.sc.getResourceAsStream(resourcePath))).f(new Closure() {
        public void f(InputStream in) throws IOException {
            JarFileScanner.scan(in, "", cfl);
        }
    });
} catch (IOException var9) {
    throw new ScannerException("IO error scanning jar " + resourcePath, var9);
} catch (Exception var10) {
	// 这里捕获到任何异常都打印日志,标明扫描失败的jar
    this.LOGGER.log(Level.WARNING, "Failed to scan path: " + resourcePath, var10);
}
  • 修改完后编译出class文件替换到jersey-servlet-1.10.jar,将新的jersey更新到项目lib下。
  • 重新部署运行,成功启动,并打印如下log.
com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner localhost-startStop-1 WARNING: Failed to scan path: /WEB-INF/lib/log4j-api-2.17.1.jar (java.lang.ArrayIndexOutOfBoundsException)\n  java.lang.ArrayIndexOutOfBoundsException
com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner localhost-startStop-1 WARNING: Failed to scan path: /WEB-INF/lib/log4j-core-2.17.1.jar (java.lang.ArrayIndexOutOfBoundsException)\n  java.lang.ArrayIndexOutOfBoundsException
com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner localhost-startStop-1 WARNING: Failed to scan path: /WEB-INF/lib/log4j-1.2-api-2.17.1.jar (java.lang.ArrayIndexOutOfBoundsException: 14385)\n  java.lang.ArrayIndexOutOfBoundsException: 14385\n      at org.objectweb.asm.ClassReader.<init>(Unknown Source)\n      at org.objectweb.asm.ClassReader.<init>(Unknown Source)\n      at org.objectweb.asm.ClassReader.<init>(Unknown Source)\n      at com.sun.jersey.spi.scanning.AnnotationScannerListener.onProcess(AnnotationScannerListener.java:133)\n      at com.sun.jersey.core.spi.scanning.JarFileScanner.scan(JarFileScanner.java:97)\n      at com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner$1.f(WebAppResourcesScanner.java:94)\n      at com.sun.jersey.core.util.Closing.f(Closing.java:71)\n      at com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner.scan(WebAppResourcesScanner.java:53)\n      at com.sun.jersey.spi.scanning.servlet.WebAppResourcesScanner.scan(WebAppResourcesScanner.java:37)\n      at com.sun.jersey.api.core.ScanningResourceConfig.init(ScanningResourceConfig.java:80)\n      at com.sun.jersey.api.core.servlet.WebAppResourceConfig.init(WebAppResourceConfig.java:102)\n      at com.sun.jersey.api.core.servlet.WebAppResourceConfig.<init>(WebAppResourceConfig.java:89)\n      at com.sun.jersey.api.core.servlet.WebAppResourceConfig.<init>(WebAppResourceConfig.java:74)\n      at com.sun.jersey.spi.container.servlet.WebComponent.getWebAppResourceConfig(WebComponent.java:672)\n      at com.sun.jersey.spi.container.servlet.ServletContainer.getDefaultResourceConfig(ServletContainer.java:414)\n      at com.sun.jersey.spi.container.servlet.ServletContainer.getDefaultResourceConfig(ServletContainer.java:581)\n      at com.sun.jersey.spi.container.servlet.WebServletConfig.getDefaultResourceConfig(WebServletConfig.java:87)\n      at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:703)\n      at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:678)\n      at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:203)\n      at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:373)\n      at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:556)\n      at javax.servlet.GenericServlet.init(GenericServlet.java:158)\n      at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1231)\n      at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1144)\n      at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1031)\n      at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4997)\n      at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5289)\n      at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)\n      at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:725)\n      at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:701)\n      at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717)\n      at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1101)\n      at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1813)\n      at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)\n      at java.util.concurrent.FutureTask.run(FutureTask.java:266)\n      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)\n      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)\n      at java.lang.Thread.run(Thread.java:745)
  • 这样的改动相当于在扫描web source的时候跳过了log4j的jar,这应该是没问题的,log4j里并不包含任何web项目的内容,只是一个打日志的工具包。
  • 至此问题解决。

3. 后记

  • 后来回想了一下,也许第一种google到的办法也许也可以解决。因为一共3个jar,而我只修改其中的一个,如果都处理的话也许是可以的,没有继续尝试。
  • 修改的代码片段是用于扫描jar的,所以扫描路径肯定是可以配置的,我找到了对应的参数名,默认是扫描/WEB-INF/lib/*也就是所有jar,如果我通过参数去指定哪些jar需要被扫描也是可以的,但没这么做。这个项目不太了解,并不确定需要扫描哪些jar,如果我们所有的jar都加上会是很长的参数,而只为了跳过log4j实在是太麻烦了不易于维护。
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐