致远OA未授权漏洞复现和简单分析

最近几天这个漏洞诈尸了。全是通告阿里云


奇安信

你说你们复现就复现吧。还全部马赛克让我等小菜如何学习进步。
我来一张高清无码的进行复现。

新漏洞出来不要吝啬分享。毕竟都能防御了。留图不留种菊花万人捅。
未授权漏洞可以利用url
位置
D:\Seeyon\A8\ApacheJetspeed\webapps\seeyon\WEB-INF\cfgHome\base/systemProperties.xml

 <not_need_logon>
            <navurl>/main.do</navurl>
            <navurl>/main.do?method=changeLocale</navurl>
            <navurl>^/main.do?method=login</navurl>
            <navurl>^/main.do?method=headerjs</navurl>
            <navurl>/main.do?method=main</navurl>
            <navurl>/main.do?method=showAbout</navurl>
            <navurl>^/main.do?method=logout</navurl>
            <navurl>^/genericController.do</navurl>
            <navurl>^/autoinstall.do</navurl>
            <navurl>^/identification.do?method=getSessionId</navurl>
            <navurl>^/thirdpartyController.do</navurl>
            <navurl>^/form/formUpgrade.do</navurl>
            <navurl>^/uploadService.do?method=processUploadService</navurl>
            <navurl>^/a8genius.do~session=false</navurl>
            <navurl>^/uc/chat.do~session=false</navurl>
            <navurl>^/fileUpload.do?method=showRTE~session=false</navurl>
            <navurl>^/commonimage.do?method=showImage</navurl>
			<navurl>mProfileManager.getProfile</navurl> 
			<navurl>mLoginManager.transLogin</navurl> 
			<navurl>mMessageManager.getPushMessageList</navurl>  
			<navurl>mBindApplyManager.bindApplyByUser</navurl> 
			<navurl>mProductManager.productStatus</navurl>
			<navurl>mProductManager.productInfo</navurl>
			<navurl>mMOneProfileManager.getUpdateServerInfo</navurl>
			<navurl>mMOneProfileManager.getOAProfile</navurl>
			<navurl>mMessageManager.getConfig</navurl>
			<navurl>portalManager.smsLoginEnabled</navurl>
			<navurl>portalManager.sendSMSLoginCode</navurl>
			<navurl>weixinLoginManager.isLogin</navurl>			
			<navurl>^/seeyonReport/checkReportController.do</navurl>
			<navurl>^/personalBind.do</navurl>
			<navurl>/individualManager.do?method=resetPasswordNologin</navurl>
			<navurl>configManager.getConfigValue</navurl>
        </not_need_logon>
/main.do
/main.do?method=changeLocale
/main.do?method=login
/main.do?method=headerjs
/main.do?method=main
/main.do?method=showAbout
/main.do?method=logout
/genericController.do
/autoinstall.do
/identification.do?method=getSessionId
/thirdpartyController.do
/form/formUpgrade.do
/uploadService.do?method=processUploadService
/a8genius.do~session=false
/uc/chat.do~session=false
/fileUpload.do?method=showRTE~session=false
/commonimage.do?method=showImage
mProfileManager.getProfile
mLoginManager.transLogin
mMessageManager.getPushMessageList
mBindApplyManager.bindApplyByUser
mProductManager.productStatus
mProductManager.productInfo
mMOneProfileManager.getUpdateServerInfo
mMOneProfileManager.getOAProfile
mMessageManager.getConfig
portalManager.smsLoginEnabled
portalManager.sendSMSLoginCode
weixinLoginManager.isLogin
/seeyonReport/checkReportController.do
/personalBind.do
/individualManager.do?method=resetPasswordNologin
configManager.getConfigValue

其中上面 mProductManager.productStatus 的这些是用来限制ajax.do请求不需要登陆。能请求类型和方法。用来限制ajax.do未登录的访问。当然如果采用上面方式绕过那么就可以通过ajax.do访问任意相关联的方法。解释下几张图:




漏洞代码

private String getUri(HttpServletRequest request) throws BusinessException {String uri = request.getRequestURI();if (uri.matches(".*?/{2,}.*?"))throw new BusinessException("url格式错误有超过2个以上的'/'" + uri);int idx = uri.indexOf(';');uri = uri.substring(request.getContextPath().length(), (idx == -1) ? uri.length() : idx);return uri;}

