当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2016-0188695

漏洞标题:用友IUFO远程命令执行(cloudeye演示)

相关厂商:用友软件

漏洞作者: menmen519

提交时间:2016-03-24 20:07

修复时间:2016-06-26 09:30

公开时间:2016-06-26 09:30

漏洞类型:命令执行

危害等级:高

自评Rank:15

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2016-03-24: 细节已通知厂商并且等待厂商处理中
2016-03-28: 厂商已经确认,细节仅向厂商公开
2016-03-31: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2016-05-22: 细节向核心白帽子及相关领域专家公开
2016-06-01: 细节向普通白帽子公开
2016-06-11: 细节向实习白帽子公开
2016-06-26: 细节向公众公开

简要描述:

用友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>

<servlet>
<servlet-name>NCFindWebServlet</servlet-name>
<servlet-class>nc.bs.framework.server.FindWebResourceServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>NCFindWebServlet</servlet-name>
<url-pattern>/NCFindWeb</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>CommonServletDispatcher</servlet-name>
<url-pattern>/ServiceDispatcherServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>CodeSynServlet</servlet-name>
<url-pattern>/CodeSynServlet</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>NCInvokerServlet</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>


入口为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 >= 0)
{
serviceName = moduleName.substring(slashIndex);
if(slashIndex > 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 < 0 || beginIndex >= 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 这个东西的所有子类

1.gif


分析完了请求流程,我们看看问题文件
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文件

2.gif


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

3.gif


4.gif


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

5.gif


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

漏洞证明:

修复方案:

版权声明:转载请注明来源 menmen519@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:20

确认时间:2016-03-28 09:23

厂商回复:

多谢

最新状态:

暂无


漏洞评价:

评价

  1. 2016-03-24 20:09 | _Thorns ( 普通白帽子 | Rank:1754 漏洞数:269 | 以大多数人的努力程度之低,根本轮不到去拼...)

    前排

  2. 2016-03-24 20:21 | 从容 ( 普通白帽子 | Rank:415 漏洞数:99 | 哇啦啦啦啦啦 我的宝贝 | Tr3jer@Gmail.c...)

    关注

  3. 2016-03-24 20:44 | xsser 认证白帽子 ( 普通白帽子 | Rank:297 漏洞数:22 | 当我又回首一切,这个世界会好吗?)

    不错

  4. 2016-03-24 21:05 | YY-2012 ( 核心白帽子 | Rank:3893 漏洞数:737 | 意淫,是《红楼梦》原创的词汇,但后来演变...)

    活抓大牛一头

  5. 2016-03-24 21:11 | Bear baby ( 普通白帽子 | Rank:238 漏洞数:28 | 总感觉我会在哪天突然顿悟。)

    大牛发飙了

  6. 2016-03-25 11:02 | loli 认证白帽子 ( 普通白帽子 | Rank:649 漏洞数:59 | 每个男人心中都住着一个叫小红的88号技师。)

    神器