Local File Inclusion (LFI) Vulnerability in InPost Gallery
One of the ways we keep track of vulnerabilities in WordPress plugins to provide our customers with the best data is by monitoring our websites for apparent activity by hackers. We recently had a request for a file from the plugin InPost Gallery, /wp-content/plugins/inpost-gallery/js/front.js. We don’t have that plugin installed on the website, so the request would likely be from someone probing for usage of the plugin. In looking over the plugin for something that hackers might target, we found a couple of vulnerabilities and some additional security issues. We are not sure if either of the vulnerabilities we found are are what the hacker was looking for or if there is still some other issue lurking in the plugin.
The more serious of the two vulnerabilities was a local file inclusion (LFI) vulnerability that would have allowed the hacker to cause a specified PHP file to to be included. That type of vulnerability could be used to get around protection in place in a file that restricts it from being loaded directly.
The plugin registers the function get_gallery_by_shortcode() to be accessible to anyone logged in or not through WordPress’ AJAX functionality (through the file /index.php):
94 95 | add_action('wp_ajax_inpost_gallery_get_gallery', array(__CLASS__, 'get_gallery_by_shortcode')); add_action('wp_ajax_nopriv_inpost_gallery_get_gallery', array(__CLASS__, 'get_gallery_by_shortcode')); |
As of version 2.1.2, that function in turns passed a user specified value, “popup_shortcode_key”, in to the function render_html() without doing any validation:
762 763 764 765 766 767 768 769 | public static function get_gallery_by_shortcode() { $attributes = (array) json_decode(base64_decode($_REQUEST['popup_shortcode_attributes'])); $attributes['show_in_popup'] = 0; $shortcode_key = $_REQUEST['popup_shortcode_key']; echo self::render_html("views/" . self::get_shortcode_key_folder($shortcode_key) . "/" . $shortcode_key . ".php", $attributes); exit; } |
Which in turn passes the value to the include() function:
418 419 420 421 422 423 424 425 | public static function render_html($pagepath, $data = array()) { $pagepath = self::get_application_path() . '/' . $pagepath; @extract($data); ob_start(); include($pagepath); return ob_get_clean(); } |
After we notified the developer of the issue, version 2.1.2.1 was released, which checks if the value of the user input “popup_shortcode_key” is one of its intended values using the function get_shortcode_key_folder() before allowing it to be included:
772 773 774 775 | if (self::get_shortcode_key_folder($shortcode_key)) { echo self::render_html("views/" . self::get_shortcode_key_folder($shortcode_key) . "/" . $shortcode_key . ".php", $attributes); } |
Proof of Concept
The following proof of concept will cause the a specified .php file from the root directory of the website to be included.
Make sure to replace “[path to WordPress]” with the location of WordPress and “[PHP filename]” with the name of the file without the “.php” portion (as that is already included).
http://[path to WordPress]/wp-admin/admin-ajax.php?action=inpost_gallery_get_gallery&popup_shortcode_key=../../../../[PHP file name]
Timeline
- 10/16/2016 – Developer notified.
- 10/18/2016 – Version 2.1.2.1 released, which fixes the issue.
Hi, i’m trying to reproduce this exploitation but i’ve always a blank page, have you got i idea?
(To do it, i’ve got checkout from the author’s repository a the revision r1477954 and checked that the code is the same as you… and it is.)
Thx
We just tried the proof of concept again and it worked. If we didn’t specify a file that should be included or included “.php” in the [PHP file name] we get a blank page, so if you are deviating from the proof of concept in some way, you could get a blank page.
Thx for your answer.
I’ve created a file ‘a.php’ (just echo a phpinfo() to test), saved on “/” of the disk and so my resquet was:
http://localhost/wp-admin/admin-ajax.php?action=inpost_gallery_get_gallery&popup_shortcode_key=../../../../../../../../../../../a
(many “../” to be sure to be on “/” of the disk)
So i think i’m not deviating from the POC.
Could you send me an archive of the plugin that you used please (there is no archive directory on the editor’s plugin)
Did you test with the last version of WordPress (4.9.1)?
Ok, i’ve tried again, with a fresh install of wp + the plugin + a beer and it’s ok !
Thanks 🙂
But this LFI seems to be quite limited, we can’t load files other than .php file, php seems to escape %00 in include file path:
[Wed Dec 20 10:40:38.425746 2017] [:error] [pid 4539] [client 192.168.8.100:37124] PHP Warning: include(): Failed opening ‘/var/www/html/wp-content/plugins/aaa//views//../../../../../../../../../../../a.log\\0.php’ for inclusion (include_path=’.:/usr/share/php:/usr/share/pear’) in /var/www/html/wp-content/plugins/aaa/index.php on line 423
That seems like a good thing, as that would have limited what a hacker could have done with this before it got fixed.