QNAP Pre-Auth Root RCE Affecting ~312K Devices on the Internet

Henry Huang
May 18 · 6 min read

In 2019, I discovered multiple vulnerabilities in QNAP PhotoStation and CGI programs. These vulnerabilities can be chained into a pre-auth root RCE. All QNAP NAS models are vulnerable, and there are ~312K vulnerable QNAS NAS instances on the Internet (statistical prediction). These vulnerabilities have been responsibly reported, fixed and assigned CVE-2019–7192 (CVSS 9.8), CVE-2019–7193 (CVSS 9.8), CVE-2019–7194 (CVSS 9.8), CVE-2019–7195 (CVSS 9.8). This article is the first public disclosure, but only 3 of the vulnerabilities are disclosed, because they’re enough to achieve pre-auth root RCE.


Vulnerable Instances

The following Shodan search reveals 564K QNAP instances on the Internet. Among those, 590 of 1065 randomly chosen instances have Photo Station enabled. (checked via GET /photo/slideshow.php and see if it responds with Invalid album selection) Therefore, statistically speaking, with 95% confidence level, confidence interval 3, there should be ~312K instances having Photo Station enabled, and they were all vulnerable at the time (2019).

564K QNAP Instances Found on Shodan

Affected Photo Station Versions

All downloadable versions before the fixed ones (6.0.3, 5.2.11, 5.4.9) were affected.

Visit QNAP’s Security Advisory for details like version info.

Now, let’s look at the 3 vulnerabilities that will later be chained to make a pre-auth root RCE.

Vulnerability 1: Pre-Auth Local File Disclosure (Effectively a Login Bypass)

This vulnerability enables an attacker to read arbitrary file on the server WITHOUT authentication.

Vulnerable Code

The vulnerable code is in /p/api/video.php:

Local File Disclosure Vulnerability

exportFile simply outputs the file contents of $source_file, whose suffix is fully controllable by the GET/POST parameter filename. Therefore, we can read arbitrary file by specifying, say, filename=./../etc/passwd, making $source_file become /share/./../etc/passwd, which is equivalent to /etc/passwd.

However, to reach the above vulnerable code, we need to pass the check CHECK_ACCESS_CODE:

Access Code Check before the Vulnerability

Function definition:

Function Definition of CHECK_ACCESS_CODE

We need to avoid stepping into exit(). So we need to:

  • Get an album ID and access code of a publicly accessible album
  • Load that album’s access code into $_SESSION['access_code']
  • Get the value of $_SESSION['access_code']

Luckily, we can do all the above very easily WITHOUT any authentication!

Step 1 to Bypass CHECK_ACCESS_CODE: Album ID & Access Code

The following request creates a sample album and returns its album ID. This API is meant for sample albums, so it’s publicly accessible and it doesn’t require authentication:

Sample Album Creation and Album ID Retrieval

The response contains the album ID, and it looks like:

The Response Containing the ID of the Created Album

Step 2, 3 to Bypass CHECK_ACCESS_CODE: Setting and Getting $_SESSION['access_code']

The following sets $_SESSION['access_code'] to the access code of the album we specify (?album=qxAPdD)

Populating $_SESSION[‘access_code’]

The populated access code ($_SESSION[‘access_code’] == NjU1MzR8M3wxNTU0NzU3NTE4) can be found in the response’s javascript:

The Response to /photo/slideshow.php — Getting Value of $_SESSION[‘access_code’]

POC: Pre-Auth Local File Disclosure

With the album ID and access code from the above, we can bypass CHECK_ACCESS_CODE and read arbitrary files without authentication:

Pre-Auth Local File Disclosure

Upgrading the Pre-Auth Local File Disclosure to Privilege Escalation (Login Bypass)

We can use this pre-auth local file disclosure to read a magic file that contains a login token, which we can use to authenticate as a valid builtin user appuser.

Magic file /etc/config/.app_token:

token_ex = V2@rzKXK9vxyaQxpnRDbWYTyoYbi3DsIiby8mkbE1dCxDI=
  • the file content won’t change after factory reset
  • the file is generated when /authLogin.cgi?app=xxx&sid=yyy succeeds for the first time
  • token_ex is encrypted
  • PhotoStation caches a plaintext version of token_ex in /share/Multimedia/.@__thumb/ps.app.token