boolean isAjax = "/ajax.do".equals(uri);  获取请求的url是是否是ajax.do

if (isAjax) { 如果是那么
String serviceName = request.getParameter("managerName");
String methodName = request.getParameter("managerMethod");
if (methodName != null)
validateUserRole(PerfmonMisc.getAopProxySaneName(AppContext.getBean(serviceName)), methodName, user);
ignoreUrl = serviceName + "." + methodName;  //拼接组成 ignoreUrl
}

boolean needlessUrl = false;
if (user == null) {  //由于没有登录那么user为空
    AppContext.removeThreadContext("SESSION_CONTEXT_USERINFO_KEY");
    String methodName = method;
    if (isAjax) {  //如果请求中有ajax.do 那么获取方法
        methodName = request.getParameter("managerMethod");
    if (methodName == null)
        methodName = method;
    }
    needlessUrl = isNeedlessLoginUrl(request, methodName, needlessUrl); //是否包含指定的methodName 这个关系不大
}

if (!needlessUrl) //
    if (!CTPSecurityManager.isIgnoreUrl(ignoreUrl, request, response)) { //那么在检测是否是忽略的url 如果是CTPSecurityManager.isIgnoreUrl(ignoreUrl, request, response)=true 是忽略的url  关键点跳过验证

 如果是ajax请求那么ignoreUrl 只能是 mProfileManager  getProfile   限制ajax访问类。
  <navurl>mProfileManager.getProfile</navurl>
            <navurl>mLoginManager.transLogin</navurl>
            <navurl>mMessageManager.getPushMessageList</navurl>  
            <navurl>mBindApplyManager.bindApplyByUser</navurl>
            <navurl>mProductManager.productStatus</navurl>
            <navurl>mProductManager.productInfo</navurl>
            <navurl>mMOneProfileManager.getUpdateServerInfo</navurl>
            <navurl>mMOneProfileManager.getOAProfile</navurl>
            <navurl>mMessageManager.getConfig</navurl>
            <navurl>portalManager.smsLoginEnabled</navurl>
            <navurl>portalManager.sendSMSLoginCode</navurl>
            <navurl>weixinLoginManager.isLogin</navurl> 
        boolean state = checkSessionPre(request, response, handler.getClass().getCanonicalName(), isAjax);
        if (!state)
        return state;
        validateUserRole(clazzName, method, user);
    }
initJsonParams(request);

