Background

IBM WebSphere Commerce or WebSphere Commerce Suite (WCS), developed by IBM, is a software platform framework for e-commerce and is actively being used by giant retailers.

While working on an engagement last year, I stumbled upon a crypto vulnerability in the IBM Websphere Commerce framework, originally found by VSR Security (CVE-2013-05230). Essentially the krypto parameter found in some URLs could be decrypted using a padding oracle attack. Since no public exploit has been published as time of this writing, I figured it might be an interesting exercise to write one.

IBM uses their own crypto library for Cipher Block Chaining (CBC) ciphers, but my guess is that the implementation will be approximate to the OpenJDK snippet found at: https://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/com/sun/crypto/provider/CipherCore.java

 if (decrypting) {

  if (outputCapacity < paddedLen) {
      cipher.save();
  }
  // create temporary output buffer so that only "real"
  // data bytes are passed to user's output buffer.
  byte[] outWithPadding = new byte[totalLen];
  totalLen = finalNoPadding(finalBuf, finalOffset, outWithPadding,
                            0, totalLen);

  if (padding != null) {
      int padStart = padding.unpad(outWithPadding, 0, totalLen);
      if (padStart < 0) {
          throw new BadPaddingException("Given final block not "
                                        + "properly padded");
      }

This crypto library will throw a BadPaddingException when invalid padding is encountered. It is then up to the code that is using this library to handle this exception. In this case, IBM WebSphere Commerce framework seems to leak the information about the padding byte by returning a different response when invalid padding occurs.

Detection

Once you have found a page with krypto URL parameter, differentiate between the following conditions:

  1. Valid ciphertext decrypted
  2. Valid padding – You will need this one to de-krypto 🙂
  3. Invalid padding error

In real world scenario, I found condition (1) and (2) are the same for most of the time. Here is how to test it:

Let us assume we have this valid krypto value:

krypto=rKPqPoqBmZV96l9Ot5GLUtgQwuEYt4oDEG[TRUNCATED]
Kt Dekrypto

Record the normal response – this is condition (1)

Replacing first few characters and run it again, record the response – this is condition (2):

krypto=AAAqPoqBmZV96l9Ot5GLUtgQwuEYt4oDEGrhpdNbubd[TRUNCATED]
Kt Dekrypto

Delete random characters and run it one final time, record the response – this is condition (3):

krypto=qPoqBmZV96l9Ot5GLUtgQwuEYt4oDEGrhpdNbubd[TRUNCATED]
Kt Dekrypto

You can use Burp’s Comparer tool to compare those responses. If (3) is different from (2) and (1) then it may be possible to decrypt this parameter.

Exploitation

Padding oracle attack is not specific to any cipher, rather it is a generic attack against CBC ciphers. Therefore any padding oracle implementations will work in this scenario. I have looked at several padding oracle algorithms, and Ron Bowes’ algorithm by far is the fastest one. The reason is, most padding oracle algorithms iterate over the ciphertext bytes of [Block (n-1)] randomly (bytes ranging from 0-255) to find the plaintext of [Block n]. Ron’s algorithm also iterates over the ciphertext bytes of [Block (n-1)]; but instead of doing so randomly, it loops over a defined character set first (which is composed of ASCII characters, bytes ranging from 32-126), then the rest of the possible byte values (0-31 and 127-255). This greatly reduces number of requests sent, since most meaningful string is composed of ASCII characters.

In addition, I made the following adaptations to Ron’s poracle framework in order for the exploit to work with this particular case:

Encoding/decoding

Ron’s Bowes’ script assumes the payload is in hexadecimal format. So extra encoding/decoding of the krypto parameter is required. The decoding process is as follow:

  1. URL decode key characters (including newline)
  2. Base64 decode
  3. ASCII encode

Encoding steps:

  1. ASCII decode
  2. Insert newline character at every 77th character (RFC 2045 or 4880)
  3. Base64 encode
  4. URL encode key characters

Format of payload

Ron’s original script is made on the assumption that the server will decrypt any two-block payload provided by the attacker. Let us assume that our valid ciphertext blocks is:

[Block 0] [Block 1] [Block 2] [Block 3]

To decrypt [Block 2], we need to send

[Block 1] [Block 2*] 

and wait until the server returns padding error.

However, with IBM Web Commerce, the server does not decrypt any arbitrary two-block payload. It seems to require a valid header or expect certain values at the beginning of the block. Therefore, in order to decrypt [Block 3], we need to send:

[Block 0] [Block 1] [Block 2*] [Block 3]

This is an easy fix, I only needed to append the early blocks to the payload.

Parallelization

  • I added support for multi-threading. The script uses Meh’s Ruby thread pool library to reduce the overhead of creating and destroying threads.
  • I also added the ability to skip already-decrypted blocks and save temporary results to a session file. In situations where the connection ends abruptly, the script only needs to work off the remaining blocks.

Usage

Usage: Usage: Dekrypto.rb [options]

Specific options:
    -s, --sort                       Sort temporary results
    -v, --verbose                    Show debug messages
    -t, --threads SIZE               Set threadpool size
    -f, --file FILE                  Save temporary results to file
    -h, --help                       Show this message

Example

Running test server:

ruby KryptoTestServer.rb

Running Dekrypto script with verbose output, ten threads, saving temporary results to a text file
(decrypted.txt)

ruby DeKryptoDemo.rb -v -f decrypted.txt -t 10

Ouput

Temporal Result:
0,
1,1996&use
2,rName=de
3,rp%20&pa
4,ssword=t
5,his+is+a
6,+secure+
7,password
8,&promoti
9,onCode=S
10,PRING+20
11,13

DECRYPTED: 1996&userName=derp%20&password=this+is+a+secure+password&promotionCode=SPRING+2013
DURATION: 12.66 seconds

Limitation

When testing against real applications, I notice that some blocks may not be decrypted properly. My guess is that the server may catch earlier exception in the decoding phase (at either the URL decoding step or the UTF-8 decoding step) and return valid response. Currently, I do not know how to overcome this limitation so any suggestions will be much appreciated.

The source code could be found at: https://github.com/NetSPI/Dekrypto

References