蓝凌OA RCE分析
正文
路由是在==/data/sys-common/datajson==处,我们直接在源码中找到该路由的位置,位于WEB-INF/libSrc/com/landray/kmss/common/actions/DataController.java
,是在他的datajson方法中
找到路由所对应的代码,主要代码如下
String s_bean = request.getParameter("s_bean");
JSONArray array = new JSONArray();
JSONArray jsonArray = null;
try {
Assert.notNull(s_bean, "参数s_bean不能为空!");
RequestContext requestInfo = new RequestContext(request, true);
String[] beanList = s_bean.split(";");
List result = null;
for(int i = 0; i < beanList.length; ++i) {
IXMLDataBean treeBean = (IXMLDataBean)SpringBeanUtil.getBean(beanList[i]);
result = treeBean.getDataList(requestInfo);
接收了一个s_bean的参数,然后用分号分隔成数组,遍历调用SpringBeanUtil.getBean(beanList[i])
,并且遍历调用getBean返回对象的getDataList方法。
根据payload我们可以知道具体调用的应该是sysFormulaSimulateByJS
这个Bean的getDataList方法,来到这个方法
public List getDataList(RequestContext requestContext) throws Exception {
String strRequestType = requestContext.getParameter("requestType");
return "getFdHandlerRoleInfoIds".equals(strRequestType) ? this.getFdHandlerRoleInfoIds(requestContext) : this.formulaSimula(requestContext);
}
判断是否传入参数requestType为getFdHandlerRoleInfoIds
,如果不是则调用formulaSimula
方法。我们这里payload没有传入这个参数,直接来到formulaSimula
方法,这里只放主要代码
public List formulaSimula(RequestContext requestContext) throws Exception {
List rtnVal = new ArrayList();
Map node = new HashMap();
String msg = null;
String confirm = null;
Object result = null;
try {
String script = requestContext.getParameter("script");
String type = requestContext.getParameter("returnType");
Enumeration ele = requestContext.getRequest().getParameterNames();
JSONObject returnData = new JSONObject();
while(ele.hasMoreElements()) {
String key = ele.nextElement().toString();
if (!paramsExcept.contains(key) && !key.endsWith(".scriptJstype")) {
String value = requestContext.getParameter(key);
String scriptJstype = requestContext.getParameter(key + ".scriptJstype");
Object newValue = FormulaParserByJS.formatValue(value, (String)scriptJstype);
returnData.put(key, newValue);
}
}
FormulaParser parse = FormulaParserByJS.getInstance(returnData, new SysFormulaSimulateByJS.SimulateVarGetter((SysFormulaSimulateByJS.SimulateVarGetter)null), (String)null);
result = parse.parseValueScript(script, type);
接收了script和returnType参数,while循环里的if判断得满足key不在paramsExcept
数组里并且不以.scriptJstype
结尾会进到if循环
static {
paramsExcept.add("script");
paramsExcept.add("method");
paramsExcept.add("s_bean");
paramsExcept.add("s_ajax");
}
我们这有传了个type和script,type不在这数组里,满足条件啊,但是这里我好像暂时没感觉和这个参数有关系?不急,先不看这,先继续往下看。
然后就是通过getInstance获取一个FormulaParserByJS
类的对象,然后调用他的parseValueScript
方法,这个方法是俩参数的,但是他有重载方法,是一个参数的,并且在这两个参数的方法里调用了this.parseValueScript(script)
,所以我们直接看一个参数的这个方法。由于方法太长,就一部分一部分来讲了。
if (StringUtil.isNull(script)) {
return null;
} else {
if (script.startsWith("#static#")) {
script = script.substring(8);
}
validateScript(script);
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByMimeType("text/javascript");
StringBuffer leftScript = new StringBuffer();
判断script是否为空并且是否以#static#
开头,如果是就截取这个后面的部分,然后就是调用了一下validateScript
方法,如果不满足条件会抛出异常
public static void validateScript(String script) {
if (StringUtil.isNotNull(script) && script.indexOf("importPackage(") > -1) {
throw new KmssRuntimeException(new KmssMessage("公式定义器非法"));
}
}
接着就是工厂模式创建了一个ScriptEngine
对象。
try {
String rightScript = script.trim();
Map funcScriptMap = new HashMap();
ScriptContext context = engine.getContext();
int k = 0;
int index = rightScript.indexOf("$");
这里的index是获取script.trim()
以后在script中,直接跳过判断。
while(true) {
if (index > -1) {
int nxtIndex = rightScript.indexOf("$", index + 1);
if (nxtIndex != -1) {
在while循环中的if判断的代码我就不放了,因为进不来,大概就是下面这样
while(true) {
String m_script = leftScript.append(rightScript).toString();
if (logger.isDebugEnabled()) {
logger.debug("原始公式:" + script);
logger.debug("执行公式:" + m_script);
}
runningData.set(this.contextData);
Object var21 = engine.eval(m_script);
return var21;
}
最后是直接通过ScriptEngine
对象的eval方法执行m_script
,m_script是通过leftScript和rightScript进行拼接以后的结果。leftScript是没东西的,rightScript是传入的那个script。直接通过eval方法造成代码执行。直接本地写个demo测试一下
疑惑的点
其实还有个问题,如果是单纯这样的话,应该不是前台RCE,经过我的测试我感觉是一个认证绕过+RCE的组合造成的前台RCE,原因是在于我在第一次找路径的时候看到他写的是==/data/sys-common/datajson.js==这个路由,然后我找了半天也没找到,最后还找错了,真是尴尬,但是其实这应该是他的一个绕过认证的手段,因为我尝试了直接访问/data/sys-common/datajson路由是需要认证的,但是访问/data/sys-common/datajson.js或者/data/sys-common/datajson.html这种,是可以直接来到控制器里的,我目前的猜测就是为了绕过filter中写的认证,但是具体在哪进行处理我并未找到。