$ cat /share/Multimedia/.@__thumb/ps.app.token

Therefore, we can use vulnerability 1 to read the cached plaintext token to bypass the login and authenticate as appuser:

Authentication Bypass Using App Token

With this trick, vulnerability 1 is actually an authentication bypass.

Quick recap:

  • Use the sample album feature to create and retrieve a public album ID, along with its access code ($_SESSION[‘access_code’])
  • Use the album ID and access code to bypass CHECK_ACCESS_CODE and trigger the LFD (Local File Disclosure) vulnerability to read arbitrary file
  • Use the LFD to read /share/Multimedia/.@__thumb/ps.app.token and use it to authenticate as appuser

Vulnerability 2: Authenticated Session Tampering — Writing PHP Code to Session

Being authenticated as appuser gives us access to the SMTP setting, which has an improper filtering in the email string. By setting an email to, for example, <?=`$_POST[c]`?>@evil.com, an authenticated attacker can inject arbitrary PHP code into the session, this can be chained in the next vulnerability, or other file inclusion vulnerabilities (e.g. include '/path/to/sess_xxx').

POC: Authenticated Session Tampering

Injecting PHP code to $_SESSION

Vulnerability 3: (Pre-Auth) Writing Session to Arbitrary Location

This vulnerability enables an unauthenticated attacker to write session contents (serialized $_SESSION) to arbitrary location on the server.

Vulnerable code:

Vulnerability: Arbitrary Session Write

The session_id() is fully controllable via cookie QMS_SID. Therefore, the highlighted line would write an encoded (serialized) session into the file we specify.

POC: Writing Session to Arbitrary Location

Writing Encoded Session to Arbitrary File

The above works because $musicStationSessionPath . ‘/sess_' . session_id() will become /share/photostation/session/qts/sess_xxxxx/../../../../../mnt/ext/opt/photostation2/a.php, a publicly accessible file via the URL path /photo/a.php. (thanks to tsrm_realpath, because it will normalize sess_xxxxx/../bbb into bbb, even if sess_xxxxx doesn’t exist. This also caused phpMyAdmin 4.8.0~4.8.1 RCE that I found in 2018)

Chaining for Pre-Auth Root RCE

  • Use vulnerability 1 to bypass authentication and authenticate as appuser
  • Use vulnerability 2 to put PHP code (via SMTP email) in PHP session ($_SESSION)
  • Use vulnerability 3 to write the polluted PHP session to Photo Station’s web directory to make a webshell

Now, you might ask: so where is that root permission? We’re only appuser, right?

I’m also surprised when I found this: the web server runs as root! Therefore, you can actually read /etc/shadow using vulnerability 1.

A pity though: vulnerability 3 could’ve been a one-shot pre-auth root RCE. However, I couldn’t find a way to inject PHP code into $_SESSION without authentication.


  • 2019/06/14: reported technical details to QNAP
  • 2019/12/16: vendor fixed all 4 vulnerabilities, offered to provide a bounty (the amount is concealed due to the bounty terms)
  • 2019/12/31: got bounty


3 vulnerabilities are chained to get this pre-auth root RCE in QNAP PhotoStation, and it works on all QNAP’s NAS models. Several tricks for exploiting QNAP products are also disclosed. Hopefully QNAP fixes these tricks some day, otherwise I’m pretty sure there will be more high-CVSS CVEs coming up.

Key Takeaways:

  • UPGRADE YOUR QNAP NAS NOW, if you haven’t already
  • Turn any file disclosure into authentication bypass by reading the magic file /share/Multimedia/.@__thumb/ps.app.token
  • There is a way to decrypt /etc/config/.app_token, but I’ll leave it as homework for you
  • QNAP’s webserver runs as root
  • QMS_SID might give you some more 0days

Vendor Advisory


InfoSec Write-ups

A collection of write-ups from the best hackers in the…

Henry Huang

Written by

Into programming, bug hunting

InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. In a nutshell, we are the largest InfoSec publication on Medium. Maintained by Hackrew

Henry Huang

Written by

Into programming, bug hunting

InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. In a nutshell, we are the largest InfoSec publication on Medium. Maintained by Hackrew

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store