Back to the articles

CVE-2023-4473 & CVE-2023-4474 - Authentication bypass and multiple blind OS command injection vulnerabilities in Zyxel’s NAS326 devices

picture of the author
Gábor Selján
November 30, 2023 18 mins read
CVE-2023-4473 & CVE-2023-4474 - Authentication bypass and multiple blind OS command injection vulnerabilities in Zyxel’s NAS326 devices

Table of contents

  1. Disclosure timeline
  2. Affected products
  3. Product URLs
  4. Summary
  5. Details
  6. CVE-2023-4473 - Authentication bypass vulnerability
  7. CVE-2023-4474 - Multiple blind OS command injection vulnerabilities
  8. Acknowledgments

Disclosure timeline

Aug 17, 2023: BugProve reported two vulnerabilities to Zyxel.

Aug 19, 2023: BugProve reported one additional vulnerability to Zyxel.

Aug 23, 2023: Zyxel assigned CVE-2023-4473 and CVE-2023-4474 and indicated the target date of Nov 7, 2023. Zyxel confirmed that one of the reported vulnerabilities is a duplicate of CVE-2019-10631 originally reported by Max Dulin.

Aug 28, 2023: BugProve reported multiple additional vulnerabilities to Zyxel.

Oct 16, 2023: Zyxel indicated that most of the reported vulnerabilities will be addressed in the upcoming firmware release and confirmed the target date of Nov 7, 2023.

Nov 2, 2023: Zyxel indicated that the disclosure date has been postponed to Nov 30, 2023, due to several issues reported by other researchers.

Nov 16, 2023: Zyxel released firmware version V5.21(AAZF.15)C0.

Nov 30, 2023: Coordinated public release of advisory.

Affected products

Zyxel’s NAS326 model devices running firmware version V5.21(AAZF.14)C0 and earlier are affected.

Product URLs

Summary

An authentication bypass vulnerability exists in the web management interface of some Zyxel NAS versions. This vulnerability could allow an unauthenticated attacker to perform unauthorized actions on an affected device remotely.

Furthermore, multiple post-auth blind OS command injection vulnerabilities exist when some Zyxel NAS versions improperly handle user-provided input. These vulnerabilities could allow attackers to remotely execute arbitrary OS commands on an affected device.

By leveraging the blind OS command injection vulnerabilities chained together with the authentication bypass vulnerability, an unauthenticated, remote attacker could perform unauthorized actions in the context of the root user.

From time to time, security bugs like these draw attention to the fact that it is essential to address vulnerabilities that require authentication to exploit. An authentication bypass vulnerability will eventually allow attackers to exploit the issues that previously required valid user credentials.

Details

CVE-2023-4473 - Authentication bypass vulnerability

The specific flaw exists within the access check mechanism implemented in the auth_zyxel_module module of the Apache web server. Based on our earlier research, we have found that attackers can use specific path segments to bypass the access check that would otherwise redirect unauthorized users to the login page. Attackers could leverage this vulnerability to access otherwise disallowed URLs.

The web management interface appears to be affected by an authentication bypass vulnerability. The web server skips the access check mechanism for URLs containing specific path segments. For example, under normal circumstances, only authenticated users can access path /nas326/cmd,/ck6fup6/system_main/whoami, and the web server redirects unauthenticated users to the login page, as Figure 1. shows.

Figure 1. The web server redirects an unauthenticated request to the login page
Figure 1. The web server redirects an unauthenticated request to the login page

However, the webserver grants access to the same /nas326/cmd,/ck6fup6/system_main/whoami path after appending the /favicon.ico path segment to the URL, as Figure 2. shows.

Figure 2. The web server did not redirect an unauthenticated request to the login page
Figure 2. The web server did not redirect an unauthenticated request to the login page

Although an additional authentication layer protects specific paths on the web application level, the access check mechanism implemented in the web server module is the only protection for many paths. Hence, leveraging this vulnerability may allow an unauthenticated attacker to exploit vulnerabilities that the access control mechanism of the web server would otherwise mitigate.

For example, chaining this authentication bypass vulnerability with the package initialization mechanism's post-auth OS command injection vulnerability allows an unauthenticated, remote attacker to execute arbitrary commands on the affected device. An attacker can access the vulnerable feature available at the /nas326/cmd,/ck6fup6/portal_main/pkg_init_cmd path by simply appending the /favicon.ico path segment to the URL, as Figure 3. shows.

