致远OA A8.0 SP2 Ajax.do调用formulaManager任意文件上传漏洞分析
漏洞环境
致远A8.0 SP2 (需要登录)
漏洞复现
1 | POST /seeyon/ajax.do?method=ajaxAction&managerName=formulaManager&requestCompress=gzip HTTP/1.1 |
![]() |
|---|
天蝎连接 http://10.0.103.5/mzr2.jsp sky
![]() |
|---|
漏洞分析
通过EXP可以知道请求了ajax.do这个路由,全局搜索ajax.do找到映射的类
![]() |
|---|
跟入到com.seeyon.ctp.common.service.AjaxController类中,从请求参数method=ajaxAction可以知道调用了AjaxController的ajaxAction方法,接下来分析ajaxAction方法。
1 | public ModelAndView ajaxAction(HttpServletRequest request, HttpServletResponse response) throws Exception { |
这个方法主要作用是接收一个responseCompress,这个参数作用是用于compressResponse压缩的方式,然后调用this.invokeService方法,在不传入ClientRequestPath参数的情况下,对得到的结果进行压缩并返回到前端。
跟入this.invokeService中。
1 | private String invokeService(HttpServletRequest request, HttpServletResponse response) throws BusinessException { |
invokeService方法中首先从request中接收了serviceName、methodName、strArgs、compressType等参数。调用了ZipUtil.uncompressRequest对strArgs进行解压缩,解压缩的格式为传入的compressType。
接着用serviceName作为参数调用了getService方法,跟入到getService方法中
1 | private static Object getService(String serviceName) throws Exception { |
这个方法作用就是根据serviceName获取到对应类的已经创建好的实例,但是不能随便获取,必须是已经在配置文件中注册过的才能获取到。大概有八百多个都可以直接调用,但所包含的类中的父类及以上类不能是DataSource、Session、SessionFactory三个类。
![]() |
|---|
请求体传入的为managerName=formulaManager,所获取到的对象就为formulaManager类的实例。
接着返回到invokeService方法中。
在122行
![]() |
|---|
调用了this.invokeMethod(service, methodName, strArgs, serviceName),从方法名大概就猜得到到反射调用了service类的methodName方法。
跟入this.invokeMethod
1 | private Object invokeMethod(Object service, String methodName, String strArgs, String serviceName) throws Exception { |
这个方法中,首先将传入的strArgs参数解析成了Object对象,接着判断这个对象是否为List子类的实例,是的话会将Object强制转换成List对象,否则就会创建一个ArrayList对象。然后将传入的Object添加到ArrayList中。
经接着会serviceName + "_" + methodName + "_" + argsNum作为键值在this.candidateMethodCache中获取已经缓存的方法。如果获取到则会将传入的strArgs和调用this.candidateMethodCache.get(key)的返回值作为参数调用this.findMethodAndArgs((List)l, (List)list)。
如果缓存中没有获取到对应的方法,那么会调用this.judgeCandidate(service, methodName, argsNum)方法反射获取到对应的方法。
接着,跟入this.findMethodAndArgs方法
1 | private Map findMethodAndArgs(List paramObject, List<MethodCache> candidateMethods) { |
candidateMethods列表保存的为根据特定条件或者是缓存中获取到的方法集合,这个方法的作用其实就是从candidateMethods列表中,根据传入的paramObject参数列表获取到对应的方法和参数值,会根据参数的值解析成对应的对象。
接着在 464 行 执行了反射调用
![]() |
|---|
以上就是Ajax.do 接口反射调用任意类任意方法及指定参数的分析过程。
通过ajax.do接口指定method参数为ajaxAction调用AjaxController类的ajaxAction方法,这个方法中又调用了AjaxController的invokeService方法,这个方法可以根据类名获取到该类的一个实例,进而调用了AjaxController的invokeMethod方法,进而反射调用该实例的方法。在这个过程中,反射调用所需的类名、方法名、参数都可以从request中获取到。要进行进一步利用需要找到存在一些危险操作的方法进行利用,如文件上传、文件写入、命令执行、代码执行等,如本次利用到的FormulaManagerImpl这个类。
FormulaManagerImpl分析
FormulaManagerImpl.validate(Formula formula, String expression, Map context, boolean isSave)
![]() |
|---|
调用了FormulaUtil.validate,跟入
com.seeyon.ctp.common.formula.FormulaUtil#validate(com.seeyon.ctp.common.po.formula.Formula, java.lang.String, java.util.Map, boolean)
1 | public static boolean validate(Formula formula, String expression, Map context, boolean isSave) throws BusinessException { |
1 | public static Object eval(String scriptText, Map context) throws ScriptException, BusinessException { |
这个方法的作用就是构造groovy脚本,调用eval方法执行。
要利用FormulaUtil.validate方法,需要传入Formula对象、String expression, Map context, boolean isSave。
需要绕过的几个限制
1 | if (formula.getFormulaType() != FormulaType.GroovyFunction.getKey() && formula.getFormulaType() != FormulaType.Variable.getKey()) { |
![]() |
|---|
在执行到eval方法前,Formula对象的formulaType值要为1或者2才不会return出去。
在eval方法中执行了log方法
![]() |
|---|
1 | private static void log(String script) throws BusinessException { |
log方法主要是对脚本中的内容进行检测,不运行字符串中出现如下字符,但是不够严谨,可以用其他方法代替黑名单中的方法。
![]() |
|---|
因为是调用的formula.toString方法
![]() |
|---|
![]() |
|---|
可以通过this.formulaExpression来传入我们想要执行的代码。
最后根据如上条件,我写了一个生成arguments参数的方法,可在payload自定义想要执行的代码。
1 | package com.example.Test; |











