How I Chained 4 Bugs (Features?) into RCE on Amazon Collaboration System

preview

Hi! This is the case study in my Black Hat USA 2018 and DEFCON 26 talk, you can also check slides here:

In past two years, I started to pay more attention on the “inconsistency” bug. What’s that? It’s just like my SSRF talk in Black Hat and GitHub SSRF to RCE case last year, finding inconsistency between the URL parser and the URL fetcher that leads to whole SSRF bypass!

There is also another very cool article Bypassing Web-Application Firewalls by abusing SSL/TLS to illustrate how “inconsistency” be awesome by @0x09AL

So this year, I started focus on the “inconsistency” which lies in the path parser and path normalization!

It’s hard to write a well-designed parser. Different entity has its own standard and implementation. In order to fix a bug without impacting business logic, it’s common to apply a work-around or a filter instead of patching the bug directly. Therefore, if there is any inconsistency between the filter and the called method, the security mechanism can be easily bypassed!

While I was reading advisories, I noticed a feature called URL Path Parameter. Some researchers have already pointed out this feature may lead to security issues, but it still depends on the programming failure! With a little bit mind-mapping, I found this feature could be perfectly applied on multi-layered architectures, and this is vulnerable by default without any coding failure. If you are using reverse proxy with Java as your back-end service, you are under threat!

Back to 2015, it was the first time I found this attack surface was during in a red teaming. After that, I realized this was really cool and I’m curious about how many people know that. So I made a challenge for WCTF 2016.

P.S. I have checked scanners in DirBuster, wFuzz, DirB and DirSearch. Until now, only DirSearch joined the pattern on 1 May, 2017

WCTF is a competition held by Belluminar and 360. It’s not similar to general Jeopardy or Attack & Defense in other CTF competitions. It invites top 10 teams from all over the world, and every team needs to design two challenges, so there are 20 challenges! The more challenges you solved, the more points you got. However, no one solved my challenge during the competition. Therefore, I think this trick may not be well-known!

This year, I decide to share this technique. In order to convince review boards this is awesome, I need more cases to prove it works! So I started hunting bugs! It turns out that, this attack surface can not only leak information but also bypass ACL(Such as my Uber OneLogin bypass case) and lead to RCE in several bug bounty programs. This post is one of them!

If you are interested in other stories, please check the slide ASAP!!!

↓ The inconsistency in multi-layered architectures!

Foreword

First, thanks Amazon for the open-minded vulnerability disclosure. It’s a really good experience working with Amazon security team(so does Nuxeo team). From the Timeline, you can see how quick Amazon’s response was and the step they have taken!

The whole story started with a domain collaborate-corp.amazon.com. It seems to be a collaboration system for internal purpose. From the copyright in the bottom, we know this system was built from an open source project Nuxeo. It’s a very huge Java project, and I was just wanting to improve my Java auditing skill. So the story begins from that…!

Bugs

For me, when I get a Java source, the first thing is to read the pom.xml and find if there are any outdated packages. In Java ecosystem, most vulnerabilities are due to the OWASP Top 10 - A9. known vulnerable components.

Is there any Struts2, FastJSON, XStream or components with deserialization bugs before? If yes. Congratz!

In Nuxeo, it seems most of packages are up to date. But I find a old friend - Seam Framework. Seam is a web application framework developed by JBoss, and a division of Red Hat. It HAD BEEN a popular web framework several years ago, but there are still lots of applications based on Seam :P

I have reviewed Seam in 2016 and found numerous hacker-friendly features! (Sorry, it’s only in Chinese) However, it looks like we can not direct access the Seam part. But still remark on this, and keep on going!

1. Path normalization bug leads to ACL bypass

While looking at the access control from WEB-INF/web.xml, we find Nuxeo uses a custom authentication filter NuxeoAuthenticationFilter and maps /* to that . From the filter we know most pages require authentication, but there is a whitelist allowed few entrance such as login.jsp. All of that is implemented in a method bypassAuth.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected boolean bypassAuth(HttpServletRequest httpRequest) {

// init unAuthenticatedURLPrefix

try {
unAuthenticatedURLPrefixLock.readLock().lock();
String requestPage = getRequestedPage(httpRequest);
for (String prefix : unAuthenticatedURLPrefix) {
if (requestPage.startsWith(prefix)) {
return true;
}
}
} finally {
unAuthenticatedURLPrefixLock.readLock().unlock();
}

// ...

return false;
}

As you can see, bypassAuth retrieves the current requested page to compare with unAuthenticatedURLPrefix. But how bypassAuth retrieves current requested page? Nuxeo writes a method to extract requested page from HttpServletRequest.RequestURI, and the first problem appears here!

1
2
3
4
5
6
7
protected static String getRequestedPage(HttpServletRequest httpRequest) {
String requestURI = httpRequest.getRequestURI();
String context = httpRequest.getContextPath() + '/';
String requestedPage = requestURI.substring(context.length());
int i = requestedPage.indexOf(';');
return i == -1 ? requestedPage : requestedPage.substring(0, i);
}

In order to handle URL path parameter, Nuxeo truncates all the trailing parts by semicolon. But the behaviors in URL path parameter are various. Each web server has it’s own implementation. The Nuxeo’s way may be safe in containers like WildFly, JBoss and WebLogic. But it runs under Tomcat! So the difference between the method getRequestedPage and the Servlet Container leads to security problems!

Due to the truncation, we can forge a request that matches the whitelist in ACL but reach the unauthorized area in Servlet!

In here, we choose login.jsp as our prefix! The ACL bypass may look like this:

1
2
3
4
5
6
7
8
$ curl -I https://collaborate-corp.amazon.com/nuxeo/[unauthorized_area]
HTTP/1.1 302 Found
Location: login.jsp
...

$ curl -I https://collaborate-corp.amazon.com/nuxeo/login.jsp;/..;/[unauthorized_area]
HTTP/1.1 500 Internal Server Error
...

As you can see, we bypass the redirection for authentication, but most pages still return a 500 error. It’s because the servlet logic is unable to obtain a valid user principle so it throws a Java NullPointerException. Even though, this still gives us a chance to knock the door!

P.S. Although there is a quicker way to open the door, it’s still worth to write down the first try!

2. Code reuse feature leads to partial EL invocation

As I mentioned before, there are numerous hacker-friendly features in Seam framework. So, for me, the next step is chaining the first bug to access unauthorized Seam servlet!

In the following sections, I will explain these “features” one by one in detail!

In order to control where browser should be redirected, Seam introduces a series of HTTP parameter, and it is also buggy in these HTTP parameters… actionOutcome is one of them. In 2013, @meder found a remote code execution on that. You can read the awesome article CVE-2010-1871: JBoss Seam Framework remote code execution for details! But today, we are going to talk about another one - actionMethod!

actionMethod is a special parameter that can invoke specific JBoss EL(Expression Language) from query string. It seems dangerous but there are some preconditions before the invocation. The detailed implementation can found in method callAction. In order to invoke the EL, it must satisfy the following preconditions:

  1. The value of actionMethod must be a pair which looks like FILENAME:EL_CODE
  2. The FILENAME part must be a real file under context-root
  3. The file FILENAME must have the content "#{EL_CODE}" in it (double quotes and are required)

For example: There is a file named login.xhtml under context-root.

1
2
3
4
5
6
7
8
9
10
<div class="entry">
<div class="label">
<h:outputLabel id="UsernameLabel" for="username">Username:</h:outputLabel>
</div>
<div class="input">
<s:decorate id="usernameDecorate">
<h:inputText id="username" value="#{user.username}" required="true"></h:inputText>
</s:decorate>
</div>
</div>

You can invoke the EL user.username by URL

1
http://host/whatever.xhtml?actionMethod=/foo.xhtml:user.username

3. Double evaluation leads to EL injection

The previous feature looks eligible. You can not control any file under context-root so that you can’t invoke arbitrary EL on remote server. However, here is one more crazy feature…

To make things worse, if the previous one returns a string, and the string looks like an EL. Seam framework will invoke again!

Here is the detailed call stack:

  1. callAction(Pages.java)
  2. handleOutcome(Pages.java)
  3. handleNavigation(SeamNavigationHandler.java)
  4. interpolateAndRedirect(FacesManager.java)
  5. interpolate(Interpolator.java)
  6. interpolateExpressions(Interpolator.java)
  7. createValueExpression(Expressions.java)

With this crazy feature. We can execute arbitrary EL if we can control the returned value! This is very similar to ROP(Return-Oriented Programming) in binary exploitation. So we need to find a good gadget!

In this case, we choose the gadget under widgets/suggest_add_new_directory_entry_iframe.xhtml

1
2
3
4
5
6
7
<nxu:set var="directoryNameForPopup"
value="#{request.getParameter('directoryNameForPopup')}"
cache="true">
<nxu:set var="directoryNameForPopup"
value="#{nxu:test(empty directoryNameForPopup, select2DirectoryActions.directoryName, directoryNameForPopup)}"
cache="true">
<c:if test="#{not empty directoryNameForPopup}">

Why we choose this? It’s because that request.getParameter returns a string that we can control from query string! Although the whole tag is to assign a variable, we can abuse the semantics!

So now, we put our second stage payload in the directoryNameForPopup. With the first bug, we can chain them together to execute arbitrary EL without any authentication! Here is the PoC:

1
2
3
http://host/nuxeo/login.jsp;/..;/create_file.xhtml
?actionMethod=widgets/suggest_add_new_directory_entry_iframe.xhtml:request.getParameter('directoryNameForPopup')
&directoryNameForPopup=/?#{HERE_IS_THE_EL}

Is that over yet? No really! Although we can execute arbitrary EL, we still failed to pop out a shell. Why?

Let’s go to next section!

4. EL blacklist bypass leads to RCE

Seam also knows that EL is insane. Since Seam 2.2.2.Final, there is a new EL blacklist to block dangerous invocations! Unfortunately, Nuxeo uses the latest version of Seam(2.3.1.Final) so that we must find a way to bypass the blacklist. The blacklist can be found in resources/org/jboss/seam/blacklist.properties.

1
2
3
4
5
.getClass(
.class.
.addRole(
.getPassword(
.removeRole(

With a little bit studying, we found the blacklist is just a simple string matching, and we all know that blacklist is always a bad idea. The first time I saw this, I recalled the bypass of Struts2 S2-020. The idea of that bypass and this one is the same. Using array-like operators to avoid blacklist patterns! Just change:

1
"".getClass().forName("java.lang.Runtime")

to

1
""["class"].forName("java.lang.Runtime")

Is it simple? Yes! That’s all.

So the last thing is to write the shellcode in JBoss EL. We use Java reflection API to get the java.lang.Runtime Object, and list all methods from that. The index 7 is the method getRuntime() to return a Runtime instance and the index 15 is the method exec(String) to execute our command!

OK! Let’s summarize our steps and chain all together!

  1. Path normalization bug leads to ACL bypass
  2. Bypass whitelist to access unauthorized Seam servlet
  3. Use Seam feature actionMethod to invoke gadgets in file suggest_add_new_directory_entry_iframe.xhtml
  4. Prepare second stage payload in HTTP parameter directoryNameForPopup
  5. Use array-like operators to bypass the EL blacklist
  6. Write the shellcode with Java reflection API
  7. Wait for our shell back and win like a boss ._./

Here is the whole exploit:

OK, by executing the Perl script, we got the shell!

The fix

I will illustrate the fix from 3 aspects!

1. JBoss

As the most buggy thing is on Seam framework. I have reported these “features” to security@jboss.org in Sept 2016. But their reply is:

Thanks very much for reporting these issues to us.


Seam was only included in EAP 5, not 6, or 7. EAP is near the end of maintenance support, which will end in Nov 2016, [1]. The upstream version you used to test was released over 3 years ago.
During maintenance support EAP 5 only receives patches for important or critical issues. While you highlight that RCE is possible, only on the precondition that the attack can first upload a file. This seems to reduce the impact to moderate.


I think we will not bother to fix these security issues at this stage of the Seam project lifecycle.


[1] https://access.redhat.com/support/policy/updates/jboss_notes/


We do appreciate your efforts in reporting these issues to us, and hope that you will continue to inform us of security issues in the future.

So due to the EOL, there seems to be no official patch for these crazy features. However, lots of Seam applications are still running in the world. So if you use Seam. I recommend you to mitigate this with Nuxeo’s fix.

2. Amazon

With a rapid investigation, Amazon security team isolated the server, discussed with the reporter about how to mitigate, and listed every step they have taken in detail! It’s a good experience working with them :)

3. Nuxeo

After the notification from Amazon, Nuxeo quickly released a patch in version 8.10. The patch overrides the method callAction() to fix the crazy feature! If you need the patch for your Seam application. You can refer the patch here!

Timeline

  • 10 March, 2018 01:13 GMT+8 Report to Amazon security team via aws-security@amazon.com
  • 10 March, 2018 01:38 GMT+8 Receive that they are under investigating
  • 10 March, 2018 03:12 GMT+8 Ask that can I join the conference call with security team
  • 10 March, 2018 05:30 GMT+8 Conference call with Amazon, get the status and the step they have taken for the vulnerability
  • 10 March, 2018 16:05 GMT+8 Ask if it’s possible public disclosure on my Black Hat talk
  • 15 March, 2018 04:58 GMT+8 Nuxeo released a new version 8.10 that patched the RCE vulnerability
  • 15 March, 2018 23:00 GMT+8 Conference call with Amazon, know the status and discuss public disclosure details
  • 05 April, 2018 05:40 GMT+8 Reward the award from Amazon