Figure 3. Chaining an authentication bypass with an OS command injection vulnerability
Figure 3. Chaining an authentication bypass with an OS command injection vulnerability

Furthermore, chaining this authentication bypass vulnerability with an information disclosure vulnerability in the system information feature allows an unauthenticated, remote attacker to hijack existing user sessions. The attacker can obtain a currently logged-in user's authorization token transmitted in the authtok cookie from the application's response to a request sent to /system_main/show_sysinfo, as Figure 4. shows.

Figure 4. Disclosed authorization token of the currently logged-on user
Figure 4. Disclosed authorization token of the currently logged-on user

The application uses the auth_zyxel_module module to enforce authentication on the web server level by redirecting unauthenticated users to the login page. However, the web server may skip this access check mechanism when serving specific routes. The AuthZyxelSkipPattern directive within the web server configuration file /etc/service_conf/httpd.conf specifies the exceptions to this mechanism, as shown in Figure 5.

Figure 5. Path segments allowed to skip access check
Figure 5. Path segments allowed to skip access check

Further analysis revealed that the vulnerability may lie in the FUN_0001116c() function within the mod_auth_zyxel.so file. The following is a simplified excerpt of the decompiled source code of the affected function:

bool FUN_0001116c(char *path, char *pattern) {
    pathLen = strlen(path);
    patternLen = strlen(pattern);
    haystack = malloc(pathLen + 1);
    needle = malloc(patternLen + 1);
    for ( i = 0; i < pathLen; ++i )
      haystack[i] = tolower(path[i]);
    haystack[i] = 0;
    for ( j = 0; j < patternLen; ++j )
      needle[j] = tolower(pattern[j]);
    needle[j] = 0;
    result = strstr(haystack, needle) != 0;
    free(haystack);
    free(needle);
    return result;
}

FUN_0001116c() calls the strstr() function to search for the given pattern within the request path. However, the function ignores the pattern's position within the path. Therefore, URLs containing the pattern within additional path segments appended to the original path will also satisfy this check. Since only the preceding path segments are relevant when processing a request, this path manipulation has no negative impact on the application.

Be aware that attackers may use any patterns specified in the web server configuration file to avoid redirection to the login page. An unauthenticated user can confirm the vulnerability via the following URLs:

http[:]//NAS326/cmd,/ck6fup6/system_main/whoami/favicon.ico
http[:]//NAS326/cmd,/ck6fup6/portal_main/pkg_init_cmd/register_main/setCookie?pkgname=myZyXELcloud-Agent&cmd=%3bid&content=1

CVE-2023-4474 - Multiple blind OS command injection vulnerabilities

OS command injection vulnerabilities in zylog_main

The specific flaws exist in the command execution mechanism implemented in various functions within the zylog_main.py file. Attackers can use specially crafted values provided in multiple query parameters in an HTTP request processed by the show_logging_entries(), configure_mail_syslog(), or test_mail() functions to execute arbitrary OS commands. Attackers could leverage these vulnerabilities to execute commands in the context of the WSGI server process, running as root.

The web management interface appears to be vulnerable to OS command injection because an attacker can use the semicolon ";" character to append arbitrary commands to the intended command string executed by the application. The application does not seem to return the command output in its response.

We submitted the payload ;sleep 5 in the type parameter at the path /cmd,/ck6fup6/zylog_main/show_logging_entries. The application took longer to respond to the PoC request than the original one, indicating that the injected sleep command caused a time delay, as Figure 6. shows.

Figure 6. OS command injection vulnerability in the type parameter
Figure 6. OS command injection vulnerability in the type parameter

We submitted the payload ;sleep 5 in the scheduleMinute parameter at the path /cmd,/ck6fup6/zylog_main/configure_mail_syslog. The application took longer to respond to the PoC request than the original one, indicating that the injected sleep command caused a time delay, as Figure 7. shows. Be aware that the response time is twice the delay, indicating that the application executed the injected command two times. Furthermore, the mailTo, mailFrom, mailServer, mailFormat, accountSMTP, passwdSMTP, scheduleDay, and scheduleHour parameters also appear to be affected by this issue.

Figure 7. OS command injection vulnerability in the scheduleMinute parameter
Figure 7. OS command injection vulnerability in the scheduleMinute parameter

