Back to the articles

CVE-2023-5372 - Post-auth blind Python code injection vulnerabilities in Zyxel’s NAS326 and NAS542 devices

picture of the author
Gábor Selján
January 30, 2024 6 mins read
CVE-2023-5372 - Post-auth blind Python code injection vulnerabilities in Zyxel’s NAS326 and NAS542 devices

Table of contents

  1. Disclosure timeline
  2. Affected products
  3. Product URLs
  4. Summary
  5. Details
  6. Acknowledgments

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 23, 2023: BugProve reported several vulnerabilities to Zyxel.

Oct 16, 2023: Zyxel assigned CVE-2023-5372 and indicated the target date of Jan 30, 2024.

Nov 30, 2023: Zyxel marked NAS326 and NAS542 devices as EOL without migration path.

Jan 19, 2024: BugProve requested confirmation regarding the patch release.

Jan 23, 2024: Zyxel confirmed the target date of Jan 30, 2024.

Jan 25, 2024: Zyxel released firmware versions V5.21(AAZF.16)C0 and V5.21(ABAG.13)C0.

Jan 30, 2024: Coordinated public release of advisory.

Affected products

Zyxel’s NAS326 and NAS542 model devices running firmware version V5.21(AAZF.15)C0 or V5.21(ABAG.13)C0 and earlier are affected. Note that both the vulnerable models reached end-of-vulnerability-support on Dec. 31, 2023.

Product URLs

Summary

BugProve reported many vulnerabilities to Zyxel in their NAS devices in July and August 2023. Zyxel has decided to address these vulnerabilities in a specific order and schedule. CVE-2023-5372 is the last one from a series of vulnerabilities that also included a critical authentication bypass tracked as CVE-2023-4473 and a high-severity command injection vulnerability known as CVE-2023-37927.

Post-auth blind Python code injection vulnerabilities exist when some Zyxel NAS versions improperly handle user provided input. These vulnerabilities may allow an authenticated attacker to execute code on an affected device remotely.

The specific flaws exist within the implementation of the service and package management mechanism in the manipulate_services(), manipulate_services_dependency() and manipulate_packages() functions within the appzone_main_model.py component of the web management interface.

A specially crafted query parameter attached to the URL of the web management interface can be used to execute arbitrary code. An attacker could leverage these vulnerabilities to execute code in the context of the WSGI server process, running as root.

Details

The following analysis has been performed on firmware version V5.21(AAZF.14)C0 of Zyxel's NAS326 device. We did not observe any differences compared to the latest V5.21(AAZF.15)C0.

The web management interface appears to be vulnerable to Python code injection attacks. It is possible to use the parenthesis "()", the double quote ("), single quote (') and various other shell metacharacters to inject arbitrary Python code in the appargs parameter at the path /cmd,/ck6fup6/appzone_main/manipulate_services or /cmd,/ck6fup6/mobile_main/manipulate_services. Note that the manipulate_services_dependency() and manipulate_packages() functions in the appzone_main_model.py file also seem to be affected by this issue. The command output does not appear to be returned in the application's responses. However, it is possible to cause the application to wait for a specified time, to verify that a command was executed.

We submitted the payload ")!=os.system("sleep 5 in the appargs parameter at the path /cmd,/ck6fup6/appzone_main/manipulate_services. 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 1. shows.

Figure 1. Python code injection vulnerability in the appargs parameter at /appzone_main/manipulate_services
Figure 1. Python code injection vulnerability in the appargs parameter at /appzone_main/manipulate_services

Note that the same vulnerability can be triggered via another path as well. We submitted the payload ")!=os.system("sleep 5 in the appargs parameter at the path /cmd,/ck6fup6/mobile_main/manipulate_services. In this case too, 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 2. shows.

Figure 2. Python code injection vulnerability in the appargs parameter at /mobile_main/manipulate_services
Figure 2. Python code injection vulnerability in the appargs parameter at /mobile_main/manipulate_services

Further analysis revealed that the vulnerability lies within the implementation of the service and package management mechanism. The application creates service_unit objects on the fly according to the user-provided parameters and maps them to appclass objects that are instances of applications like the FTP or the Print Server to perform certain operations like enabling or disabling them.

The manipulate_services(), manipulate_services_dependency() and manipulate_packages() functions will map the received list of service_unit objects to their corresponding appclass object and invoke the specified action method using the eval() function. The following is the relevant excerpt of the decompiled source code of the affected functions within the appzone_main_model.py file:

def manipulate_services(service_list):
...SNIP...
    for s_item in service_list:
        if app_mapper.has_key(s_item.s_name):
            s_instance = app_mapper[s_item.s_name]
            eval('s_instance.%s(*%s)' % (s_item.s_action, s_item.s_args))
        else:
...SNIP...

    if package_list:
        s_instance = app_mapper['Package']
        eval('s_instance.%s("%s")' % (package_action, string.join(package_list, '|')))
    return


def manipulate_services_dependency(usertype, service_list):
...SNIP...
    for s_item in service_list:
        if app_mapper.has_key(s_item.s_name):
            s_instance = app_mapper[s_item.s_name]
            eval('s_instance.%s(*%s)' % (s_item.s_action, s_item.s_args))
        else:
            package_list.append(s_item.s_name)
            package_action = s_item.s_action

    if package_list:
...SNIP...
        s_instance = app_mapper['Package']
        eval('s_instance.%s("%s")' % (package_action, string.join(package_list, '|')))
    return

def manipulate_packages(service_list):
...SNIP...
    for s_item in service_list:
        if pkg_mapper.has_key(s_item.s_name):
            s_instance = pkg_mapper[s_item.s_name]
            rvalue.append(eval('s_instance.%s(*%s)' % (s_item.s_action, s_item.s_args)))
        else:
...SNIP...
    return rvalue

Python code injection vulnerabilities arise when the application incorporates user-controllable data into a string that is dynamically evaluated by a code interpreter, in this case via the eval() function. If the user data is not strictly validated, an attacker can use crafted input to modify the original code to be executed, and inject arbitrary code that will be executed by the server. Note that the attacker must bypass the original s_instance.%s("%s") Python statement to execute malicious code.

The vulnerabilities can be confirmed with an authenticated user via the following URLs:

http[:]//NAS326/cmd,/ck6fup6/appzone_main/manipulate_services?action=enable&appargs=%22)!%3dos.system(%22sleep%205
http[:]//NAS326/cmd,/ck6fup6/mobile_main/manipulate_services?action=enable&appargs=%22)!%3dos.system(%22sleep%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.