用友IUFO远程命令执行(cloudeye演示)

2016-03-24T00:00:00
ID SSV:93394
Type seebug
Reporter Root
Modified 2016-03-24T00:00:00

Description

简要描述:

用友IUFO远程命令执行

详细说明:

google:inurl:/service/~iufo 这个只是其中的一条线索,当然这个已经搜出来10几页以上的站点 应用的使用量比较大,部署采用WebSphere 代码分析: web.xml:

``` <servlet> <servlet-name>NCInvokerServlet</servlet-name> <servlet-class>nc.bs.framework.server.InvokerServlet</servlet-class> </servlet>

&lt;servlet&gt;
 &lt;servlet-name&gt;NCFindWebServlet&lt;/servlet-name&gt;
  &lt;servlet-class&gt;nc.bs.framework.server.FindWebResourceServlet&lt;/servlet-class&gt;
&lt;/servlet&gt;

&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;NCFindWebServlet&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/NCFindWeb&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;

&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;CommonServletDispatcher&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/ServiceDispatcherServlet&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;
&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;CodeSynServlet&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/CodeSynServlet&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;

&lt;servlet-mapping&gt;
  &lt;servlet-name&gt;NCInvokerServlet&lt;/servlet-name&gt;
  &lt;url-pattern&gt;/service/*&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;

```

入口为InvokerServlet,分析一下调用过程