We submitted the payload ;sleep 5 in the to parameter at the path /cmd,/ck6fup6/zylog_main/test_mail. The application took longer to respond to the PoC request than the original one, indicating that the injected sleep command caused a time delay, as Figure 8. shows. Be aware that the server, from, user and passwd parameters are also affected.

Figure 8. OS command injection vulnerability in the to parameter
Figure 8. OS command injection vulnerability in the to parameter

Further analysis revealed that the vulnerabilities lie in the configure_mail_syslog() function within the zylog_main.py file and in the show_log(), configure_mail(), configure_no_syslog(), configure_syslog() and test_mail() functions within the zylog_show_config.py file.

The application usually constructs command strings to call the zylog_dump, zylog_config, zylog_no_logging, and zylog_send_testmail binaries in the /usr/sbin directory via the os.popen() function. One exception is the configure_mail_syslog() function, which calls the os.system() function with a command string containing unsanitized user input from multiple query parameters. The following is the relevant excerpt of the affected function’s decompiled source code:

def configure_mail_syslog(cherrypy, arguments):
...SNIP...
    write_pyconf()
    mail_schedule = pyconf.get_conf_value(MAINTENANCE_LOG_MAIL, 'schedule')
    if mail_schedule != '':
        if mail_schedule == 'weekly':
            mail_day = pyconf.get_conf_value(MAINTENANCE_LOG_MAIL, 'day')
            mail_hour = pyconf.get_conf_value(MAINTENANCE_LOG_MAIL, 'hour')
            mail_minute = pyconf.get_conf_value(MAINTENANCE_LOG_MAIL, 'miniute')
            cmd = '/usr/sbin/zylog_config mail 1 schedule weekly day %s hour %s minute %s' % (mail_day, mail_hour, mail_minute)
            os.system(cmd)
        if mail_schedule == 'daily':
            mail_hour = pyconf.get_conf_value(MAINTENANCE_LOG_MAIL, 'hour')
            mail_minute = pyconf.get_conf_value(MAINTENANCE_LOG_MAIL, 'miniute')
            cmd = '/usr/sbin/zylog_config mail 1 schedule daily hour %s minute %s' % (mail_hour, mail_minute)
            os.system(cmd)
...SNIP...

The function calls write_pyconf() to store the new settings, including the injected commands, in the application's configuration file at /etc/zyxel/py_conf, as Figure 9. shows. Therefore, this vulnerability would allow attackers to quickly deploy a persistent backdoor on the affected system.

Figure 9. Injected commands stored in the configuration file
Figure 9. Injected commands stored in the configuration file

In other instances of the vulnerability, the application uses the os.popen() function to run a specific zylog_* binary and return the executed command’s output. The following are the relevant excerpts of the affected functions within the zylog_show_config.py file.

def show_log(type):
    if type == 'all':
...SNIP...
    else:
        strcmd = '/usr/sbin/zylog_dump entries category %s' % type
    retvalue = os.popen(strcmd)
...SNIP...

def configure_mail(type, data):
   if type == 'enable' and data == 'yes':
...SNIP...
   else:
       strcmd = '/usr/sbin/zylog_config mail 1 %s %s' % (type, data)
       retvalue = os.popen(strcmd)

def configure_no_syslog(type, data):
   if type == 'enable' and data == '':

...SNIP...
   else:
       strcmd = '/usr/sbin/zylog_no_logging syslog 1 %s %s' % (type, data)
       retvalue = os.popen(strcmd)

def configure_syslog(type, data):
   if type == 'enable' and data == 'yes':
...SNIP...
   else:
       strcmd = '/usr/sbin/zylog_config syslog 1 %s %s' % (type, data)
       retvalue = os.popen(strcmd)

def test_mail(server, mail_from, to, user, passwd):
    if passwd != '':
        strcmd = '/usr/sbin/zylog_send_testmail mail test server %s from %s to %s user %s passwd %s' % (server, mail_from, to, user, passwd)
    elif user != '':
        strcmd = '/usr/sbin/zylog_send_testmail mail test server %s from %s to %s user %s' % (server, mail_from, to, user)
    else:
        strcmd = '/usr/sbin/zylog_send_testmail mail test server %s from %s to %s' % (server, mail_from, to)
    retvalue = os.popen(strcmd)
