struts2任意文件上传分析

渗透技巧 11个月前 admin
202 0 0

漏洞说明

于Struts框架在处理参数名称大小写方面的不一致性,导致攻击者能够通过修改参数名称的大小写来利用目录遍历技术(如使用../路径)上传文件到服务器的非预期位置。

漏洞复现

环境介绍 2.5.32

struts2任意文件上传分析

漏洞分析

struts2在处理http请求的时候,会经过很多的自身实现的拦截器等,这里下个断点,调用栈如下

uploadFile:39, UploadAction (org.test)invoke:-1, GeneratedMethodAccessor55 (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)invokeMethodInsideSandbox:1266, OgnlRuntime (ognl)invokeMethod:1251, OgnlRuntime (ognl)callAppropriateMethod:1969, OgnlRuntime (ognl)callMethod:68, ObjectMethodAccessor (ognl)callMethodWithDebugInfo:98, XWorkMethodAccessor (com.opensymphony.xwork2.ognl.accessor)callMethod:90, XWorkMethodAccessor (com.opensymphony.xwork2.ognl.accessor)callMethod:2045, OgnlRuntime (ognl)getValueBody:97, ASTMethod (ognl)evaluateGetValueBody:212, SimpleNode (ognl)getValue:258, SimpleNode (ognl)getValue:537, Ognl (ognl)getValue:501, Ognl (ognl)execute:492, OgnlUtil$3 (com.opensymphony.xwork2.ognl)compileAndExecuteMethod:544, OgnlUtil (com.opensymphony.xwork2.ognl)callMethod:490, OgnlUtil (com.opensymphony.xwork2.ognl)invokeAction:438, DefaultActionInvocation (com.opensymphony.xwork2)invokeActionOnly:293, DefaultActionInvocation (com.opensymphony.xwork2)invoke:254, DefaultActionInvocation (com.opensymphony.xwork2)intercept:250, DebuggingInterceptor (org.apache.struts2.interceptor.debugging)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)doIntercept:179, DefaultWorkflowInterceptor (com.opensymphony.xwork2.interceptor)intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)doIntercept:263, ValidationInterceptor (com.opensymphony.xwork2.validator)doIntercept:49, AnnotationValidationInterceptor (org.apache.struts2.interceptor.validation)intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)doIntercept:142, ConversionErrorInterceptor (com.opensymphony.xwork2.interceptor)intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)doIntercept:140, ParametersInterceptor (com.opensymphony.xwork2.interceptor)intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)doIntercept:140, ParametersInterceptor (com.opensymphony.xwork2.interceptor)intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:201, StaticParametersInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:67, MultiselectInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:133, DateTextFieldInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:89, CheckboxInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:321, FileUploadInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:101, ModelDrivenInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:142, ScopedModelDrivenInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:160, ChainingInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)doIntercept:175, PrepareInterceptor (com.opensymphony.xwork2.interceptor)intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:121, I18nInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:167, ServletConfigInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:228, AliasInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:196, ExceptionMappingInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)execute:48, StrutsActionProxy (org.apache.struts2.factory)serviceAction:574, Dispatcher (org.apache.struts2.dispatcher)executeAction:79, ExecuteOperations (org.apache.struts2.dispatcher)doFilter:141, StrutsPrepareAndExecuteFilter (org.apache.struts2.dispatcher.filter)internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)doFilter:162, ApplicationFilterChain (org.apache.catalina.core)invoke:197, StandardWrapperValve (org.apache.catalina.core)invoke:97, StandardContextValve (org.apache.catalina.core)invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)invoke:135, StandardHostValve (org.apache.catalina.core)invoke:92, ErrorReportValve (org.apache.catalina.valves)invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)invoke:78, StandardEngineValve (org.apache.catalina.core)service:360, CoyoteAdapter (org.apache.catalina.connector)service:399, Http11Processor (org.apache.coyote.http11)process:65, AbstractProcessorLight (org.apache.coyote)process:889, AbstractProtocol$ConnectionHandler (org.apache.coyote)doRun:1743, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)run:49, SocketProcessorBase (org.apache.tomcat.util.net)runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)run:745, Thread (java.lang)

 

 

struts2本身存在的漏洞问题,所以我们只需要关注struts2相关的拦截器包,找到了如下拦截器

(1)FileUploadInterceptor

