Sansec logo

Magento crypt key rotation and JWT forgery

Sansec

by Sansec

Published in Guides

The Magento encryption key (crypt/key in app/etc/env.php) protects stored secrets and signs the JWT tokens used for admin, integration and customer API access. If an attacker obtains the key (through a file disclosure bug such as CosmicSting, a leaked backup or a stolen env.php), they can forge an API token for any user without a password.

This guide explains what rotating the key does, and whether a leftover old key in env.php can still be used against your store.

How Magento stores multiple keys

bin/magento encryption:key:change generates a new key, appends it to crypt/key (one key per line) and re-encrypts the known encrypted fields in the database with the new key. The old key stays in env.php. Encrypted values carry a key index, so Magento can still decrypt anything that was not re-encrypted.

'crypt' => [
    'key' => 'old-key-here
new-key-here'
],

Can an old key still forge a JWT?

It depends on the Magento version. We checked the Magento 2 source of every release in the 2.4.4 to 2.4.9 range.

Before the fix, the JWT verifier built its key set from all entries in crypt/key, so a token signed with any listed key was accepted. Rotation did not invalidate a leaked old key.

Since the fix (Adobe issue AC-12485, shipped with the CosmicSting patches in July 2024), Magento\JwtUserToken\Model\SecretBasedJwksFactory uses only the last entry in crypt/key, both for signing and for verifying. A token signed with an older key is rejected.

Release lineSingle-key fix included from
2.4.42.4.4-p10
2.4.52.4.5-p9
2.4.62.4.6-p7
2.4.72.4.7-p2
2.4.8 and laterall releases

Earlier releases in each line (including 2.4.7 and 2.4.7-p1) accept every key in crypt/key. Adobe Commerce and Mage-OS builds follow their own patch numbering, so check the file itself if you are not on Magento Open Source.

The last line of crypt/key is the JWT key. That is the newest key only if the keys were added in order.

What a leftover old key still allows

On a patched version an old key can no longer forge API tokens. It can still decrypt any value that was encrypted with it and never re-encrypted. Treat an old key as a secret for as long as it is in env.php.

The key that is current when an attacker obtains it is fully exposed: it can forge tokens on every version until you rotate it.

  1. Update to a version that includes the single-key fix (table above).
  2. Run bin/magento encryption:key:change to generate a new key and re-encrypt the database.
  3. Check that the new key is the last line of crypt/key.
  4. Rotate the secrets that were readable with the old key: payment gateway credentials, SMTP and API passwords in core_config_data, integration tokens, and admin passwords.
  5. Find values that are still encrypted with an old key and re-encrypt them. bin/magento encryption:data:list-re-encryptors shows what Magento can re-encrypt, and bin/magento encryption:data:re-encrypt does it. Some values are not covered, such as third party module settings and two factor authentication secrets in tfa_user_config. Re-save the former in the admin and reset the latter.
  6. Check that no stored value still starts with an old key index (see below), then neutralize the old key in env.php. Do not delete the line. Replace it with a random value of the same kind.
  7. Revoke existing admin and integration tokens and review admin users and integrations for additions you did not make.

Replace the old key, do not delete it

Magento stores the key index with every encrypted value, as a prefix such as 1:3:. The index is the position of the key in crypt/key, counting from 0, and the current key is the last line. If you delete the first line, every key moves up one position and all values that carry the prefix 1: are decrypted with the wrong key and fail.

Replacing the old line with a random string keeps the numbering intact and makes the leaked key worthless. Before you do that, search the columns that hold encrypted values (core_config_data.value, tfa_user_config, and any module tables you know of) for values that begin with the old index, and make sure none are left. Test on a copy of the database first.

The same applies when several stores share an old key, which happens when stores were cloned from one another. Replace the shared line on every store, and give a store that has only that one line a key of its own with bin/magento encryption:key:change. On such a store the shared key is the JWT key.

Rotation does not remove malware or backdoors the attacker may have planted while they had access. Run eComscan and follow the forensic investigation guide if you suspect a breach.

Need expert advice? We are here to help!

Stay up to date with the latest eCommerce attacks

Sansec logo

experts in eCommerce security

Terms & Conditions
•
Privacy & Cookie Policy