...SNIP...

As the above source code shows, the application constructs a command string from the values of the user-supplied parameters. The os.popen() function passes the constructed command string to the shell, meaning that attackers can use whitespace and shell metacharacters to create shell commands. For example, the semicolon ";" metacharacter used in the PoC payloads allows the execution of sequential commands, one after the other. Executing shell commands that incorporate unsanitized input from an untrusted source makes the application vulnerable to command injection, resulting in arbitrary command execution.

The vulnerabilities can be confirmed with an unauthenticated user via the following URLs by leveraging the authentication bypass vulnerability:

http[:]//NAS326/cmd,/ck6fup6/zylog_main/show_logging_entries/favicon.ico?type=%3bsleep%205
http[:]//NAS326/cmd,/ck6fup6/zylog_main/configure_mail_syslog/favicon.ico?schedulePeriod=daily&scheduleHour=1&scheduleMinute=%3bsleep%205
http[:]//NAS326/cmd,/ck6fup6/zylog_main/test_mail/favicon.ico?server=a&from=b&to=c%3bsleep%205

OS command injection vulnerabilities in zypkg_main

The specific flaws exist within the implementation of the package management mechanism in the zypkg_main.py component of the web management interface. Attackers can use a specially crafted value in the pkgname query parameter attached to a URL with the path /cmd,/ck6fup6/zypkg_main/pkg_disable to execute arbitrary OS commands. Attackers could leverage this vulnerability to execute commands in the context of the WSGI server process, running as root.

The web management interface appears to be vulnerable to OS command injection because an attacker can use the semicolon ";" character to append arbitrary commands to the intended command string executed by the application. The application does not seem to return the command output in its response.

We submitted the payload ;sleep 5 in the pkgname parameter at the path /cmd,/tjp6jp6y4/zypkg_main/pkg_disable. The application took longer to respond to the PoC request than the original request, indicating that the injected sleep command caused a time delay, as Figure 10. shows. Be aware that other actions within the zypkg_main controller (e.g. pkg_install) are also affected by this issue.

Figure 10. OS command injection vulnerability in the pkgname parameter
Figure 10. OS command injection vulnerability in the pkgname parameter

Further analysis revealed that the vulnerability lies in the zymanager_cmd_interface() function within the zypkg_main_model.py and the StoExecRoot() function within the storage_func.py files. The application constructs a command string to perform the chosen operation of the package specified in the pkgname parameter, in some cases with additional arguments. The command string /usr/local/apache/web_framework/bin/executer_su /usr/bin/ipkg-cl -f /etc/zyxel/pkg_conf/zypkg_conf/zy-pkg.conf -t /i-data/.system/zy-pkgs/tmp disable &lt;PACKAGE> is an example of the disable operation where the user provides the value for &lt;PACKAGE>. The following is the decompiled source code of the affected functions:

def pkgdisable(pkgname):
    cmd_result = zymanager_cmd_interface(ZYPKG_DISABLE, pkgname)
    return cmd_result

def zymanager_cmd_interface(CmdID, pkgname=None):
    if pkgname == None:
        cmd = zypkgcmdname[CmdID]
        cmd_result = storage_func.StoExecRoot(cmd)
    else:
        cmd = zypkgcmdname[CmdID] + ' ' + pkgname
        cmd_result = storage_func.StoExecRoot(cmd)
    return cmd_result

The StoExecRoot() function calls the subproccess.Popen() function to execute the constructed command string passed in the cmd parameter. Since the shell parameter is set to True, the specified command will be executed through the shell, meaning that attackers can use whitespace and shell metacharacters to create shell commands. For example, the semicolon ";" metacharacter used in the PoC payload allows the execution of sequential commands, one after the other. The following is the decompiled source code of the affected function:

def StoExecRoot(cmd, withRetCode=False):
    exec_path = '/usr/local/apache/web_framework/bin/executer_su '
    arg_set = exec_path + cmd
    pipe = Popen(arg_set, shell=True, stdout=PIPE, stderr=None)
    if withRetCode:
        return (pipe.communicate()[0].strip(), pipe.returncode)
    else:
        return pipe.communicate()[0].strip()

Executing shell commands that incorporate unsanitized input from an untrusted source makes the application vulnerable to command injection, resulting in arbitrary command execution. For this reason, the use of shell=True is strongly discouraged in cases where the application constructs a command string from external input.