public static boolean isIgnoreUrl(String navurl, HttpServletRequest request, HttpServletResponse response) {
boolean result = ignoredUrl.contains(navurl), inited = false;
if (!result)
for (String url : ignoredUrl) {
Map<String, String> configMap = new HashMap<String, String>();
int idx = url.indexOf('~');
int idxUrl = url.indexOf('~');
if (idx != -1) {
String[] cfgs = url.substring(idx + 1).split(";");
for (String cfg : cfgs) {
idx = cfg.indexOf('=');
if (idx != -1) {
String key = cfg.substring(0, idx);
String value = cfg.substring(idx + 1);
configMap.put(key, value);
}
}
url = url.substring(0, idxUrl);
}
String initSessionCfg = configMap.get("session");
if (initSessionCfg != null && !Boolean.parseBoolean(initSessionCfg)) {
AppContext.initSystemEnvironmentContext(request, response, false);
inited = true;
}
if (url.startsWith("^") && navurl.startsWith(url.substring(1))) {
result = true;
break;
}
}
if (!inited)
AppContext.initSystemEnvironmentContext(request, response);
return result


private static Set<String> ignoredUrl;
private static void init() {
if (ignoredUrl == null) {
ignoredUrl = new HashSet<String>();
loadNotNeedLogon("ctp", ignoredUrl);
Object obj = MclclzUtil.invoke(c1, "getInstance");
List<String> plugins = (List<String>)MclclzUtil.invoke(c1, "getPluginIds", null, obj, null);
for (String plugin : plugins)
loadNotNeedLogon(plugin, ignoredUrl);
}
}

D:\Seeyon\A8\ApacheJetspeed\webapps\seeyon\WEB-INF\cfgHome\base/systemProperties.xml
private static void loadNotNeedLogon(String prefix, Set<String> ignoreList) {
String sysConfig = AppContext.getSystemProperty(prefix + ".not_need_logon.navurl"); ctp.not_need_logon.navurl
if (sysConfig != null) {
String[] urls = sysConfig.split("\\|");
for (String c : urls)
ignoreList.add(c);
}
}

相关的漏洞jar包就提供了。以上仅贴出了关键代码。由于对致远A8不熟悉难免分析不准。如果有分析比较模糊的。大家可以跟帖进行详细分析。好了此贴完毕!!!

6 Likes

以下代码解释为何payload要那么构造。要先gzip压缩然后。url编码。本人对致远oa不熟悉。另外上传写的payload大部分oa版本不可用。所以影响没有想象中的大未授权通杀。如果有不对的地方。留言跟帖。漏洞原理利用未授权AjaxController。利用类名方法名可控。可控formulaManager然后利用反射invokeService给service:formulaManager类名属性赋值。然后利用致远自己的类动态编译的手法编译。触发漏洞

 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 = 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;
  }
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 {
      Object service = getService(Strings.escapeJavascript(serviceName));
      Object<Object, Object> result = (Object<Object, Object>)invokeMethod(service, methodName, strArgs, serviceName);
      if (result instanceof FlipInfo) {
        FlipInfo fpi = (FlipInfo)result;
        result = (Object<Object, Object>)new HashMap<>();
        Map<String, Integer> rmap = (Map)result;
        rmap.put("total", Integer.valueOf(fpi.getTotal()));
        rmap.put("data", fpi.getData());
        rmap.put("page", Integer.valueOf(fpi.getPage()));
        rmap.put("pages", fpi.getPages());
        rmap.put("total", Integer.valueOf(fpi.getTotal()));
        rmap.put("size", Integer.valueOf(fpi.getSize()));
      } 
      if (ctpJSONPCallback == null) {
        retValue = JSONUtil.toJSONString4Ajax(result);
      } else {
        retValue = (String)result;
        if (result == null)
          result = (Object<Object, Object>)"null"; 
      } 
    } catch (Throwable e) {
      String eid;
      Throwable e1 = e;
      if (e instanceof InvocationTargetException)
        e1 = ((InvocationTargetException)e).getTargetException(); 
      boolean isAlert = false;
      String message = null;
      String stacktrace = null;
      Throwable tracet = null;
      if (e1 instanceof BusinessException) {
        BusinessException ae = (BusinessException)e1;
        Throwable rawCause = ae.getRawCause();
        if (rawCause == null) {
          ae = ae.getRawBusinessException();
          message = ae.getMessage();
          isAlert = true;
        } else {
          tracet = rawCause;
        } 
        eid = ae.getCode();
      } else {
        tracet = e1;
        String curm = String.valueOf(System.currentTimeMillis());
        eid = curm.substring(curm.length() - 10);
      } 
      if (tracet != null) {
        message = tracet.getMessage();
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        tracet.printStackTrace(pw);
        stacktrace = sw.toString();
      } 
      JsonErrorObject jsonErrorObject = new JsonErrorObject();
      jsonErrorObject.setMessage(message);
      jsonErrorObject.setCode(eid);
      try {
        jsonValue = JSONMapper.toJSON(jsonErrorObject);
        if (ctpJSONPCallback == null) {
          retValue = jsonValue.render(false);
        } else {
          retValue = String.format("%1$s(%2$s)", new Object[] { ctpJSONPCallback, jsonValue.render(false) });
        } 
      } catch (Exception exception) {}
      if (ctpJSONPCallback == null) {
        response.setStatus(500);
      } else {
        response.setStatus(200);
      } 
      if (!isAlert)
        LOGGER.error("+ eid, e1); 
    } 
    return retValue;
  }
  public static String uncompressRequest(String source, String compressType, String responseEncoding, Log logger) {
    String result = null;
    if (StringUtils.isNotBlank(compressType) && StringUtils.isNotBlank(source) && ("GZIP"
      .equalsIgnoreCase(compressType) || "ZIP".equalsIgnoreCase(compressType))) {
      ByteArrayInputStream sourceIn = null;
      InputStream uncompressIn = null;
      try {
        sourceIn = new ByteArrayInputStream(source.getBytes("iso-8859-1"));
        if ("GZIP".equalsIgnoreCase(compressType)) {
          uncompressIn = new GZIPInputStream(sourceIn);
        } else if ("ZIP".equalsIgnoreCase(compressType)) {
          uncompressIn = new ZipInputStream(sourceIn);
        } 
        result = IOUtils.toString(uncompressIn, responseEncoding);
      } catch (Exception e) {
        logger.error("compress data error.", e);
      } finally {
        try {
          if (uncompressIn != null)
            uncompressIn.close(); 
          if (sourceIn != null)
            sourceIn.close(); 
        } catch (Exception ex) {
          logger.warn("", ex);
        } 
      } 
    } else {
      result = source;
    } 
    return result;
  }
