漏洞简介
用友NC系统的 ActivityNotice/doSingUp 接口存在SQL注入漏洞。攻击者可通过构造恶意的 SQL 语句注入请求参数,绕过身份验证或获取数据库敏感信息,进而可能导致任意数据读取、篡改甚至系统权限提升,影响系统的安全性和数据完整性。
影响版本
NC63、NC65
fofa语法
app="用友-UFIDA-NC"
漏洞分析
直接看ActivityAction 类的doSingUp方法的实现逻辑吧
@Servlet(
path = "/ActivityNotice"
)
public class ActivityAction extends BaseAction {
@Action
public void doSingUp() {
HttpServletResponse response = this.getResponse();
response.setContentType("text/html");
response.setHeader("Cache-Control", "no-cache");
response.setCharacterEncoding("UTF-8");
response.addHeader("Content-type", "text/html;charset=UTF-8");
HttpServletRequest request = this.request;
String pk_user = request.getParameter("pk_psndoc");
String actid = request.getParameter("actid");
if (pk_user != null && !pk_user.equals("")) {
IActivitySignupService signup = (IActivitySignupService)NCLocator.getInstance().lookup(IActivitySignupService.class);
try {
if (signup.isSingup(pk_user, actid, pk_user)) {
signup.addUserActivitySignup(pk_user, actid, pk_user, true);
}
} catch (BusinessException e3) {
throw new LfwRuntimeException(e3.getMessage());
}
参数pk_psndoc、和actid被带入addUserActivitySignup方法,跟进addUserActivitySignup方法看下
public void addUserActivitySignup(String pkUser, String pkActivity, String signupUser, boolean isSchedule) throws BusinessException {
SignUpVO signup = this.createSignUpFromUser(signupUser);
signup.setStatus(2);
signup.setSignupactivity(pkActivity);
this.saveUserActivitySignup(pkUser, pkActivity, signup, isSchedule);
}
继续跟进saveUserActivitySignup方法
public SignUpVO saveUserActivitySignup(String pkUser, String pkActivity, SignUpVO signup, boolean isSchedule) throws BusinessException {
AggActivityVO activityVO = this.getAggActivityVOByUserPKandActivity(signup.getPk_person(), pkActivity);
继续跟进getAggActivityVOByUserPKandActivity方法
private AggActivityVO getAggActivityVOByUserPKandActivity(String pk_person, String pkActivity) throws BusinessException {
AggActivityVO activityVO = this.getActivityQueryService().getAggActivityByPk(pkActivity);
继续跟进getAggActivityByPk方法
public AggActivityVO getAggActivityByPk(String pk_activity) throws LfwBusinessException, BusinessException {
if (pk_activity != null && pk_activity.length() != 0) {
AggActivityVO aggvo = (AggActivityVO)this.getOaQueryService().queryBillOfVOByPK(AggActivityVO.class, pk_activity, true);
继续跟进queryBillOfVOByPK方法
public <T> T queryBillOfVOByPK(Class<T> voClass, String billPK, boolean bLazyLoad) throws MetaDataException {
return (T)(new MDBaseDAO()).queryBillOfVOByPK(voClass, billPK, bLazyLoad);
}
public Object queryBillOfVOByPK(Class voClass, String billPK, boolean bLazyLoad) throws MetaDataException {
NCObject ncObj = (new VOQueryPersister(voClass.getName())).queryBillImp(billPK, bLazyLoad);
继续跟进queryBillImp方法
protected NCObject queryBillImp(String billPK, boolean bLazyLoad) throws MetaDataException {
NCObject resNCObj = null;
try {
Object resVO = this.dao.retrieveByPK(billPK, this.ignoreDrEqual1);
跟进retrieveByPK方法
public Object retrieveByPK(String pkValue, boolean ignoreDrEqual1) throws MetaDataException {
if (this.metaCollection != null && this.metaCollection.size() != 0) {
String whereConStr = "";
whereConStr = (String)this.tableAliasMap.get(this.bean.getTable().getName()) + "." + this.bean.getTable().getPrimaryKeyName() + "='" + pkValue + "'";
if (ignoreDrEqual1) {
whereConStr = whereConStr + " and isnull(" + this.bean.getTable().getName() + ".dr,0)=0 ";
}
跟到这里,漏洞原因就很明了了,参数actid经过一系列的传递,最终在retrieveByPK方法这里被拼接进SQL语句中,整个过程没有对参数actid进行校验或过滤,从而造成了SQL注入漏洞,也是朴实无华的!这个类之前也发过相关SQL注入漏洞:用友NC ActivityNotice/export SQL注入漏洞
漏洞复现
需注意NC 大多数为Oracle 少数MSSQL
POST /portal/pt/ActivityNotice/doSingUp HTTP/1.1
Host: nc.mrxn.net
Content-Type: application/x-www-form-urlencoded
pageId=login&pk_psndoc=1&actid=SQLI_POC

通过报错注入成功在响应回显当前数据库用户!