The vulnerabilities can be confirmed with an unauthenticated user via the following URLs by leveraging the authentication bypass vulnerability:

http[:]//NAS326/cmd,/ck6fup6/zypkg_main/pkg_disable/favicon.ico?pkgname=%26sleep%205
http[:]//NAS326/cmd,/ck6fup6/zypkg_main/pkg_install/favicon.ico?pkgname=a&active=b&volumeID=c%26id%3E/tmp/bugproved

Be aware that some operations (e.g. package installation) are executed in another thread, hence the vulnerability cannot be confirmed via time delays.

OS command injection vulnerabilities in time_machine_main

The specific flaw exists within the implementation of the service configuration mechanism in the time_machine_main.py component of the web management interface. Attackers can use a specially crafted value provided in the target_share query parameter attached to a URL with the path /cmd,/ck6fup6/time_machine_main/setTimeMachineStatus to execute arbitrary OS commands.

The web management interface appears to be vulnerable to OS command injection because attackers can use the semicolon ";" character to append arbitrary commands to the original command intended to generate avahi service configuration files. The application does not seem to return the command output in its response.

We submitted the payload ;sleep 5 in the share_name parameter at the path /cmd,/ck6fup6/time_machine_main/setTimeMachineStatus. The application took longer to respond to the PoC request than the original one, indicating that the injected sleep command caused a time delay, as Figure 11. shows.

Figure 11. OS command injection vulnerability in the target_share parameter
Figure 11. OS command injection vulnerability in the target_share parameter

Further analysis revealed that the vulnerability lies in the setTimeMachineStatus() and the set_time_machine_config() functions within the time_machine_main.py and time_machine_main_model.py files. The application constructs a command string to generate an avahi service configuration file. The following is the decompiled source code of the affected function:

def setTimeMachineStatus(cherrypy, arguments):
    auth_status = tools_cherrypy.authentication(arguments)
    if auth_status != tools_cherrypy.AUTH_PASS:
        return tools_cherrypy.gui_errmsg(auth_status)
    if arguments.has_key('target_share') and arguments.has_key('active'):
        time_machine_config = {}
        time_machine_config['active'] = arguments['active']
        time_machine_config['target_share'] = arguments['target_share']
        rvalue = time_machine_model.set_time_machine_config(time_machine_config)
        if rvalue != 'Success':
            return tools_cherrypy.GUI_SUCCESS.gui_errmsg(rvalue)
    else:
        return tools_cherrypy.GUI_SUCCESS.gui_errmsg('Argument error!')
    write_pyconf()
    return tools_cherrypy.GUI_SUCCESS

The command string constructed from the user-supplied target_share parameter will be executed in a subshell via the os.system() function, meaning that attackers can use whitespace and shell metacharacters to create shell commands. For example, the semicolon ";" metacharacter used in the PoC payload allows the execution of sequential commands, one after the other. The following is the decompiled source code of the affected function:

def set_time_machine_config(time_machine_config):
    pyconf.modify_conf_value(TIME_MACHINE_PYCONF_PATH, 'active', time_machine_config['active'])
    pyconf.modify_conf_value(TIME_MACHINE_PYCONF_PATH, 'target_share', time_machine_config['target_share'])
    if time_machine_config['active'] == 'true':
        cmd = '/usr/sbin/gen_avahi_config.sh tm %s' % time_machine_config['target_share']
        os.system(cmd)
    else:
        cmd = 'rm -f /etc/avahi/services/tm.service'
        os.system(cmd)
    return 'Success'

Executing shell commands that incorporate unsanitized input from an untrusted source makes the application vulnerable to command injection, resulting in arbitrary command execution. For this reason, the use of os.system() is strongly discouraged in cases where the command string is constructed from external input.

The vulnerability can be confirmed with an authenticated user via the following URL:

http[:]//NAS326/cmd,/ck6fup6/time_machine_main/setTimeMachineStatus?whoami=admin&active=true&target_share=%3bsleep%205

Acknowledgments

The vulnerabilities were found by Gábor Selján at BugProve.

Thanks to Zyxel’s PSIRT team for the effective coordination process.

Was it worth your time?

Sign up for our newsletter to receive articles like this in your inbox 1-2 times per month.