Most applications have a critical feature for identifying users. The aim is to guarantee the confidentiality and integrity of their data. Common methods used to enable the server to identify a user include session cookies, JSON Web Tokens (JWT) or, in some cases, customised solutions.

Clearly, the logic behind the generation of these session tokens is essential, because bypassing or hijacking them can lead to account takeover, with critical impacts on data security and even the total compromise of the system.

In this write-up, we present a situation encountered during a mobile application penetration test that enabled us to understand and hijack the operation of tokens and take control of user accounts.

Context: grey box penetration test of a mobile application communicating with an API

Being in grey box, we had a user account on the mobile application. After capturing the traffic, we could see the following request:

Request:

GET /login HTTP/1.1
Host: 172.23.76.53:8000
TOKEN: T1P6eRxSX7DosonF7cUSD+OALbeWmMYu3DpNYrYEdtvxnrL3LoiE7SFAimZ1hrBEk4vinGO89IvycKEGR7SyoA== 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: close
Upgrade-Insecure-Requests: 1

Response:

HTTP/1.1 200 OK
Host: 172.23.76.53:8000
Date: Tue, 12 Sep 2023 13:40:30 GMT
Connection: close
X-Powered-By: PHP/8.2.8
Content-type: text/html; charset=UTF-8

Welcome User

This connection request is the very first in the application. This means that the password is probably in the token, which is by the way in base64.

Our first objective is therefore to try and decode it. To do this, we use CyberChef:

As we can see, the token is not limited to a simple base64 encoding. The data contained in the token is also encrypted, which means that a method must be found to access the information needed to decrypt it.

Luckily for us, mobile applications mean source code! And as no prior request was sent, it’s highly likely that all this information is embedded in the mobile application.

So we’re trying to recover the source code.

Decompiling the mobile application

Depending on the technologies used (native, Flutter, etc.), there are several methods for recovering the source code of a mobile application.

In our case, the application was in Xamarin. This means that the mobile code is written in .NET. To recover the source code, we’ll proceed in several stages:

  • Decompile the application with apktool: apktool d app.apk
  • Unpack the Xamarin files: pyxamstore unpack -d app/unknown/assemblies/
  • Use a .NET decompiler, in our case dotPeek from JetBrains.

This then allows us to navigate through the decompiled code and analyse it.

Discovering the encryption used (AES-256-CBC)

In this context, our aim was to find out in the source code how the “TOKEN” header was generated. To do this, we searched the decompiled code using the keyword “token” to identify the generation process.

And this told us that the token data was encrypted, before being encoded in base64, using the AES-256-CBC algorithm.

  • We have symmetric encryption, so if we know how to encrypt we know how to decrypt and vice versa.
  • With this encryption mode, we need to know the passphrase and the initialization vector (IV).
  • To summarise, here is an image showing the data encryption method with AES and CBC mode:

Source: Wikimedia

As mentioned above, the application encrypts the data directly. In fact, it also embeds the passphrase and the initialization vector (IV):

iv = 8c7b7ef51795142327fe06cea699e39b (sous forme hexadécimal)
passphrase = "AlfredWillFindMeaGoodPassphrase!"

So we can decrypt the token with all this information:

And obtain the following token:

[email protected]|password=superpassword1234|App=company1

Now let’s imagine that the user wants to target an administrator. He has practically all the information except the password, which makes it impossible to forge an admin token.

It should also be remembered that including the password in the token is bad security practice, as it makes it much easier for attackers in the event of a leak. We can also see that the token does not have a validity period. In the event of a leak, it will still be valid.

So with this vulnerability, it was not possible to steal an account. We could have stopped here, but let’s continue our investigation.

Identification of a predictive token

We realised that after connection, the application used a different token:

Encrypted version:

T1P6eRxSX7DosonF7cUSD1jxPCZrgA0tcnW+3wfxhX9aRuxSURkzQ39W1PH2K81icOoCRqR3jXj0tkQcjjArfOVzE2wahVpQu40xoobj1j1ToQ84fDq82sQA/8mxQd06KX+RoiW3FbNoTtUfON54qbmtptE9e2NtWAv2IX++FTmzlL0uPeF9G5X/wtoKTp/UEoyP7OtWuc97Pt832pIqPtnMvebL6k2S/CDa590XbIw=

Decrypted version:

[email protected]|App=company1|hash=250ec51456a3c45e4a8b55344fffaf188c16180823efb813130c1672405308042eaded1146cbc1705635cddf386596834e17d67ebdcafe1a20d8700011f7220a

We note here that the password is not directly marked in clear text in the token because it is replaced by a hash.

Looking at the size, we can assume that it is a sha512. However, we still need to know what data to provide it with to obtain a valid hash. In this case, we look again in the source code and see that the hash is of the form sha512(email+’#’+company). In other words, the hash is sha512([email protected]#company1).

Now, let’s say we want to access the admin account. We’ll start by generating the hash:

[email protected]#company1  => f765f0945a56a49e6f245eb74fe312a2a6619f7cdfe2298bf8bc4d816060583704ebb5bf87bcdd02218d699c0934f78b7044ba475432d5f6a4eb135348ad43a7

Then we encrypt it:

7p/fKUgE5tiG7J+4MMeAsvaw66KNur4NpskFiwAnxqbqvvuElIwusapyaN7V1MOMG/YSeY+3OZ/RzunzUUKWRMI+y8XCL1bX0lJQtJYaWkey9LQBBy158M6Dq3SbglAqes+B+dG18vf6urXISZZAONkR7FxoDbFekqVgZ4urzQBBzkjkRvXJlAVkuhQ879OgplBSNSN382d4Qnrufeb6wqJWp0tFp/dj+BhsbkKsQfU=

And if we submit the request with this token, we see that it works:

Request:

GET /login HTTP/1.1
Host: 172.17.42.3:8000
Token: 7p/fKUgE5tiG7J+4MMeAsvaw66KNur4NpskFiwAnxqbqvvuElIwusapyaN7V1MOMG/YSeY+3OZ/RzunzUUKWRMI+y8XCL1bX0lJQtJYaWkey9LQBBy158M6Dq3SbglAqes+B+dG18vf6urXISZZAONkR7FxoDbFekqVgZ4urzQBBzkjkRvXJlAVkuhQ879OgplBSNSN382d4Qnrufeb6wqJWp0tFp/dj+BhsbkKsQfU=
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close

Response:

HTTP/1.1 200 OK
Host: 172.17.42.3:8000
Date: Thu, 17 Aug 2023 07:57:36 GMT
Connection: close
Content-type: text/html; charset=UTF-8

Welcome Admin

So, thanks to the mobile application’s source code, we were able to forge a valid token and pretend to be an admin, knowing only his email address.

Here, we used grey box testing conditions to understand how the application worked, starting with a valid token (that of a user).

Exploiting this vulnerability in black box

This attack could also work with a black box approach. Here are the various stages:

  • To begin with, invalid data is entered in the connection form.
  • We see that the token is sent in the headers.
  • We look in the code to find out how to decrypt it.
  • We find the information needed to decrypt and therefore encrypt it.
  • We also realise that the application has two tokens and not just one.

The two prerequisites for this attack are therefore knowledge of the name of the application and an email address. In fact, this attack works best when targeted, although it is also possible to find the name of the application (via brute force) and test with an existing email list or generate a potentially valid email list (following a recon, for example).

In addition, this attack has a greater impact in grey box, because the solution was deployed for several customers on different servers, but with the token used in the same way. This means that by knowing the server hosting the application and a valid email, it is possible to compromise another client organisation.

Conclusion

The publisher of the tested solution had chosen a multi-tenant approach with one database per organisation. However, the segmentation was not sufficient, as it was possible to forge a token valid for all the tenants.

The publisher of the tested solution had chosen a multi-tenant approach with one database per organisation. However, the segmentation was not sufficient, as it was possible to forge a token valid for all the tenants.

Indeed, all the tenants had the same passphrase and the same initialisation vector. However, additional information was required, such as the name of the target application. This made exploitation less trivial, but not impossible.

Furthermore, in the case presented here, the token never expired. If the token leaked, an attacker could reuse it indefinitely without knowing the generation logic.

The root of this vulnerability is that the solution relied solely on client-side security, which is not recommended for authentication management. One of our recommendations was therefore to rely on the server to provide the authentication token.

Finally, a little pentester advice: never reinvent the wheel! There are common, tried and tested solutions, such as JWT, which can be used to provide authentication and session management for applications easily and securely (if implemented correctly).

Author: Thomas DELFINO – Pentester @Vaadata