SSD Advisory – QNAP QTS5 – /usr/lib/ JSON parsing leads to RCE


QTS’s JSON parsing functionality is vulnerable to type confusion due to a failure to properly check the type of the json-object->data field. The bug allows an attacker to hijack control flow, and is accessible via the /cgi-bin/qid/qidRequestV2.cgi binary.

Successful exploitation would allow an unauthenticated attacker to execute arbitrary code as the admin user (equivalent to root in the QTS operating system). Under the default configuration the bug can be triggered by a network adjacent attacker, but if the HTTP server is configured for remote access it is fully remote.


An independent security researcher, dcs, working with SSD Secure Disclosure.



Affected Versions

QNAP TS-494 NAS running QTS operating system build 20230721

The bug is present across QNAP NAS devices running QTS, and exists at least as far back as build 20230325 (oldest build publicly available for download).

Vendor Response

The vendor has issued a fix for the vulnerability, information about the fix is available at:

Technical Analysis

QTS’s JSON functionality is based on the MIT-licensed json-c project and is implemented in /usr/lib/ The function json_tokener_parse_verbose iterates through a provided JSON string, and constructs a JSON object. The json-object structure is defined as follows:

struct json_object {
    enum json_type o_type; [1]
    json_func *_delete;
    json_func *_to_json_string;
    int ref_count;
    struct printbuf *pb;
    union data {
        boolean c_boolean;
        double c_double;
        int c_int;
        struct lh_table *c_object;
        struct array_list *c_array;
        char *c_string;
    } o;

The json_object->o union field allows a json_object to contain a variety of different data types. The data type held by each json_object is indicated in the o_type [1] field. While processing a JSON string, json_tokener_parse_verbose will read the first few characters of the JSON string, and set the o and o_type fields. For example the following string: string {} will return a json_object with o_type set to json_type_string and o set to a pointer to string, while the string 1234 {} will return a json_object with o_type json_type_int and o set to the integer value 1234.

When parsing an attacker-provided JSON string, the /home/httpd/cgi-bin/qid/qidRequestV2.cgi binary does not properly check the o_type field before attempting to add values to a json_object. Instead, it assumes that json_tokener_parse_verbose returns a json_object with o_type json_type_object. This is only true for strings that begin with {. A highly condensed summary of how qidRequestV2.cgi parses JSON data is as follows:

json_obj = json_tokener_parse_verbose(json_string);
json_string = json_object_new_string(cgi_value);
json_object_object_add(json_object, cgi_param, json_string); [2]

The call to json_object_object_add at [2] assumes that json_object->o_type is json_type_object. However that type is controlled by an attacker. By sending a specifically crafted JSON string, an attacker can get qidRequestV2.cgi to treat a provided string or integer value as a struct lh_table. This is especially dangerous since json_object_object_add attempts to call a function pointer in lh_table (json_object->o->hash_fn()). The JSON string “702111234474983745 {} will cause json_object_object_add to attempt to dereference an lh_table at address 0x4141414141414141.

Steps to reproduce/proof of concept

To reproduce the crash simply issue the following curl request to the NAS:

curl -X POST -H "Content-Type: application/json" -d "4702111234474983745 {}" "{NAS_IP}:8080/cgi-bin/qid/qidRequestV2.cgi?param=value"

This will cause the program to crash attempting to dereference a lh_table structure at 0x4141414141414141.


Get in touch