# QNAP Pre-Auth Root RCE Affecting ~450K Devices on the Internet
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 ~450K vulnerable QNAS NAS
instances on the Internet (statistical prediction). These vulnerabilities have
been responsibly reported, fixed and assigned
[CVE-2019-7192](https://nvd.nist.gov/vuln/detail/CVE-2019-7192\)) (CVSS 9.8),
[CVE-2019-7193](https://nvd.nist.gov/vuln/detail/CVE-2019-71923) (CVSS 9.8),
[CVE-2019-7194](https://nvd.nist.gov/vuln/detail/CVE-2019-7194) (CVSS 9.8),
[CVE-2019-7195](https://nvd.nist.gov/vuln/detail/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.
# Impact
## Vulnerable Instances
The following Shodan search reveals 564K QNAP instances on the Internet. Among
those, 8 of 10 randomly chosen instances have Photo Station enabled. (one can
tell from the login page) Therefore, statistically speaking, there should be
~450K instances having Photo Station enabled, and they were all vulnerable at
the time (2019).
![]()
~560K 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](https://www.qnap.com/zh-tw/security-
advisory/nas-201911-25) 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`:
```
[PHOTO_STATION]
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
```
eaee23cf0c2640ecbe2f43f11cf42871
```
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](/@happyholic1203/phpmyadmin-4-8-0-4-8-1-remote-code-
execution-257bcc146f8e) 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.
# Disclosure
- 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
# Conclusion
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.
暂无评论