Zyxel authentication bypass patch analysis (CVE-2022-0342)

A few months ago, new firmware was released for many Zyxel security devices to patch a critical vulnerability. The vulnerability was subsequently reported by numerous news outlets, however no technical details were published.

The vulnerability has been assigned CVE-2022-0342 and is described as follows:

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.

The information released by Zyxel is available at:

This is a bonus article for our Zyxel audit series, in which we patch-diff the firmware update to discover the root cause of the fixed vulnerability and how to exploit it.

How HTTP authentication is managed by Zyxel

On Zyxel devices the web interfaces are managed via the Apache HTTP Server. The main configuration file is /usr/local/zyxel-gui/httpd.conf which contains the following line:

LoadModule auth_zyxel_module modules/mod_auth_zyxel.so

This Apache module is developed and maintained by Zyxel and manages the authentication process.

The login process generates a cookie named “authtok” which is used to authenticate the user in the next requests.

Diffing the patch

Comparing the two versions of mod_auth_zyxel.so with Bindiff, it’s quite easy to identify the modified routines that fix the issue:

The function “check_authtok” is the one directly called by Apache to verify the authentication; “create_server_config” has been modified as well.

Based on the analysis it’s possible to see that a part of the code has been removed:

The vulnerable pseudocode is:

and the patched one is:

Basically, all the code that allows direct access without authentication in some case related to a mysterious “non GUI access” has been removed.

The routine “get_server_conf” takes the content of the /tmp/__HTTP_SERVER_CONFIG file and puts it into some variables:

Then these variables are compared with a part of the data structure passed to the “check_authtok” function by Apache.

Based on the Apache documentation the data structure passed to the function is “request_rec” which is defined in http://svn.apache.org/repos/asf/httpd/httpd/trunk/include/httpd.h

The first pointer used during the comparison is “request_rec + 4”, which means the second element of the structure on a 32-bit processor (hoping that compiler optimization didn’t mix up the data structure’s order). In this case “conn_rec *connection;”:

struct request_rec {
    /** The pool associated with the request */
    apr_pool_t *pool;
    /** The connection to the client */
    conn_rec *connection;
    /** The virtual host for this request */
    server_rec *server;

    /** Pointer to the redirected request if this is an external redirect */
    request_rec *next;
    /** Pointer to the previous request if this is an internal redirect */
    request_rec *prev;

The second pointer accessed is the 4th element of the “conn_rec” structure which is “apr_sockaddr_t *local_addr;” defined in the same file:

struct conn_rec {
    /** Pool associated with this connection */
    apr_pool_t *pool;
    /** Physical vhost this conn came in on */
    server_rec *base_server;
    /** used by http_vhost.c */
    void *vhost_lookup_data;

    /* Information about the connection itself */
    /** local address */
    apr_sockaddr_t *local_addr;
    /** remote address; this is the end-point of the next hop, for the address
     *  of the request creator, see useragent_addr in request_rec
     */
    apr_sockaddr_t *client_addr;

The third pointer accessed is the 4th element of apr_sockaddr_t defined in apr_network_io.h file and it’s “apr_port_t port;”:

239 struct apr_sockaddr_t {
240     /** The pool to use... */
241     apr_pool_t *pool;
242     /** The hostname */
243     char *hostname;
244     /** Either a string of the port number or the service name for the port */
245     char *servname;
246     /** The numeric port */
247     apr_port_t port;
248     /** The family */
249     apr_int32_t family;

So, the data which is compared with the results of the get_server_conf() function is the local port which received the request.

It’s also possible to confirm this by looking inside the file accessed by the “get_server_conf” function, which contains the main ports used by the Apache HTTP Server:

Root cause of the problem

We need to understand why that check can affect authentication. Since the check is carried out on the socket we can hypothesize two different scenarios:

  • we can connect to other ports, and by modifying the HTTP protocol “Host” header, go to a different virtual host;
  • we can reach some CGIs from some virtual hosts accessible via different ports than those present in the control file.

Theoretically, the Apache HTTP Server should guarantee the separation of the environments according to ports and interfaces on which the listening takes place, therefore it is unlikely that our first hypothesis can be correct.

Let’s check which are the ports used by Apache on our system:

Accessing the TCP port 8008 via browser we get:

It looks like something related to 2FA, which is configured in the /var/zyxel/service_conf/httpd_twofa.conf file:

Accessing the TCP port 54088 we get this page:

It looks like something related to blocking alert page, which is configured in the /var/zyxel/service_conf/cf_blockpage_https.conf file:

By checking the main configuration file of the Apache HTTP Server (/usr/local/zyxel-gui/httpd.conf) it’s possible to see that the “cgi-bin” directory is configured in a global area, which means that all the CGIs will be accessible on every different virtual host:

Exploitation

Putting together all information found so far, exploitation is quite easy. Based on the patch we can assume that all CGIs will be reachable on all interfaces exposed by Apache.

Accessing a CGI via standard ports with an invalid cookie will return an authentication error:

But accessing the same CGI via a non-standard port will give full access to the CGIs and consequently also to the device configuration:

See you next time.