package com.seeyon.ctp.common.po.formula;

import com.seeyon.ctp.common.formula.enums.DataType;
import com.seeyon.ctp.common.formula.enums.FormulaType;
import com.seeyon.ctp.common.po.BasePO;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import net.sf.json.JSONObject;
import www.seeyon.com.utils.json.JSONUtils;

public class Formula extends BasePO {
  private static final long serialVersionUID = 3050472922776688005L;
  
  private Integer formulaType;
  
  private String formulaAlias;
  
  private String formulaName;
  
  private String description;
  
  private String paramsJson;
  
  private List<FormulaParam> params;
  
  private Integer category;
  
  private String templates;
  
  private String sample;
  
  private String formulaExpression;
  
  private String expectValue;
  
  private Integer dataType;
  
  private Integer returnType;
  
  private Long creator;
  
  private Date createTime;
  
  private Date updateTime;
  
  public Integer getFormulaType() {
    return this.formulaType;
  }
  
  public void setFormulaType(Integer formulaType) {
    this.formulaType = formulaType;
  }
  
  public String getFormulaAlias() {
    return this.formulaAlias;
  }
  
  public void setFormulaAlias(String formulaAlias) {
    this.formulaAlias = formulaAlias;
  }
  
  public String getFormulaName() {
    return this.formulaName;
  }
  
  public void setFormulaName(String formulaName) {
    this.formulaName = formulaName;
  }
  
  public String getDescription() {
    return this.description;
  }
  
  public void setDescription(String description) {
    this.description = description;
  }
  
  public String getParamsJson() {
    return this.paramsJson;
  }
  
  public void setParamsJson(String paramsJson) {
    this.paramsJson = paramsJson;
    if (null != getParamsJson() && !"".equals(getParamsJson())) {
      List<FormulaParam> params = new ArrayList<FormulaParam>();
      Object obj = JSONObject.fromObject(getParamsJson()).get("paramsJson");
      List<Object> list = JSONUtils.parseJsonToListBean(obj.toString());
      for (int i = 0; i < list.size(); i++) {
        FormulaParam parm = new FormulaParam();
        JSONObject jsonObject = JSONObject.fromObject(list.get(i));
        parm.setName(jsonObject.getString("name"));
        parm.setType(DataType.getEnum(jsonObject.getInt("type")));
        parm.setDescription(jsonObject.getString("description"));
        params.add(parm);
      } 
      setParams(params);
    } 
  }
  
  public Integer getCategory() {
    return this.category;
  }
  
  public void setCategory(Integer category) {
    this.category = category;
  }
  
  public String getTemplates() {
    return this.templates;
  }
  
  public void setTemplates(String templates) {
    this.templates = templates;
  }
  
  public String getSample() {
    return this.sample;
  }
  
  public void setSample(String sample) {
    this.sample = sample;
  }
  
  public String getFormulaExpression() {
    return this.formulaExpression;
  }
  
  public void setFormulaExpression(String formulaExpression) {
    this.formulaExpression = formulaExpression;
  }
  
  public Long getCreator() {
    return this.creator;
  }
  
  public void setCreator(Long creator) {
    this.creator = creator;
  }
  
  public Date getCreateTime() {
    return this.createTime;
  }
  
  public void setCreateTime(Date createTime) {
    this.createTime = createTime;
  }
  
