Why is randomness important, especially in the world of cryptocurrencies? (Part 1)

SkyWallet.com is a cryptocurrency wallet in the cloud, officially used for storing X12, but it can also keep BTC. As any other web applications it may be vulnerable to all kinds of web-based attacks. Even if the cryptocurrency protocol is cryptographicaly secure, a little weakness in web application may result in money theft.

Damian Rusinek 2018.04.18   –   6 min read

SkyWallet.com is a cryptocurrency wallet in the cloud, officially used for storing X12, but it can also keep BTC. As any other web applications it may be vulnerable to all kinds of web-based attacks. Even if the cryptocurrency protocol is cryptographicaly secure, a little weakness in web application may result in money theft.


TL;DR;

I have found that SkyWallet.com uses its own Pseudo Random Number Generator (PRNG) which has extremely low entropy and therefore the ObjectId value from MongoDB as session identifier which is easy to guess (thanks @arturcygan for noticing that!). Actually, it returns time (accurate within one second) combined with a nonce-counter (incremented on each call) encoded in HEX (e.g. 5a607e37c663c042f689abac).

The custom PRNG is used to generate user ids, wallet ids and session ids. It allowed me to guess and take over newly coming sessions by regularly observing the nonce being incremented. The wallet seed reminder functionality was fortunately password-protected but the application did not block brute-force attack on the password.


Authentication

The authentication process is simple. After providing valid e-mail address and password, the application returns user details together with his session token. The token is used to authorize further requests.

Here is the login response, which includes the session token:

{
  "status":true,
  "result":{
    "name": "Attacker",
    "email": "attacker@example.com",
    "lang": "en",
    "token": "5a60<redacted>abac",
    "role": "user",
    "userId": "5a60<redacted>ab71",
    "isSyncing": true
  }
}

When you look at the userId and token parameters in the above JSON server response you will probably notice the problem. They look very similar.

The randomness

The random (or pseudo-random to be exact) sequences typically exhibit statistical randomness while being generated by a deterministic algorithm. It plays crucial role in applications authentication and you can confirm it in most of security cheat sheets, especially for session management. These checklist emphasize that session identifier must be unpredictable and random enough to prevent guessing where an attacker is able to obtain the identifier of a valid session.

OWASP Session Cheat Cheet specifies the following:

The session ID value must provide at least 64 bits of entropy (if a good PRNG is used, this value is estimated to be half the length of the session ID).

It is not a good idea to implement custom PRNG as there are available verified implementations. However there are two types of them: statistical and cryptographic. Statistical PRNGs has useful statistical attributes, but the output easy to guess and is unsuitable for secure parameters such as session identifier. Here is an example of PRNG in Java that returns random int, but it is easy to reproduce as the seed is taken from UNIX timestamp.

Statistical PRNG

The other group, cryptographic PRNGs, return values which are more difficult to predict. For a value to be cryptographically secure, the attacker must not distinguish it from the truly random number. Java provides the SecureRandom class as an example of cryptographic PRNG.

Cryptographic PRNG

In general, if a PRNG algorithm is not advertised as being cryptographically secure, then it is probably a statistical PRNG and should not be used in security-sensitive contexts.

Discovery

At first, I wanted to find out what is the similarity between the token values. I used Burp Suite Sequencer module to generate 100 consecutive tokens. Here is the list:

...
5a8b3cae2283a17e7619fea3
5a8b3cae2283a17e7619fea4
5a8b3cae2283a17e7619fea5
5a8b3cae2283a17e7619fea6
5a8b3caf2283a17e7619fea7
5a8b3caf2283a17e7619fea8
5a8b3caf2283a17e7619fea9
5a8b3caf2283a17e7619feaa
5a8b3caf2283a17e7619feab
5a8b3cb02283a17e7619feac
5a8b3cb02283a17e7619fead
...

The format of the token value is easily noticeable — it is a sequence of 12 bytes encoded in hex. There are only two changes: in the fourth byte and in the last byte. The last byte increases by one on each call while the second byte increases by one a little less often.

After a few tests I have found out that the fourth byte is increased every second. It means that the first fourth bytes are the time! Indeed, when I decoded 0x5a8b3caf to decimal, which is 1519074479, it turned out to be the date in UNIX timestamp — 02/19/2018 9:07PM (UTC).

Token format explained

To sum up, the token format is the following:

  1. Encode current UNIX timestamp in hex.
  2. Increase by one the nonce value.
  3. Encode the nonce in hex (0x2283a17e7619fea7 value in the last token above).
  4. Concatenate and return encoded time and nonce.

Here is the code in Python:

Later I have found out that the same algorithm was used to generate the identifiers of users’ accounts and wallets. I̶t̶ ̶m̶e̶a̶n̶s̶ ̶t̶h̶a̶t̶ ̶t̶h̶e̶ ̶a̶l̶g̶o̶r̶i̶t̶h̶m̶ ̶i̶s̶ ̶a̶n̶ ̶a̶p̶p̶l̶i̶c̶a̶t̶i̶o̶n̶-̶w̶i̶d̶e̶ ̶P̶R̶N̶G̶.̶ Now I know that they use the ObjectId value from MongoDB as the identifiers.

Exploitation

The easy to guess format of token value allows to take over valid sessions of other users. The exploitation plan is the following:

  1. Generate new token value.
  2. Wait some time (e.g. 30s).
  3. Generate new token value.
  4. Check if the difference between the tokens from point 3 and 1 is greater that 1. If no, go back to point 1.
  5. For all nonce values between token from point 3 and 1:
  6. For each UNIX timestamp between two checks:
  7. Generate token identifier using time from point 6 and nonce from point 5.
  8. Access authorized resource (e.g. /api/rate).
  9. If the application returns successful response, the valid token has been found.

By taking over the session, the attacker can perform any action on behalf of the victim. The most critical functionality is restoration of the X12 wallet seed which would allow to transfer the cryptocurrency to the attacker’s wallet.

The wallet seed is a secret phrase (usually a dozen or so words) used as the input for the private key generation algorithm. When you have the seed, you can generate the private key and transfer cryptocurrency from the wallet.

Fortunately, the seed restoration functionality was password-protected and it did not allow to easily empty victims’ wallets. However, attacker can perform a brute-force attack to find user’s password.

I have sent the following request 100 times with different password and the application did not block the account.

POST /api/wallet/mnemonic HTTP/1.1
Host: app.skywallet.com:9018
Authorization: sky-wallet <5a85<redacted>5b>
Content-Length: 48
Origin: https://app.skywallet.com
Connection: close
 
{"id":"5a60<redacted>a2","password":"<password-guess>"}

Consequences

The vulnerability (̶w̶e̶a̶k̶ ̶P̶R̶N̶G̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶)̶ (using ObjectId value as session identifier) allows anonymous attacker to take over victim’s account, but applies only to victims that log in when the attack is being executed. The attacker can view victim’s wallets details and perform actions on his behalf, including (in a critical case) the complete victim’s wallet and cryptocurrency takeover. To do so, he must first passively monitor victim’s wallet balances and wait for the cryptocurrency to appear (which is easy with victim’s authorization token).

The next step is the wallet seed restoration process, which is password-protected. However, as the functionality does not prevent brute-force attack, it is possible (depending on the password complexity) to recover victim’s password and recover his wallet seed. Now all he has to do is generate a private key and transfer the currency to his wallet.

Another potential consequence is the Denial of Service attack on users. Whenever the attacker finds new valid token, he can invalidate the session related to this token. This would not allow users to perform any action in the web application.

Remediation

The remediation of the vulnerability is to use cryptographically secure Pseudo Random Number Generator. The simplest solution is to use UUID generator, but in version 4 which is random! A universally unique identifier (UUID) is a 128-bit number used to identify information in computer systems. This would prevent guessing the identifiers of users, their wallets and, what is most important, their authorization token.


Response Disclosure

As an ethical hacker I have reported the vulnerability to SkyWallet support team and waited until it is fixed.

02/16/2018 — Vulnerability discovered and reported to support team.

02/17/2018 — The support team responded with thanks and requested for more details.

02/20/2018 — Write up sent to support team.

04/04/2018 — The vulnerability was removed. The session token has been changed to UUID 4 (The vulnerability might have been removed before but I was not informed).

Intro to part 2

In the second part I describe how support team have responded and how it led to the discovery of similar vulnerability in another website.


If you want to find useful recommendations and guidelines which should be implemented if you work with solutions based on blockchain, check out my Developing Secure Blockchain Application handbook.

Damian Rusinek
Damian Rusinek Principal IT Security Consultant
Head of Blockchain Security