Back to the articles
CVE-2023-4473 & CVE-2023-4474 - Authentication bypass and multiple blind OS command injection vulnerabilities in Zyxel’s NAS326 devices
Table of contents
Disclaimer: The following vulnerability was detected by BugProve's security research team conducting analysis on publicly available products/firmware. Firmware uploaded by users to BugProve's platform have no connection with any of our own research projects. For more information, check out our Vulnerability Disclosure Policy.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 <PACKAGE>
is an example of the disable operation where the user provides the value for <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.
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.