A Journey into Synology NAS — Part 4: HTTP Request Processing Flow and Vulnerability Analysis

cq674350529
10 min readDec 3, 2023

--

Preface

The previous two articles analyzed the services available on Synology NAS device from the perspective of a local area network. However, in most scenarios, Synology NAS devices are used for remote access, with the primary entry point being through 5000/http (5001/https) (excluding the use of QuickConnect or other proxies for now). Therefore, this article will mainly analyze the HTTP request processing flow and its handling mechanism, then share several security issues discovered in some packages.

HTTP Request Processing Flow

During a regular login process, some http requests are captured. As we can see, these requests are sent to endpoints like /webapi/query.cgi, /webapi/login.cgi and /webapi/entry.cgi. According to the Synology developer's document, the general routines to interact with the device are as follows:

  1. Retrieve API-related information through query.cgi;
  2. Authenticate and obtain a session ID through login.cgi and encryption.cgi;
  3. Send requests and parse responses through entry.cgi;
  4. Logout after completing the interaction.

An example of a specific request is as follows. For most requests, the endpoint is /webapi/entry.cgi. As to the POST data, it will contain some common parameters such as api, method, version and so on. Among them, the api parameter represents the requested API name, the method parameter indicates the method within the requested API, and version parameter represents the requested API version.

For API requests, a meta-data file named SYNO.***.***.lib in json format is used in the back end to define information related to the API. An example is as follows

{
"SYNO.Core.PersonalNotification.Event": { // API name
"allowUser": [ "admin.local"], // which group can access this API
"appPriv": "",
"authLevel": 1, // authentication is required or not (0 means no authentication)
"disableSocket": false,
"lib": "lib/SYNO.Core.PersonalNotification.so", // the file to handle this request
"maxVersion": 1,
"methods": { // which methods are available and its corresponding version
"1": [{
"fire": {
"allowUser": [ "admin.local","normal.local" ], // overwrite above definition
"grantByUser": false,
"grantable": true }
}]
},
"minVersion": 1,
"priority": 0,
"socket": ""
}
}

According to the above information, we know how to make a specific request to reach the target binary in the back end.

The following is a simple and high-level process flow. First, a request is sent to the device via port 5000. Then based on the request url, nginx will schedule the corresponding cgi to handle the request, such as query.cgi, login.cgi, and entry.cgi. Among them, entry.cgi is the endpoint for most POST requests. These cgi may communicate with another two process: synocgid and synoscgi. synocgid mainly handles session-related stuff. And synoscgi is mainly responsible for dispatching specific requests to the target binary.

Security Issues

With a general understanding of the HTTP request processing flow and its handling mechanism, we can start to analyze those modules related to request processing on the Synology NAS device. In general, there are mainly two attack surfaces: the DSM operating system itself and the various packages provided by Synology. Next, we will share some security issues found in detail.

Diagnosis Tool

As mentioned before, Diagnosis Tool is a utility package provided by Synology, supporting packet capturing, debugging, and so on. The GUI of this tool and an example of a captured http request for this package are shown below.

The above request will be handled by the packet_capture.cgi binary. Some code snippets are as follows. In handle_action_start(), it will first obtain parameters, then pass them to another binary called tcpdump_wrapper as parameters in json string.

