致远OA系统由北京致远互联软件股份有限公司开发,是一款基于互联网高效协作的协同管理软件,在各企业机构中被广泛使用。
1.漏洞介绍致远 OA 系统的一些版本存在代码执行漏洞,攻击者在无需登录的情况下可通过向 URL /seeyon/ajax.do地址发送构造好的POST请求包,造成代码执行,可向目标服务器写入任意文件造成getshell。
影响版本:
致远OA V8.0、V8.0SP1
致远OA V7.1、V7.1SP1
2.漏洞演示发送构造的请求
成功写入shell
3.漏洞原理3.1代码执行根据漏洞触发点,找对应的控制器
/ajax.do 对应的class是com.seeyon.ctp.common.service.AjaxControlle
反编译其对应的jar包,在
com.seeyon.ctp.common.service.AjaxController#invokeService
处,对http请求进行处理,
ZipUtil.uncompressRequest
Gzip解码arguments参数为 utf-8编码的string字符串
Object service = getService(Strings.escapeJavascript(serviceName));
获取formulaManager bean,实现formulaManager接口
接着调用invokeMethod方法
把string类型的arguments转成json
Method.invoke反射调用formulaManagerlmpl的validate方法
堆栈信息:
invokeMethod:591, AjaxController (com.seeyon.ctp.common.service)invokeService:359, AjaxController (com.seeyon.ctp.common.service)ajaxAction:163, AjaxController (com.seeyon.ctp.common.service)invoke:-1, GeneratedMethodAccessor928 (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)invokeNamedMethod:471, MultiActionController (org.springframework.web.servlet.mvc.multiaction)handleRequestInternal:408, MultiActionController (org.springframework.web.servlet.mvc.multiaction)handleRequest:153, AbstractController (org.springframework.web.servlet.mvc)handle:48, SimpleControllerHandlerAdapter (org.springframework.web.servlet.mvc)doDispatch:933, DispatcherServlet (org.springframework.web.servlet)doService:867, DispatcherServlet (org.springframework.web.servlet)processRequest:951, FrameworkServlet (org.springframework.web.servlet)doPost:853, FrameworkServlet (org.springframework.web.servlet)service:660, HttpServlet (javax.servlet.http)service:827, FrameworkServlet (org.springframework.web.servlet)service:741, HttpServlet (javax.servlet.http)internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilter:52, WsFilter (org.apache.tomcat.websocket.server)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilter:47, GenericFilter (com.seeyon.ctp.common.web)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilter:58, CharacterEncodingFilter (com.seeyon.ctp.common.web.filter)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilter:88, CTPSecurityFilter (com.seeyon.ctp.common.web.filter)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilter:38, CTPCsrfGuardFilter (com.seeyon.ctp.common.web.filter)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilterInternal:26, CTPSessionRepositoryFilter (org.springframework.session.web.http)doFilter:80, OncePerRequestFilter (org.springframework.session.web.http)invokeDelegate:343, DelegatingFilterProxy (org.springframework.web.filter)doFilter:260, DelegatingFilterProxy (org.springframework.web.filter)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)invoke:199, StandardWrapperValve (org.apache.catalina.core)invoke:96, StandardContextValve (org.apache.catalina.core)invoke:493, AuthenticatorBase (org.apache.catalina.authenticator)invoke:137, StandardHostValve (org.apache.catalina.core)invoke:81, ErrorReportValve (org.apache.catalina.valves)invoke:87, StandardEngineValve (org.apache.catalina.core)service:343, CoyoteAdapter (org.apache.catalina.connector)service:798, Http11Processor (org.apache.coyote.http11)process:66, AbstractProcessorLight (org.apache.coyote)process:808, AbstractProtocol$ConnectionHandler (org.apache.coyote)doRun:1498, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)run:49, SocketProcessorBase (org.apache.tomcat.util.net)runWorker:1149, ThreadPoolExecutor (java.util.concurrent)run:624, ThreadPoolExecutor$Worker (java.util.concurrent)run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)run:748, Thread (java.lang)跟进formulaManagerlmpl.class
Validate方法为overloading,当参数为4个的时候,调用的是
FormulaUtil.validate方法
跟进seeyon-ctp-core.jar包中,
可以看到,这里对FormulaType做判断,GroovyFunction和Variable为固定值2和1,FormulaType可控。通过后执行eval写入文件。
跟进com.seeyon.ctp.common.formula.FormulaUtil#eval
其最终调用的是com.seeyon.ctp.common.script.ScriptEvaluator#eval
执行代码,写入文件。
run:8, Script1eval:321, GroovyScriptEngineImpl (org.codehaus.groovy.jsr223)eval:72, GroovyCompiledScript (org.codehaus.groovy.jsr223)eval:92, CompiledScript (javax.script)eval:63, CompiledScriptRunner (com.seeyon.ctp.common.script)eval:91, ScriptEvaluator (com.seeyon.ctp.common.script)eval:536, FormulaUtil (com.seeyon.ctp.common.formula)validate:417, FormulaUtil (com.seeyon.ctp.common.formula)validate:481, FormulaManagerImpl (com.seeyon.ctp.common.formula.manager)invoke:-1, FormulaManagerImpl$$FastClassBySpringCGLIB$$cf3c5088 (com.seeyon.ctp.common.formula.manager)invoke:204, MethodProxy (org.springframework.cglib.proxy)invokeJoinpoint:701, CglibAopProxy$CglibMethodInvocation (org.springframework.aop.framework)proceed:150, ReflectiveMethodInvocation (org.springframework.aop.framework)proceedWithInvocation:103, CTPTransactionInterceptor$1 (org.springframework.transaction.interceptor)invokeWithinTransaction:133, CTPTransactionInterceptor (org.springframework.transaction.interceptor)invoke:101, CTPTransactionInterceptor (org.springframework.transaction.interceptor)proceed:172, ReflectiveMethodInvocation (org.springframework.aop.framework)invoke:91, ExposeInvocationInterceptor (org.springframework.aop.interceptor)proceed:172, ReflectiveMethodInvocation (org.springframework.aop.framework)intercept:633, CglibAopProxy$DynamicAdvisedInterceptor (org.springframework.aop.framework)validate:-1, FormulaManagerImpl$$EnhancerBySpringCGLIB$$fecdef2d (com.seeyon.ctp.common.formula.manager)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)invokeMethod:591, AjaxController (com.seeyon.ctp.common.service)invokeService:359, AjaxController (com.seeyon.ctp.common.service)ajaxAction:163, AjaxController (com.seeyon.ctp.common.service)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)invokeNamedMethod:471, MultiActionController (org.springframework.web.servlet.mvc.multiaction)handleRequestInternal:408, MultiActionController (org.springframework.web.servlet.mvc.multiaction)handleRequest:153, AbstractController (org.springframework.web.servlet.mvc)handle:48, SimpleControllerHandlerAdapter (org.springframework.web.servlet.mvc)doDispatch:933, DispatcherServlet (org.springframework.web.servlet)doService:867, DispatcherServlet (org.springframework.web.servlet)processRequest:951, FrameworkServlet (org.springframework.web.servlet)doPost:853, FrameworkServlet (org.springframework.web.servlet)service:660, HttpServlet (javax.servlet.http)service:827, FrameworkServlet (org.springframework.web.servlet)service:741, HttpServlet (javax.servlet.http)internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilter:52, WsFilter (org.apache.tomcat.websocket.server)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilter:47, GenericFilter (com.seeyon.ctp.common.web)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilter:58, CharacterEncodingFilter (com.seeyon.ctp.common.web.filter)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilter:88, CTPSecurityFilter (com.seeyon.ctp.common.web.filter)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilter:38, CTPCsrfGuardFilter (com.seeyon.ctp.common.web.filter)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)doFilterInternal:26, CTPSessionRepositoryFilter (org.springframework.session.web.http)doFilter:80, OncePerRequestFilter (org.springframework.session.web.http)invokeDelegate:343, DelegatingFilterProxy (org.springframework.web.filter)doFilter:260, DelegatingFilterProxy (org.springframework.web.filter)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)invoke:199, StandardWrapperValve (org.apache.catalina.core)invoke:96, StandardContextValve (org.apache.catalina.core)invoke:493, AuthenticatorBase (org.apache.catalina.authenticator)invoke:137, StandardHostValve (org.apache.catalina.core)invoke:81, ErrorReportValve (org.apache.catalina.valves)invoke:87, StandardEngineValve (org.apache.catalina.core)service:343, CoyoteAdapter (org.apache.catalina.connector)service:798, Http11Processor (org.apache.coyote.http11)process:66, AbstractProcessorLight (org.apache.coyote)process:808, AbstractProtocol$ConnectionHandler (org.apache.coyote)doRun:1498, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)run:49, SocketProcessorBase (org.apache.tomcat.util.net)runWorker:1149, ThreadPoolExecutor (java.util.concurrent)run:624, ThreadPoolExecutor$Worker (java.util.concurrent)run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)run:748, Thread (java.lang)3.2未授权在tomcat中,在接收到请求时会对客户端提交的参数、URL、Header和Body数据进行解析,并生成Request对象,在Servlet处理URL请求的路径时,HTTPServletRequest有如下几个常用的函数:
request.getRequestURL():返回全路径;
request.getRequestURI():返回除去Host(域名或IP)部分的路径;
request.getContextPath():返回工程名部分,如果工程映射为/,则返回为空;
request.getServletPath():返回除去Host和工程名部分的路径;
request.getPathInfo():仅返回传递到Servlet的路径,如果没有传递额外的路径信息,则此返回Null;
回到seeyon代码,查看其web.xml中配置的filter,可以发现,所以的.do都会经过dispatcherServlet,CSRFGuard,SecurityFilter,encodingFilter,从命名来看,猜测主要的权限判断在SecurityFilter。
跟进SecurityFilter
com.seeyon.ctp.common.web.filter.CTPSecurityFilter#doFilter
根据filter链,如果access=true,则可以filterChain.doFilter下一个filter,
只要accept = authenticator.authenticate(req, resp);为真,就能满足条件。
跟进authenticate方法,可以看到,方法内会检查当前用户,是否在线和用户权限等情况,当用户为空的时候,会检查当前访问地址,是否需要权限认证
if (user == null) {AppContext.removeThreadContext("SESSION_CONTEXT_USERINFO_KEY");isAnnotationNeedlessLogin = this.isNeedlessCheckLogin(context);if (!isAnnotationNeedlessLogin) {LoginTokenUtil.checkLoginToken(request);}} else {isGuest = user.isGuest();OnlineUser onlineUser = !isGuest ? OnlineRecorder.getOnlineUser(user) : null;isOnlineMember = !isGuest && onlineUser != null && onlineUser.getSessionIds().contains(user.getSessionId());if (!isOnlineMember) {isAnnotationNeedlessLogin = this.isNeedlessCheckLogin(context);}AppContext.putThreadContext("SESSION_CONTEXT_USERINFO_KEY", user);}在代码中可以看到,isNeedlessCheckLogin是做权限判断,继续跟进。
private boolean isNeedlessCheckLogin(CTPRequestContext context) throws BusinessException {HttpServletRequest request = context.getRequest();String accessUrl = request.getRequestURI();String method = this.getRealMethodName(context);if (context.isAjax()) {accessUrl = context.getParameter("managerName");}Map needlessUrlMap = this.checkLoginAnnotationAware.getNeedlessUrlMap();Set keys = needlessUrlMap.keySet();boolean needlessUrl = false;Iterator var8 = keys.iterator();while(var8.hasNext()) {String key = (String)var8.next();if (accessUrl.indexOf(key) != -1) {Set methods = (Set)needlessUrlMap.get(key);needlessUrl = methods.contains("*") || methods.contains(method);if (needlessUrl) {break;}}}return needlessUrl;}该方法通过getRequestURI去获取用户请求地址,再和白名单中不需要认证的路由地址做判断,如果accessUrl的值中包含白名单中的值,则放行。
可以看到未对uri做处理,至此,造成未授权,结合前面的代码执行漏洞,造成直接写入shell。
代码审计 java