Express fuzzing of MQTT brokers

MQTT brokers seem to lend themselves well to fuzzing because they often implement their own versions of the MQTT protocol, which includes parsing of MQTT control packets. These packets often include length–value encodings, a typical source of bugs if not carefully implemented. For example, a PUBLISH packet’s control header encodes the topic length over two bytes; if the parser naively attempts to read as many bytes as specified regardless of the packet’s actual length, an out-of-bound memory access could occur—the same kind of bug that caused Heartbleed.

With the intuition that such bugs and other parsing bugs could exist, we set out to run some dumb fuzzing on MQTT brokers. We started by manually crafting malformed packets but quickly discovered mqtt_fuzz, a small project from F-Secure that does exactly what we wanted, namely blind fuzzing of common MQTT control packets.

We started fuzzing brokers written in C—more sensitive to memory corruption bugs—Mosquitto and Bevywise. The former is the most popular open-source broker, and unsurprisingly our straightforward methodology didn’t yield any result. But we got several Bevywise crashes after running the fuzzer for about 20 seconds, apparently caused by the above length–value pattern (thanks to zx2c4 for helping us reverse engineering the binaries). A debugger would for example show the following:

* thread #15, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x000000010b6e3bc0`on_recv_publish_func + 368`on_recv_publish_func:
->  0x10b6e3bc0 <+368>: movzbl (%r15,%rax), %edx
    0x10b6e3bc5 <+373>: movb   %dl, (%rbx,%rax)
    0x10b6e3bc8 <+376>: movslq %ecx, %rax
    0x10b6e3bcb <+379>: incl   %ecx
Target 0: (Broker) stopped.

We didn’t investigate further and can’t tell whether these specific bugs are exploitable for more than remotely crashing a broker. But being able to crash a remote broker by sending a string such as 8206000200e0bfadf3a081f3a0812b2f762fa9bdcb90f3a0819001cd2b2f762f8523cab000 is already a problem.

We quickly looked at some other brokers, found to be resilient to our basic fuzzer: In EMQ and VerneMQ, both written in Erlang, certain malformed packets would crash session processes (by design), without interrupting the service. In HiveMQ, written in Java, we spotted an uncaught index-out-of-bound exception, which did not interrupt the service (HiveMQ directly fixed and credited us for the observation). In Mosca, written in JavaScript, we didn’t notice any abnormal behavior.

The largest part of this project was done in a Geneva–Lausanne train ride (~40min), so there’s probably much more to find, starting with using a less dumb fuzzer (afl, libFuzzer?) and looking at other brokers. Feel free to let us know your findings!