  public Date getUpdateTime() {
    return this.updateTime;
  }
  
  public void setUpdateTime(Date updateTime) {
    this.updateTime = updateTime;
  }
  
  public String getExpectValue() {
    return this.expectValue;
  }
  
  public void setExpectValue(String expectValue) {
    this.expectValue = expectValue;
  }
  
  public Integer getDataType() {
    return this.dataType;
  }
  
  public void setDataType(Integer dataType) {
    this.dataType = dataType;
    if (null != getDataType())
      setReturnType(getDataType()); 
  }
  
  public Integer getReturnType() {
    return this.returnType;
  }
  
  public void setReturnType(Integer returnType) {
    this.returnType = returnType;
  }
  
  public void setReturnType(Class clazz) {
    if (null != clazz)
      this.returnType = Integer.valueOf(DataType.valueOf(clazz).getKey()); 
  }
  
  public List<FormulaParam> getParams() {
    return this.params;
  }
  
  public void setParams(List<FormulaParam> params) {
    this.params = params;
  }
  
  public String toString() {
    if (this.formulaType.intValue() == FormulaType.GroovyFunction.getKey()) {
      StringBuilder script = new StringBuilder();
      script.append("def static ").append(getFormulaName());
      script.append("(");
      if (this.params != null && this.params.size() != 0) {
        int len = this.params.size();
        for (int i = 0; i < len; i++) {
          script.append(" ").append(((FormulaParam)this.params.get(i)).getName());
          if (i < len - 1)
            script.append(","); 
        } 
      } 
      script.append(")");
      script.append("{\n");
      script.append(this.formulaExpression);
      script.append("\n}");
      return script.toString();
    } 
    return "";
  }
}

public static void compileFormulas(Collection<Formula> formulas) throws BusinessException {
    StringBuilder script = new StringBuilder("package com.seeyon.ctp.common.formula.runtime;");
    script.append("import com.seeyon.ctp.common.AppContext;");
    script.append("public class CtpFormula{\n");
    for (Formula formula : formulas) {
      Integer formulaType = formula.getFormulaType();
      if (formulaType.intValue() != FormulaType.GroovyFunction.ordinal())
        continue; 
      script.append(formula.toString()).append("\n");
    } 
    script.append(" def static getVar(name){com.seeyon.ctp.common.formula.FormulaUtil.getVar(name)}");
    script.append("}");
    try {
      String s = script.toString();
      LOG.debug("+ s);
      ScriptEvaluator.getInstance().compile(s);
    } catch (Exception e) {
      throw new BusinessException(e);
    } 
  

 
package com.seeyon.ctp.common.formula;

import com.seeyon.ctp.common.AppContext;
import com.seeyon.ctp.common.exceptions.BusinessException;
import com.seeyon.ctp.common.formula.enums.CATEGORY;
import com.seeyon.ctp.common.formula.enums.DataType;
import com.seeyon.ctp.common.formula.enums.FormulaType;
import com.seeyon.ctp.common.formula.manager.FormulaManager;
import com.seeyon.ctp.common.function.CtpFunction;
import com.seeyon.ctp.common.po.formula.Formula;
import com.seeyon.ctp.common.po.formula.FormulaParam;
import com.seeyon.ctp.common.script.ScriptEvaluator;
import com.seeyon.ctp.common.web.util.WebUtil;
import com.seeyon.ctp.util.DateUtil;
import com.seeyon.ctp.util.Strings;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.script.ScriptException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class FormulaUtil {
  private static final Log LOG = LogFactory.getLog(FormulaUtil.class);
  
  private static final Log SCRIPTLOG = LogFactory.getLog("scriptcompile");
  
  private static FormulaManager formulaManager;
  
  public static List<Formula> formulas = new ArrayList<Formula>();
  
  private static Set<String> SCRIPT_BLACKLIST = new HashSet<String>();
  
  private static Map<String, Object> realValues = new ConcurrentHashMap<String, Object>();
  
  static {
    SCRIPT_BLACKLIST.add("System.exit");
    SCRIPT_BLACKLIST.add("Runtime");
    SCRIPT_BLACKLIST.add("ClassLoader");
    SCRIPT_BLACKLIST.add("delete");
  }
  
  public static FormulaManager getFormulaManager() {
    if (formulaManager == null)
      formulaManager = (FormulaManager)AppContext.getBean("formulaManager"); 
    return formulaManager;
  }
  
  public static Map getFormulaContext() {
    Map<Object, Object> context = new HashMap<Object, Object>();
    if (getFormulaManager() != null) {
      List<Formula> allVariable = getFormulaManager().getAllVariable();
      for (Formula formula : allVariable) {
        if (FormulaType.Constant.ordinal() == formula.getFormulaType().intValue())
          context.put(formula.getFormulaName(), formula.getFormulaExpression()); 
      } 
    } 
    return context;
  }
  
  public static void compileFormulas() {}
  
  public static void compileFormulas(Collection<Formula> formulas) throws BusinessException {
    StringBuilder script = new StringBuilder("package com.seeyon.ctp.common.formula.runtime;");
    script.append("import com.seeyon.ctp.common.AppContext;");
    script.append("public class CtpFormula{\n");
    for (Formula formula : formulas) {
      Integer formulaType = formula.getFormulaType();
      if (formulaType.intValue() != FormulaType.GroovyFunction.ordinal())
        continue; 
      script.append(formula.toString()).append("\n");
    } 
    script.append(" def static getVar(name){com.seeyon.ctp.common.formula.FormulaUtil.getVar(name)}");
    script.append("}");
    try {
      String s = script.toString();
      LOG.debug("+ s);
      ScriptEvaluator.getInstance().compile(s);
    } catch (Exception e) {
      throw new BusinessException(e);
    } 
  }
  
  public static boolean isValidate(String script) {
    return false;
  }
  
  public static Object getVar(String variableName) throws ScriptException, BusinessException {
    List<Formula> allVariable = getFormulaManager().getAllVariable();
    for (Formula formulaPo : allVariable) {
      if (formulaPo.getFormulaName().equals(variableName)) {
        if (formulaPo.getFormulaType().intValue() == FormulaType.Constant.ordinal()) {
          Object value = realValues.get(formulaPo.getFormulaName());
          if (value != null)
            return value; 
          value = eval(formulaPo.getFormulaExpression(), new HashMap<Object, Object>());
          if (value != null)
            realValues.put(formulaPo.getFormulaName(), value); 
          return value;
        } 
        if (formulaPo.getFormulaType().intValue() == FormulaType.Variable.ordinal())
          return eval(formulaPo.getFormulaExpression(), new HashMap<Object, Object>()); 
      } 
    } 
    return null;
  }
  
  public static void updateRealValue(Formula formulaPo) throws ScriptException, BusinessException {
    if (formulaPo.getFormulaType().intValue() == FormulaType.Constant.ordinal()) {
      Object value = eval(formulaPo.getFormulaExpression(), new HashMap<Object, Object>());
      if (value != null)
        realValues.put(formulaPo.getFormulaName(), value); 
    } 
  }
  
  public static Formula getVarFormulaByName(String variableName) {
    List<Formula> allVariable = getFormulaManager().getAllVariable();
    for (Formula formulaPo : allVariable) {
      if (formulaPo.getFormulaName().equals(variableName))
        return formulaPo; 
    } 
    return null;
  }
  
  public static boolean isGroovyFunction(Formula formula) throws BusinessException {
    return (FormulaType.GroovyFunction.getKey() == formula.getFormulaType().intValue());
  }
  
  public static boolean isJavaFunction(Formula formula) throws BusinessException {
    return (FormulaType.JavaFunction.getKey() == formula.getFormulaType().intValue());
  }
  
  public static boolean isVaraible(Formula formula) throws BusinessException {
    if (isJavaFunction(formula) || isGroovyFunction(formula))
      return false; 
    return !isConstant(formula);
  }
  
  public static boolean isConstant(Formula formula) throws BusinessException {
    if (isJavaFunction(formula) || isGroovyFunction(formula))
      return false; 
    try {
      String expr = formula.getFormulaExpression();
      Object value = eval(expr, new HashMap<Object, Object>());
      return (expr.equals(String.valueOf(value)) || expr.equals("\"" + value + "\""));
    } catch (ScriptException e) {
      LOG.error(", e);
      throw new BusinessException("+ e.getLocalizedMessage());
    } 
  }
  
  public static Formula transform(CtpFunction func) {
    Formula formula = new Formula();
    formula.setDescription(formula.getDescription());
    formula.setFormulaAlias(func.getTitle());
    formula.setFormulaName(func.getAliases());
    formula.setCategory(Integer.valueOf(CATEGORY.Global.getKey()));
    formula.setFormulaType(Integer.valueOf(FormulaType.JavaFunction.getKey()));
    formula.setReturnType(func.getMethod().getReturnType());
    List<FormulaParam> params = new ArrayList<FormulaParam>();
    formula.setIdIfNew();
    formula.setParams(params);
    return formula;
  }
  
  public static void initFunctions(Collection<CtpFunction> functions) {
    for (CtpFunction func : functions)
      formulas.add(transform(func)); 
  }
  
  public static boolean validate(Formula formula, String expression, Map context, boolean isSave) throws BusinessException {
    Object obj = null;
    if (formula.getFormulaType().intValue() != FormulaType.GroovyFunction.getKey())
      return false; 
    try {
      String script = formula.toString() + ";" + expression;
      obj = eval(script, context);
    } catch (ScriptException e) {
      LOG.error(", e);
      throw new BusinessException("+ e.getLocalizedMessage());
    } catch (StackOverflowError e) {
      LOG.error(", e);
      throw new BusinessException(");
    } 
    if (!isSave && 
      null != formula.getExpectValue())
      if (formula.getReturnType().intValue() == DataType.DateTime.getKey()) {
        try {
          if (!DateUtil.parse(formula.getExpectValue()).equals(obj))
            throw new BusinessException("); 
        } catch (ParseException e) {
          LOG.error(e.getLocalizedMessage(), e);
          throw new BusinessException(");
        } 
      } else if (!obj.toString().equals(formula.getExpectValue())) {
        throw new BusinessException(");
      }  
    return true;
  }
  
  private static void log(String script) throws BusinessException {
    for (String keyword : SCRIPT_BLACKLIST) {
      if (script.indexOf(keyword) > -1)
        throw new BusinessException("+ keyword); 
    } 
    SCRIPTLOG.info(Strings.getRemoteAddr(WebUtil.getRequest()) + "," + AppContext.currentUserLoginName() + "," + script);
  }
  
  public static boolean validate(Formula formula) throws BusinessException {
    if (formula.getFormulaType().intValue() == FormulaType.GroovyFunction.getKey())
      return validate(formula, formula.getSample(), new HashMap<Object, Object>(), false); 
    if (formula.getFormulaType().intValue() == FormulaType.Variable.getKey()) {
      Object value;
      String expr = formula.getFormulaExpression();
      try {
        log(expr);
        value = ScriptEvaluator.getInstance().eval(expr, new HashMap<Object, Object>());
      } catch (ScriptException e) {
        LOG.error(", e);
        throw new BusinessException("+ e.getLocalizedMessage());
      } catch (StackOverflowError e) {
        LOG.error(", e);
        throw new BusinessException(");
      } 
      return (expr.equals(String.valueOf(value)) || expr.equals("\"" + value + "\""));
    } 
    return false;
  }
  
  public static Object eval(String scriptText, Map<?, ?> context) throws ScriptException, BusinessException {
    log(scriptText);
    Map<Object, Object> ctx = new HashMap<Object, Object>(getFormulaContext());
    ctx.putAll(context);
    return ScriptEvaluator.getInstance().eval("import com.seeyon.ctp.common.AppContext;import static com.seeyon.ctp.common.formula.runtime.CtpFormula.*;" + scriptText, ctx);
  }
}

1 Like

鉴于自己构造自定义payload不方便写了一个工具直接构造效果。工具默认使用冰蝎3.0 网马。shell名字换了一下默认:


也可以使用自定义构造payload。shell名称和shell内容自定义

实战效果

工具下载:
致远OA未授权上传payload构造器.zip (4.2 MB)

1 Like

感谢分析与分享!正好最近在深入分析这个洞的原理,感谢指点!

1 Like

所以说归根结底这不是一个任意文件上传,而是个任意代码执行。

1 Like