Fork me on GitHub

Detecting Heartbleed with Haka v0.2

As most of you must have heard, a very nasty bug was discovered few weeks ago in the OpenSSL project, a widely used open source implementation of the SSL/TLS protocol. This bug which is better known as the heartbleed bug, relies on a wrongly implemented SSL extension called "heartbeat".

As you know, the initial version of Haka doesn't ship with a SSL dissector. However, the 0.2 version — which will be released soon — features a new grammar allowing to specify network protocols and their underlying state machine. Thanks to that grammar, we were able to write, with a little effort, a dissector covering almost the full specification of SSLv3 protocol. This specification will be covered in upcoming post.

heartbleed

Hereafter, we give a set of rules to detect/block hearbleed attacks.

Detecting heartbeat extensions

This first rule warns if a heartbeat extension is used in Client Hello Handshake.

-- Let's use our brand new SSL dissector
-- on port 443
local ssl_dissector = require('protocol/ssl')
ssl_dissector.install_tcp_rule(443)

haka.rule{
    -- hook syntax has slightly changed in version 0.2.
    hook = ssl_dissector.events.client_hello,
    eval = function (ssl, data)
        -- ssl fields are available through data param
        for i = 1, #data.extensions do
            -- extension type 15 is heartbeat extension
            if data.extensions[i].type == 15 then
                print("Heartbeat extensions detected")
                print("### Warning, this is probably heartbleed ###")
            end
        end
  end
}

On a pcap containing a SSL heartbeat extension, Haka will output the following message:

Heartbeat extensions detected
### Warning, this is probably heartbleed ###

That's a first step, but with Haka you can do better than that. Haka detects heartbeats, but a heartbeat is not necessary a heartbleed attack.

Detecting SSL state machine violation

We made some pcap traces with an exploiting tool (link not given) and observed that heartbeat requests are sent during SSL negociation which violates the RFC 6520: "a HeartbeatRequest message SHOULD NOT be sent during handshakes". Our dissector has an internal state machine analyzing each step of the SSL handshake. The attack made by this tool is therefore blocked due to an incorrect state transition. More precisely, the attack tool sends a heartbeat request just after the ServerHelloDone while Haka expects a ClientKeyExchange message. The default behavior is to raise an alert and to drop the connection:

info  alert:         id = 7
        time = Wed Apr 23 10:38:09 2014
        severity = low
        description = awaited client key exchange handshakes
debug state-machine: ssl: error transition on state 'server_hello_done'
debug state-machine: tcp: transition from state 'established' to state 'reset'
debug state-machine: tcp: enter transition on state 'reset'

Haka can now detects the attacks launched by any tool which doesn't respect RFC, but what if attacker takes care of each step of the SSL negociation?

Detecting unexpected hearbeat messages length

RFC 6520 states that: "... the receiver MUST send a corresponding HeartbeatResponse message carrying an exact copy of the payload of the received" which is not the case with a heartbleed attack. Moreover, we must have only one heartbeat message in flight at a time. In the following rule we check if these two requirements are met. Otherwise, we raise an alert and drop the ssl connection:

haka.rule{
    hook = ssl_dissector.events.data,
    eval = function (ssl, data, direction)
        -- type == 24 => Heartbeat
        if data.type ~= 24 then return end
        -- create a table to store hearbeat requests
        if not ssl.heartbeat then ssl.heartbeat = {} end

        -- we should not have more that one heartbeat
        -- message in flight at a time
        if ssl.heartbeat[direction] then
            haka.alert{
                description = "unexpected heartbeat request",
                sources = haka.alert.address(ssl.flow.srcip)
            }
            ssl:drop()
        else
            local opposite_direction = haka.dissector.other_direction(direction)
            if ssl.heartbeat[opposite_direction] then
                if data.length > ssl.heartbeat_len then
                    haka.alert{
                        description = "unexpected heartbeat response data length",
                        sources = haka.alert.address(ssl.flow.srcip),
                    }
                    ssl:drop()
                else
                    ssl.heartbeat[direction] = nil
                end
            else
                ssl.heartbeat[direction] = true
                ssl.heartbeat_len = data.length
            end
        end
    end
}

For a sake of simplicity, we assumed that only one side is requesting heartbeats in order to distinguish between encrypted heartbeat requests and responses. However, with better heuristics this configuration case could be also covered by Haka.

Conclusion

Heartbleed was an interesting test case for Haka. We were able to write quickly a dissector for a complex protocol along with its state machine and define advanced security rules.