__int64 __fastcall handle_action_start(__int64 a1, __int64 a2, const char *a3, const char *a4)
{
// ...
Json::Value::Value((Json::Value *)&v39, (const std::string *)&v28);
v17 = Json::Value::operator[](&v35, "output_dir");
Json::Value::operator=(v17, &v39);
Json::Value::~Value((Json::Value *)&v39);
Json::Value::Value((Json::Value *)&v40, v4);
v18 = Json::Value::operator[](&v35, "expression");
Json::Value::operator=(v18, &v40);
Json::Value::~Value((Json::Value *)&v40);
Json::Value::Value((Json::Value *)&v41, v6);
v19 = Json::Value::operator[](&v35, "interface");
Json::Value::operator=(v19, &v41);
Json::Value::~Value((Json::Value *)&v41);
Json::FastWriter::write((Json::FastWriter *)&v33, (const Json::Value *)&v37);
std::string::assign((std::string *)&v29, (const std::string *)&v33);
// ...
if (SLIBCExec("/var/packages/DiagnosisTool/target/bin/tcpdump_wrapper", "--params", v29, 0LL, 0LL) == -1 )
// ...

The main() function in binary tcpdump_wrapper is shown below. As can be seen, sub_401F10() is called to parse the output_dir, expression and interface parameters, which are then passed to RunTcpDump(). In RunTcpdump(), it will ultimately invoke execve() to execute the command "tcpdump -i <interface> -w <file> -C 10 -s 0 filter_expression".

__int64 __fastcall main(signed int a1, char **a2, char **a3)
{
if ( a1 > 1 )
{
// ...
if ( v3 != 2 && !strcmp(v4[1], "--params") )
{
std::string::string(&v11, v4[2], &v6);
// resolve parameters from json string
sub_401F10(&v11, &output_dir, &expression,&interface);
// ...
}
}
if (sub_4019D0(&output_dir) )
{
if (sub_401900() && !RunTcpdump(&output_dir, &expression, &interface) )
{
// ...

Calling execve() is relatively safe to avoid command injection issues. However, the filter_expression parameter is user-controlled. By examining the document of tcpdump, it's found that there is a -z option, when combined with -C or -G option, it can also lead to command execution.

Fortunately, the -C option is already in the command "tcpdump -i <interface> -w <file> -C 10 -s 0 filter_expression". Therefore, by crafting a filter_expression parameter like -z<path to your shell script>, we can achieve code execution.

DS File

DS File is a mobile application provided by Synology, facilitating access and management of files on DiskStation from mobile devices. The process of accessing DiskStation using this app is similar to the web-based process. When attempting to login into DiskStation, the authentication process involves a PKI-based encryption mechanism. In certain situations, such as entering an incorrect target IP or temporary network unavailability, normal requests may fail, leading DS File to send additional requests.

By examining the third request, it’s observed that the request header contains the Authorization information encoded in Base64. In other words, it's equivalent to plaintext.

As a result, in an insecure network environment, attempting to access DiskStation via the DS File application could lead to the interception of the user's plaintext account information by MITM through simply discarding or redirecting the corresponding request.

Synology Calendar

This package is a web-based application designed for managing daily events and tasks. It supports features such as adding attachments to events and sharing schedules. In general, there are two ways to add attachments to events: uploading from a local device or uploading from the NAS. The following is an example of a normal user creating an event and adding an attachment, along with some HTML code related to attachment links.

As we can see, the file name is concatenated to the href link. Can we control the corresponding href link by crafting a file name? After testing, it was found that due to the lack of file name validation, by forging a suitable file name, one can alter the corresponding href link while making the displayed file name appear normal.

Furthermore, with the help of the event sharing feature, this event can be shared with the administrator group. When someone from the administrator group views the event and clicks on the corresponding attachment, the associated request will be executed. Therefore, by exploiting this vulnerability, a user with normal privileges can execute “arbitrary” requests with “administrator” permissions, such as adding themselves to the administrator group.

Media Server

Media Server package provides services related to multimedia, allowing playback of multimedia content on the NAS through DLNA/UPnP. After installation, it initiates several custom services as follows.

Through simple analysis, it turns out that there are some accessible urls in ./sbin/dms without the need for authentication.

The first interesting API is related to videotranscoding.cgi, and the corresponding request URL format is "http://%s:%d/transcoder/videotranscoding.cgi/%s/id=%d%s". The code responsible for handling this request is shown below. It can be observed that if the URL contains the strings "id=" and "?", the content between "id=" and "?" will be copied to the dest buffer. However, due to not considering the order of occurrence, if the request URL is "http://%s:%d/transcoder/videotranscoding.cgi/VideoStation?id=1", integer underflow would occur when calling strncpy() later.

__int64 sub_406E80(__int64 a1)
{
// ...
v4 = getenv("REQUEST_URI");
snprintf(s, 0x800uLL, "%s", v4);
v99 = strstr(s, "id=");
if ( v99 )
{
v5 = strchr(s, '?');
if ( v5 )
strncpy(dest, v99 + 3, v5 - (v99 + 3)); // integer underflow
}
// ...
std::string::assign(v3, dest, strlen(dest));
// ...
sub_403F50(a1, v1, v3, (std::string *)(a1 + 136));

Assuming that the request URL format matches the program’s expectations, then sub_403F50() will be invoked later, and its third parameter corresponds to the content between "id=" and "?" copied from request URL. In sub_403F50(), after a simple validation of parameter a2, parameter a3 will be formatted as the id parameter. Due to the lack of proper validation on parameter a3, there exists SQL injection vulnerability.

__int64 sub_403F50(__int64 a1, std::string *a2, _QWORD *a3, std::string *a4)
{
// ...
if ( !(unsigned int)std::string::compare(a2, "MediaServer") )
{
std::string::assign((std::string *)v32, "mediaserver", 0xBuLL);
std::string::assign((std::string *)&v34, "MediaServer", 0xBuLL);
std::string::assign((std::string *)v33, "video", 5uLL);
}
else
{
if ( (unsigned int)std::string::compare(a2, "VideoStation") )
goto LABEL_4;
std::string::assign((std::string *)v32, "video_metadata", 0xEuLL);
std::string::assign((std::string *)&v34, "VideoStation", 0xCuLL);
std::string::assign((std::string *)v33, "video_file", 0xAuLL);
}
snprintf(s, 0x100uLL, "SELECT * from %s where id = %s", v33[0], (const char *)*a3); // SQL injection

Another similar API is jpegtnscaler.cgi, and the corresponding request URL format is "http://%s:%d/transcoder/jpegtnscaler.cgi/%s/%d.%s". The code responsible for handling this request is as follows. It can be observed that there is no length parameter validation before calling strncpy(). By constructing a request like "http://%s:%d/transcoder/jpegtnscaler.cgi/<a*0x450>/1", we can cause buffer overflow when calling strncpy().

__int64 main(__int64 a1, char **a2, char **a3)
{
// ...
v3 = getenv("REQUEST_URI");
// ...
v4 = strrchr(v3, '/');
v5 = v4;
// ...
v6 = strtol(v4 + 1, 0LL, 10);
bzero(s, 0x400uLL);
strncpy(s, v3, v5 - v3); // buffer overflow

Audio Station

The Audio Station package provides features such as listening to radio programs, managing music libraries, creating personal playlists, and sharing with friends. After installing this package, there are some custom CGI programs in its installation path, such as media_server.cgi, web_player.cgi, audiotransfer.cgi, etc. Some requests captured when using this package are shown below.

According to the HTTP request processing flow mentioned above, execl_cgi() is responsible for handling custom CGI requests. Importantly, in certain scenarios, authentication is handled by custom CGI programs.

Upon analysis, the most interesting API is audiotransfer.cgi, with the corresponding request URL format "http://%s:%d/webman/3rdparty/AudioStation/webUI/audiotransfer.cgi/%s.%s". Part of code for handling this request is as follows. It can be observed that sub_402730() is called at the beginning of the main() function. In sub_402730() , the last part of the request URL is obtained and passed to MediaIDDecryption(). In MediaIDDecryption(), after calculating the length of parameter a1, it will copy the first 6 bytes, and then call snprintf(). Due to the controllable size parameter and string content in the snprintf() call, there exists a buffer overflow vulnerability. Moreover, this process does not handle authentication, meaning no authentication is required when accessing this endpoint. Therefore, by sending a specific request, a remote unauthenticated user can trigger this buffer overflow vulnerability.

__int64 main(__int64 a1, char **a2, char **a3)
{
sub_402730((__int64)v5);
// ...

_BOOL8 sub_402730(__int64 a1)
{
// ...
v8 = getenv("REQUEST_URI");
snprintf(s, 0x400uLL, "%s", v8);
// ...
v11 = strrchr(s, '/');
v12 = v11;
if ( v11 )
{
// ...
v15 = MediaIDDecryption((__int64)(v12 + 1));
// ...

__int64 MediaIDDecryption(const char *a1)
{
// ...
v1 = strlen(a1);
if ( v1 > 5 )
{
v3 = (v1 - 6) >> 1;
snprintf(s, 7uLL, "%s", a1);
v14 = 0; v4 = s; v5 = (char *)&v14;
do
{
v6 = *v4; --v5; ++v4; v5[6] = v6;
}
while ( v5 != &v13 ); // copy first 6 bytes
__isoc99_sscanf(s, "%x", &v8);
__isoc99_sscanf(&v14, "%x", &v9);
snprintf(v17, v3 + 1, "%s", a1 + 6);
snprintf(v18, v3 + 1, "%s", &a1[v3 + 6]); // buffer overflow
// ...

Regarding vulnerability exploitation, @fenix conducted analysis and testing based on DSM 5.2-5592 and Audio Station 5.4-2860. The relevant contexts are x86 architecture, NX protection, and semi-random ASLR. Here are some additional notes:

  • For x86 architecture, based on DSM 6.x, even if ASLR is fully random, stable exploitation can be achieved by finding suitable gadgets, without the need for heap spraying or brute-forcing.
  • On DSM 6.x, elevation of privileges is still required after obtaining a shell.
  • For x86-64 architecture, due to the issue of address truncation, a suitable approach for exploitation has not been found yet.

One More Thing

The above only listed a few typical packages and some issues found within them. In reality, Synology DSM has many features and numerous packages available for analysis. Synology officially releases security advisories for its products irregularly. Combined with Synology archive repository, it's convenient to conduct patch analysis and vulnerability exploration.

Summary

In this article, we focus on the remote scenario of Synology NAS, and analyze its process and handling mechanisms of web requests. Additionally, we share some security issues discovered within several typical packages.

This article concludes the series, hoping it brings insights to those interested in Synology NAS devices.

References

--

--