[Esapi-dev] HMAC validation bypass in ESAPI Symetric Encryption

Renaud Dubourguais renaud.dubourguais at synacktiv.com
Mon Dec 2 15:53:24 UTC 2013


Hi guys,

After some investigations, we found a new way to bypass the HMAC
validation in the ESAPI Symmetric Encryption.

Problem description:
====================

The security fix for the CVE-2013-5679 vulnerability ("MAC Bypass in
ESAPI Symmetric Encryption") does not prevent a malicious user to bypass
the HMAC validation on a serialized cipher text.

A serialized cipher text generated by ESAPI has the following structure:

<kdfInfo/kdfPrf:int> : identification of the key derivation function
<timestamp:long> : time when the cipher text is generated
<cipherlen:short> : size of the "cipher" string
<cipher:str> : algorithm for encryption and decryption
<keysize:short> : size of the HMAC validation key (in bits)
<blocksize:short> : size of the encryption blocks
<ivlen:short> : size of the IV (in bytes)
<iv:str> : Initialisation Vector
<ciphertextlen:int> : size of the cipher text in bytes
<ciphertext:str> : encrypted value
<maclen:short> : size of the HMAC in bytes
<mac:str> : HMAC of the IV and the cipher text

The "keysize" field, under the control of a malicious user, is used to
compute the HMAC encryption key. The ESAPI library checks for a minimal
key size of 56 bytes using an assertion. However, if the application is
not started with the "-ea" java option (which is usually not the case in
production environments), assertions are turned off. By setting the
"keysize" field in a serialized CipherText to a value between 1 and 8
(bits), ESAPI will compute the HMAC using a 1 byte key, which can be
trivially brute-forced to produce a valid HMAC.

>From the code:
==============

If "keysize" is set to [1-8], the CryptoHelper.computeDerivedKey()
method used to compute the HMAC encryption key will return an "authKey"
of 1 byte. The following extract from the file
org/owasp/esapi/crypto/CryptoHelper.java shows were the
computeDerivedKey() is called:

public static boolean isCipherTextMACvalid(SecretKey sk, CipherText ct)
{
   if ( CryptoHelper.isMACRequired( ct ) ) {
            try {
                SecretKey authKey = CryptoHelper.computeDerivedKey(sk,
ct.getKeySize(), "authenticity");
                // VULN: If ct.getKeySize() is 1, authKey.length will be
1 byte
                boolean validMAC = ct.validateMAC( authKey );
                return validMAC;
            } catch (Exception ex) {
                logger.warning(Logger.SECURITY_FAILURE, "Unable to
validate MAC for ciphertext " + ct, ex);
                return false;
            }
        }
        return true;
    }

The computeDerivedKey() method is found in the file
org/owasp/esapi/crypto/KeyDerivationFunction.java:

public SecretKey computeDerivedKey(SecretKey keyDerivationKey, int
keySize, String purpose)
                        throws NoSuchAlgorithmException,
InvalidKeyException, EncryptionException
     {
[...]
                assert keySize >= 56 : "Key has size of " + keySize + ",
which is less than minimum of 56-bits.";
                // VULN: assert not triggered in production
[...]
                keySize = calcKeySize( keySize ); // if keySize is 1,
calcKeySize() returns 8 bits
[...]
                do {
                        mac.update( ByteConversionUtil.fromInt( ctr++ ) );
                        mac.update(label);
                        mac.update((byte) '\0');
                        mac.update(context);
                        tmpKey = mac.doFinal(ByteConversionUtil.fromInt(
keySize ) );

                        if ( tmpKey.length >= keySize ) {
                                len = keySize;
                        } else {
                                len = Math.min(tmpKey.length, keySize -
totalCopied);
                        }
                        System.arraycopy(tmpKey, 0, derivedKey, destPos,
len);
                        label = tmpKey;
                        totalCopied += tmpKey.length;
                        destPos += len;
                } while( totalCopied < keySize );
[...]
		return tmpkey; // VULN: tmpkey will only contains 1 byte
     }

If CryptoHelper.computeDerivedKey() returns an "authKey" containing only
1 byte, ct.validateMac(authKey) will compute a HMAC using a 1-byte key.
It will then compare the calculated HMAC with the HMAC in the CipherText
sent by the attacker. The validateMAC() method is in the file
org/owasp/esapi/crypto/CipherText.java :

public boolean validateMAC(SecretKey authKey) {
            boolean requiresMAC =
ESAPI.securityConfiguration().useMACforCipherText();

            if (  requiresMAC && macComputed() ) {
                byte[] mac = computeMAC(authKey); // VULN:
authKey.length is 1 byte
                assert mac.length == separate_mac_.length : "MACs are of
different lengths. Should both be the same.";
                return CryptoHelper.arrayCompare(mac, separate_mac_);
[...]

It means that a malicious user can set the "keysize" field to a value
between 1 and 8 in the CipherText structure, recompute all possible HMAC
in the CipherText using a 1-byte key, until CipherText.computeMAC(key)
matches the forged HMAC. Brute forcing 1 byte takes only 256 iterations
in the worst case. When the right value is found,
CipherText.validateMAC(key) will return "true" and the HMAC check will
be bypassed.

Impacts:
========

Once the MAC is bypassed, the attacker will be in a good position to
attack the encryption layer, by using for example padding oracle
attacks. Other cryptographic attacks are also possible depending on the
encryption algorithm and mode used.

Affected versions:
==================

2.1.0
2.1.1-SNAPSHOT (from svn)

Proof of concept:
=================

A sample application using the ESAPI library and a proof of concept of
the attack can be found here:
http://www.synacktiv.fr/ressources/owasp_esapi_hmac_bypass_poc.tgz.

To decrypt a token, use:
$ java EsapiVictim d <token>

To generate a token, use:
$ java EsapiVictim g key=value

To use the exploit trying to bypass the HMAC validation of
EsapiVictim.java, the java classpath should be adjusted following your
system, then do:

$ python esapi_brute.py <token>

If esapi_brute.py bypasses the MAC validation, a padding exception will
be thrown by the EsapiVictim application:
"Caused by: javax.crypto.BadPaddingException: Given final block not
properly padded"

Remediation:
===========

Enforce a minimal size for the HMAC key at runtime (and not using
"assert" which is usually turned off). Other vulnerabilities may lurk in
the dark, as the serialized CipherText exposes sensitive parameters used
for the decryption and HMAC validation. Do we really need the "keysize"
field in the CipherText structure?

-- 
Renaud Dubourguais
Security Expert - Synacktiv



More information about the Esapi-dev mailing list