struts2任意文件上传分析
public String intercept(ActionInvocation invocation) throws Exception {    ActionContext ac = invocation.getInvocationContext();    HttpServletRequest request = (HttpServletRequest)ac.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest");    if (!(request instanceof MultiPartRequestWrapper)) {        if (LOG.isDebugEnabled()) {            ActionProxy proxy = invocation.getProxy();            LOG.debug(this.getTextMessage("struts.messages.bypass.request", new String[]{proxy.getNamespace(), proxy.getActionName()}));        }
        return invocation.invoke();    } else {        ValidationAware validation = null;        Object action = invocation.getAction();        if (action instanceof ValidationAware) {            validation = (ValidationAware)action;        }
        MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper)request;        if (multiWrapper.hasErrors() && validation != null) {            TextProvider textProvider = this.getTextProvider(action);
            String errorMessage;            for(Iterator var8 = multiWrapper.getErrors().iterator(); var8.hasNext(); validation.addActionError(errorMessage)) {                LocalizedMessage error = (LocalizedMessage)var8.next();                if (textProvider.hasKey(error.getTextKey())) {                    errorMessage = textProvider.getText(error.getTextKey(), Arrays.asList(error.getArgs()));                } else {                    errorMessage = textProvider.getText("struts.messages.error.uploading", error.getDefaultMessage());                }            }        }
        Enumeration fileParameterNames = multiWrapper.getFileParameterNames();
        while(fileParameterNames != null && fileParameterNames.hasMoreElements()) {            String inputName = (String)fileParameterNames.nextElement();            String[] contentType = multiWrapper.getContentTypes(inputName);            if (!this.isNonEmpty(contentType)) {                if (LOG.isWarnEnabled()) {                    LOG.warn(this.getTextMessage(action, "struts.messages.invalid.content.type", new String[]{inputName}));                }            } else {                String[] fileName = multiWrapper.getFileNames(inputName);                if (!this.isNonEmpty(fileName)) {                    if (LOG.isWarnEnabled()) {                        LOG.warn(this.getTextMessage(action, "struts.messages.invalid.file", new String[]{inputName}));                    }                } else {                    UploadedFile[] files = multiWrapper.getFiles(inputName);                    if (files != null && files.length > 0) {                        List<UploadedFile> acceptedFiles = new ArrayList(files.length);                        List<String> acceptedContentTypes = new ArrayList(files.length);                        List<String> acceptedFileNames = new ArrayList(files.length);                        String contentTypeName = inputName + "ContentType";                        String fileNameName = inputName + "FileName";
                        for(int index = 0; index < files.length; ++index) {                            if (this.acceptFile(action, files[index], fileName[index], contentType[index], inputName, validation)) {                                acceptedFiles.add(files[index]);                                acceptedContentTypes.add(contentType[index]);                                acceptedFileNames.add(fileName[index]);                            }                        }
                        if (!acceptedFiles.isEmpty()) {                            Map<String, Parameter> newParams = new HashMap();                            newParams.put(inputName, new Parameter.File(inputName, acceptedFiles.toArray(new UploadedFile[acceptedFiles.size()])));                            newParams.put(contentTypeName, new Parameter.File(contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()])));                            newParams.put(fileNameName, new Parameter.File(fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()])));                            ac.getParameters().appendAll(newParams);                        }                    }                }            }        }
        return invocation.invoke();    }}

 

 

流程大致处理如下

(1)从context获取HttpServletRequest,判断是否为MultiPartRequestWrapper类型(2)从文件上传请求中获取Content-Typefilename等请求参数(3)经过一系列的处理以后,将文件名保存到HttpParameters.parameters#ac.getParameters().appendAll(newParams);

 

 

struts2任意文件上传分析

代码中发现这一行

String contentTypeName = inputName + "ContentType";String fileNameName = inputName + "FileName";其中inputName获取如下,也就是fileNameName这里是可控的Enumeration fileParameterNames = multiWrapper.getFileParameterNames();while(fileParameterNames != null && fileParameterNames.hasMoreElements()) {String inputName = (String)fileParameterNames.nextElement();

 

(2)ParametersInterceptor

这里通过treemap.put将参数put到acceptableParameters变量中

struts2任意文件上传分析

参数限制参考https://struts.apache.org/security/#accepted–excluded-patterns

struts2任意文件上传分析

调用newStack.setParameter(name, value.getObject());传递参数

struts2任意文件上传分析

看如下调用栈

isAccessible:157, DefaultMemberAccess (ognl)isAccessible:118, SecurityMemberAccess (com.opensymphony.xwork2.ognl)isMethodAccessible:2850, OgnlRuntime (ognl)hasSetMethod:2952, OgnlRuntime (ognl)hasSetProperty:2970, OgnlRuntime (ognl)setProperty:83, CompoundRootAccessor (com.opensymphony.xwork2.ognl.accessor)setProperty:3356, OgnlRuntime (ognl)setValueBody:134, ASTProperty (ognl)evaluateSetValueBody:220, SimpleNode (ognl)setValue:308, SimpleNode (ognl)setValue:780, Ognl (ognl)execute:436, OgnlUtil$1 (com.opensymphony.xwork2.ognl)execute:428, OgnlUtil$1 (com.opensymphony.xwork2.ognl)compileAndExecute:523, OgnlUtil (com.opensymphony.xwork2.ognl)setValue:428, OgnlUtil (com.opensymphony.xwork2.ognl)trySetValue:186, OgnlValueStack (com.opensymphony.xwork2.ognl)setValue:173, OgnlValueStack (com.opensymphony.xwork2.ognl)setParameter:157, OgnlValueStack (com.opensymphony.xwork2.ognl)setParameters:214, ParametersInterceptor (com.opensymphony.xwork2.interceptor)doIntercept:132, ParametersInterceptor (com.opensymphony.xwork2.interceptor)intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)doIntercept:140, ParametersInterceptor (com.opensymphony.xwork2.interceptor)

 

 

这里是反射调用set方法并赋值。

struts2任意文件上传分析

我们观察我们的struts2的上传代码,结果呼之欲出,如果我们可以调用setUploadFileName,那么就能完成文件上传文件名控制的操作!

struts2任意文件上传分析 struts2任意文件上传分析

当第一次访问文件上传的action

addIfAccessor:2728, OgnlRuntime (ognl)collectAccessors:2715, OgnlRuntime (ognl)getDeclaredMethods:2682, OgnlRuntime (ognl)_getSetMethod:2912, OgnlRuntime (ognl)getSetMethod:2881, OgnlRuntime (ognl)hasSetMethod:2952, OgnlRuntime (ognl)hasSetProperty:2970, OgnlRuntime (ognl)setProperty:83, CompoundRootAccessor (com.opensymphony.xwork2.ognl.accessor)setProperty:3356, OgnlRuntime (ognl)setValueBody:134, ASTProperty (ognl)evaluateSetValueBody:220, SimpleNode (ognl)setValue:308, SimpleNode (ognl)setValue:780, Ognl (ognl)execute:436, OgnlUtil$1 (com.opensymphony.xwork2.ognl)execute:428, OgnlUtil$1 (com.opensymphony.xwork2.ognl)compileAndExecute:523, OgnlUtil (com.opensymphony.xwork2.ognl)setValue:428, OgnlUtil (com.opensymphony.xwork2.ognl)trySetValue:186, OgnlValueStack (com.opensymphony.xwork2.ognl)setValue:173, OgnlValueStack (com.opensymphony.xwork2.ognl)setParameter:157, OgnlValueStack (com.opensymphony.xwork2.ognl)setParameters:214, ParametersInterceptor (com.opensymphony.xwork2.interceptor)doIntercept:132, ParametersInterceptor (com.opensymphony.xwork2.interceptor)intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)doIntercept:140, ParametersInterceptor (com.opensymphony.xwork2.interceptor)intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:201, StaticParametersInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:67, MultiselectInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:133, DateTextFieldInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:89, CheckboxInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:321, FileUploadInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:101, ModelDrivenInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:142, ScopedModelDrivenInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:160, ChainingInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)doIntercept:175, PrepareInterceptor (com.opensymphony.xwork2.interceptor)intercept:99, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:121, I18nInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:167, ServletConfigInterceptor (org.apache.struts2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:228, AliasInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)intercept:196, ExceptionMappingInterceptor (com.opensymphony.xwork2.interceptor)invoke:249, DefaultActionInvocation (com.opensymphony.xwork2)execute:48, StrutsActionProxy (org.apache.struts2.factory)serviceAction:574, Dispatcher (org.apache.struts2.dispatcher)executeAction:79, ExecuteOperations (org.apache.struts2.dispatcher)doFilter:141, StrutsPrepareAndExecuteFilter (org.apache.struts2.dispatcher.filter)internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)doFilter:162, ApplicationFilterChain (org.apache.catalina.core)invoke:197, StandardWrapperValve (org.apache.catalina.core)invoke:97, StandardContextValve (org.apache.catalina.core)invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)invoke:135, StandardHostValve (org.apache.catalina.core)invoke:92, ErrorReportValve (org.apache.catalina.valves)invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)invoke:78, StandardEngineValve (org.apache.catalina.core)service:360, CoyoteAdapter (org.apache.catalina.connector)service:399, Http11Processor (org.apache.coyote.http11)process:65, AbstractProcessorLight (org.apache.coyote)process:889, AbstractProtocol$ConnectionHandler (org.apache.coyote)doRun:1743, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)run:49, SocketProcessorBase (org.apache.tomcat.util.net)runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)run:745, Thread (java.lang)

这里如果第一个字母小写,第二个大写 则直接返回propertyName。否则就将第一个字母变为大写,然后返回propertyName。这里导致我们有以下的写法返回Upload

Uploadupload

 

struts2任意文件上传分析

这里判断是否为baseName结尾,也就是我们可控的setxxx,getxxx方法的xxx结尾。

struts2任意文件上传分析

获取到方法以后,返回OgnlRuntime,这里会将方法push到缓存中,第二次调用就直接从缓存中获取了

struts2任意文件上传分析 struts2任意文件上传分析

注意这里的本质是我们对map中的数值进行进一步的覆盖,所以我们传入的先后顺序有影响。这里赋值的主要关系在于treemap

struts2任意文件上传分析

大写在上,小写在下。所以我们取值的时候,需要将小写的uploadFileName设置为我们的恶意文件名,达到文件名覆盖的目的。

分析发现,是因为struts2通过传入的参数进行get,set filename的方法调用覆盖导致的问题。所以不一定所有的问题参数都是upload,需要根据上传的name去做判断

struts2任意文件上传分析

原文始发于微信公众号(e0m安全屋):struts2任意文件上传分析

版权声明:admin 发表于 2023年12月11日 下午5:42。
转载请注明:struts2任意文件上传分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...