private void doAction(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pathInfo; long requestTime; pathInfo = request.getPathInfo(); log.debug((new StringBuilder()).append("Before Invoke: ").append(pathInfo).toString()); requestTime = System.currentTimeMillis(); String serviceName; Object obj; if(pathInfo == null) throw new ServletException("Service name is not specified, pathInfo is null"); pathInfo = pathInfo.trim(); String moduleName = null; serviceName = null; if(pathInfo.startsWith("/~")) { moduleName = pathInfo.substring(2); int slashIndex = moduleName.indexOf("/"); if(slashIndex &gt;= 0) { serviceName = moduleName.substring(slashIndex); if(slashIndex &gt; 0) moduleName = moduleName.substring(0, slashIndex); else moduleName = null; } else { moduleName = null; serviceName = pathInfo; } } else { serviceName = pathInfo; } if(serviceName == null) throw new ServletException("Service name is not specified"); int beginIndex = serviceName.indexOf("/"); if(beginIndex &lt; 0 || beginIndex &gt;= serviceName.length() - 1) throw new ServletException("Service name is not specified"); serviceName = serviceName.substring(beginIndex + 1); obj = null; try { obj = getServiceObject(moduleName, serviceName); } catch(ComponentException e) { String msg = svcNotFoundMsgFormat.format(((Object) (new Object[] { serviceName }))); Logger.error(msg, e); throw new ServletException(msg); } if(!(obj instanceof Servlet)) break MISSING_BLOCK_LABEL_487; Logger.init(obj.getClass()); try { if(obj instanceof GenericServlet) ((GenericServlet)obj).init(); preRemoteProcess(); ((Servlet)obj).service(request, response); postRemoteProcess(); }

入口InvokerServlet if(pathInfo.startsWith("/~")) { moduleName = pathInfo.substring(2); // iufo/com.ufida.web.action.ActionServlet int slashIndex = moduleName.indexOf("/"); if(slashIndex >= 0) { serviceName = moduleName.substring(slashIndex); if(slashIndex > 0) moduleName = moduleName.substring(0, slashIndex); else moduleName = null; } else { moduleName = null; serviceName = pathInfo; } } else { serviceName = pathInfo; } 如果url为http://xxxxxxx/service/~iufo/com.ufida.web.action.ActionServlet moduleName : iufo pathInfo : /~iufo/com.ufida.web.action.ActionServlet serviceName : com.ufida.web.action.ActionServlet 继续往下 obj = getServiceObject(moduleName, serviceName); 这一行跟进去看看

private Object getServiceObject(String moduleName, String serviceName) throws ComponentException { Object retObject = null; if(moduleName == null) { retObject = NCLocator.getInstance().lookup(serviceName); } else { retObject = serviceObjMap.get((new StringBuilder()).append(moduleName).append(":").append(serviceName).toString()); if(retObject == null) { Module module = BusinessAppServer.getInstance().getModule(moduleName); if(module instanceof DeployedModule) { DeployedModule deployed = (DeployedModule)module; try { retObject = deployed.getContext().lookup(serviceName); } catch(ComponentException exp) { try { Class clazz = deployed.getClassLoader().loadClass(serviceName); retObject = clazz.newInstance(); } catch(ClassNotFoundException e) { throw new FrameworkRuntimeException(instantiateMsgFormat.format(((Object) (new Object[] { moduleName, serviceName }))), e); } catch(InstantiationException e) { throw new FrameworkRuntimeException(instantiateMsgFormat.format(((Object) (new Object[] { moduleName, serviceName }))), e); } catch(IllegalAccessException e) { throw new FrameworkRuntimeException(instantiateMsgFormat.format(((Object) (new Object[] { moduleName, serviceName }))), e); } } } } if(retObject != null) serviceObjMap.put((new StringBuilder()).append(moduleName).append(":").append(serviceName).toString(), retObject); } return retObject; }

合格函数的意思就是根据moduleName 找到WebSphere 安装时候对应的应用 然后 Class clazz = deployed.getClassLoader().loadClass(serviceName); retObject = clazz.newInstance(); 实例化对象 获取到了这个对象之后,逻辑就走到

if(!(obj instanceof Servlet)) break MISSING_BLOCK_LABEL_487; Logger.init(obj.getClass()); try { if(obj instanceof GenericServlet) ((GenericServlet)obj).init(); preRemoteProcess(); ((Servlet)obj).service(request, response); postRemoteProcess(); }

根据代码的意思,可以理解为不是Servlet实例 是GenericServlet的实例,就进行调用 看一下GenericServlet 这个东西的所有子类

<img src="https://images.seebug.org/upload/201603/24175623699c248cfdb617ab555545fb7724723c.gif" alt="1.gif" width="600" onerror="javascript:errimg(this);">

分析完了请求流程,我们看看问题文件 BIReportOperServlet

public class BIReportOperServlet extends HttpServlet { public BIReportOperServlet() { } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { performTask(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { performTask(request, response); } private void performTask(HttpServletRequest request, HttpServletResponse response) { try { ObjectInputStream ois = new ObjectInputStream(new GZIPInputStream(request.getInputStream())); String repID = (String)ois.readObject(); AppDebug.debug((new StringBuilder()).append("[debug]: BIReportOperServlet repID = ").append(repID).toString()); BaseReportModel model = null; model = (BaseReportModel)request.getSession().getAttribute(repID); ObjectOutputStream objectoutputstream = new ObjectOutputStream(response.getOutputStream()); objectoutputstream.writeObject(model); objectoutputstream.flush(); objectoutputstream.close(); } catch(Throwable e) { e.printStackTrace(System.out); } } }

通过流读出来一个gzip的压缩的文件内容并解压,然后问题点就是通过String repID = (String)ois.readObject();这个东西就读取 经过测试这个东西网上都说没有对其结果进行强制类型转换就会导致反序列化漏洞产生 实际测试情况不然,ois.readObject() 已经进行了反序列化操作 然后在进行String强制类型转换 这时候已经没有意义了 做一个反序列化的bin文件

<img src="https://images.seebug.org/upload/201603/24180448fb3c62b1c6dfbb072f7a1931d07a3793.gif" alt="2.gif" width="600" onerror="javascript:errimg(this);">

用gzip压缩一下dahan.bin.gz 如果对方站点的commons-collections.jar 版本过低的话,那么就存在命令执行 我们开始从google第一页一个个试试,最后看看cloudeye 的dns日志就知道了

<img src="https://images.seebug.org/upload/201603/24181548094a8269ff092c003a6fcfc3b64ca2d1.gif" alt="3.gif" width="600" onerror="javascript:errimg(this);">

<img src="https://images.seebug.org/upload/201603/2418155895f4c3a3c7fac0fb4d0c22918a44b752.gif" alt="4.gif" width="600" onerror="javascript:errimg(this);">

案例: ... ... ... ... ...:9080 ...

<img src="https://images.seebug.org/upload/201603/24182330e78101b414ac958efd87c317c236968c.gif" alt="5.gif" width="600" onerror="javascript:errimg(this);">

例子不多举了 因为这个使用量非常大,所以写个程序fuzz一下,还是能出来一大堆

漏洞证明: