Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Insecure X-Zoom-App-Context decryption allows user impersonation #24

Open
Starkteetje opened this issue Apr 18, 2024 · 0 comments
Open

Comments

@Starkteetje
Copy link

The decrypt routine is used to decrypt the X-Zoom-App-Context header. The decrypted value contains important information for the application, such as the user's ID and action, which the application will act upon.

Unfortunately, the decryption process is insecure, as the Node crypto API does not enforce an expected length of the authentication tag (docs, a blog post I wrote about this issue), which allows the verification of the GCM authentication tag, even if it has been truncated to 32bits (as opposed to the desired 128bit). The unpack routine does allow an attacker to supply such a shortened authentication tag, leading to a tag value being checked that is in brute-force range (2^32).

For a minimal PoC of the behaviour of the Node crypto module, consider

const { createDecipheriv, createCipheriv, randomBytes } = require('node:crypto')

const encrypt = (key, plaintext) => {
    const nonce = randomBytes(12)
    const cipher = createCipheriv('aes-256-gcm', key, nonce)
    const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()])
    const auth = cipher.getAuthTag()
    return {ciphertext, auth, nonce}
};

const decrypt = (key, nonce, ciphertext, auth) => {
    const decipher = createDecipheriv('aes-256-gcm', key, nonce)
    decipher.setAuthTag(auth)
    plaintext = Buffer.concat(([decipher.update(ciphertext), decipher.final()]))
    console.log("Successfully decrypted:", plaintext)
};

const key = randomBytes(32)
const msg = {
    "typ": "meeting",
    "uid": "c39dbb14-2bdf-4dcb-b357-df3e88f3cd7e",
    "mid": "0991abc3-e99f-4b52-bd7c-d40d7772e9a1",
    "act": "delete",
    "ts": 1707915150,
    "exp": 1739535150
}

encrypted = encrypt(key, JSON.stringify(msg))
decrypt(key, encrypted.nonce, encrypted.ciphertext, encrypted.auth)
decrypt(key, encrypted.nonce, encrypted.ciphertext, encrypted.auth.slice(0,4))

Since GCM is built on the Counter mode, any bit flip to the ciphertext flips the corresponding bit in the plaintext. As such, an attacker with a valid token for user with ID 123 can forge a ciphertext that matches a user with ID 456. Then they can brute-force the authentication tag and create events that seem to originate from Zoom, which pertain to a different user.

According to the Zoom documentation applications are invited to store OAuth tokens for users to act on their behalf:

Optionally, you may store it in a database or cache for later use if your app will be making requests to Zoom Restful APIs on behalf of the user.

As such, if an application uses the X-Zoom-App-Context header to fetch the corresponding OAuth token and act on behalf of the user, the above allows an attacker to impersonate their victim toward the Zoom application and via the OAuth token stored by the application even toward Zoom.

Any application that uses the implementation of this repository or the offical documentation's example is vulnerable, with the impact depending on what the application does.

Fixing the vulnerability

The fix would be to explicitly specify the expected auth tag length using the options field of the Decipher class here

    const decipher = crypto
        .createDecipheriv('aes-256-gcm', hash, iv, {authTagLength: 16})
        .setAAD(aad)
        .setAuthTag(tag)
        .setAutoPadding(false);

With {authTagLength: 16} specified, the Decipher object will reject authentication tags of shorter-than-expected lengths

TypeError: Invalid authentication tag length: 4
at Decipheriv.setAuthTag (node:internal/crypto/cipher:220:22)
...
at node:internal/main/run_main_module:28:49 {
code: 'ERR_CRYPTO_INVALID_AUTH_TAG'
}

A note on disclosure

I tried to disclose this issue to Zoom in a coordinated disclosure process. However, the issue was closed as "this submission relies on the fact that other users/customers build a vulnerable application making it, therefore, exploitable".
I hope raising this issue here prevents someone from implementing an insecure solution or makes Zoom reconsider their stance on fixing this (in all their reference implementation and their documentation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant