CVE-2020-15175: Files or Directories Accessible to External Parties (CWE-552)
value: In GLPI before version 9.5.2, the `?pluginimage.send.php?` endpoint allows a user to specify an image from a plugin. The parameters can be maliciously crafted to instead delete the .htaccess file for the files directory. Any user becomes able to read all the files and folders contained in “/files/”. Some of the sensitive information that is compromised are the user sessions, logs, and more. An attacker would be able to get the Administrators session token and use that to authenticate. The issue is patched in version 9.5.2.
CWE description: The product makes files or directories accessible to unauthorized actors, even though they should not be.
CVSS v3 base score: 9.1
Published: 2020-10-07T19:15Z
Repository: https://github.com/glpi-project/glpi
Language: PHP
Files changed: CHANGELOG.md, front/pluginimage.send.php
Commit date: 2020-10-06 13:57:20 +0200
Commit message: Merge pull request from GHSA-rm52-jx9h-rwcp

- Prevent removal of any plugin file from unauthenticated user
- Prevent access to files outside plugins directories
- Prevent access to files from inactive plugins
- Limit access to images and fix content-type
- Add changelog entry
Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4cd96b2e8..73115a4d96 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,10 +8,11 @@ The present file will list all changes made to the project; according to the
 ### API changes
 
 #### Removed
 
 - Ability to use SQL expressions as string in criterion values in SQL iterator (replaced by usage of `QueryExpression`).
+- Ability to delete a plugin image using `/front/pluginimage.send.php` script.
 
 ## [9.5.1] 2020-07-16
 
 ## [9.5.0] 2020-07-07
 
diff --git a/front/pluginimage.send.php b/front/pluginimage.send.php
index 722d10c3a3..f2a4e46e2a 100644
--- a/front/pluginimage.send.php
+++ b/front/pluginimage.send.php
@@ -41,36 +41,38 @@
 
 use Glpi\Event;
 
 include ('../inc/includes.php');
 
-if (!isset($_GET["name"]) || !isset($_GET["plugin"])) {
+if (!isset($_GET["name"]) || !isset($_GET["plugin"]) || !Plugin::isPluginActive($_GET["plugin"])) {
    Event::log("-1", "system", 2, "security",
               //TRANS: %s is user name
               sprintf(__('%s makes a bad usage.'), $_SESSION["glpiname"]));
    die("security");
 }
 
+$dir = GLPI_PLUGIN_DOC_DIR."/".$_GET["plugin"]."/";
+$filepath = $dir.$_GET["name"];
+
 if ((basename($_GET["name"]) != $_GET["name"])
-    || (basename($_GET["plugin"]) != $_GET["plugin"])) {
+    || (basename($_GET["plugin"]) != $_GET["plugin"])
+    || !Toolbox::startsWith(realpath($filepath), realpath(GLPI_PLUGIN_DOC_DIR))
+    || !Document::isImage($filepath)) {
 
    Event::log("-1", "system", 1, "security",
               sprintf(__('%s tries to use a non standard path.'), $_SESSION["glpiname"]));
    die("security");
 }
-$Path = GLPI_PLUGIN_DOC_DIR."/".$_GET["plugin"]."/";
 
 // Now send the file with header() magic
 header("Expires: Sun, 30 Jan 1966 06:30:00 GMT");
 header('Pragma: private'); /// IE BUG + SSL
 header('Cache-control: private, must-revalidate'); /// IE BUG + SSL
 header('Content-disposition: filename="' . $_GET["name"] . '"');
-header("Content-type: image/png");
 
-if (file_exists($Path.$_GET["name"])) {
-   readfile($Path.$_GET["name"]);
-   if (isset($_GET["clean"])) {
-      unlink($Path.$_GET["name"]);
-   }
+if (file_exists($filepath)) {
+   header("Content-type: " . Toolbox::getMime($filepath));
+   readfile($filepath);
 } else {
+   header("Content-type: image/png");
    readfile($CFG_GLPI['root_doc'] . "/pics/warning.png");
 }

Vulnerable code: if (!isset($_GET["name"]) || !isset($_GET["plugin"])) {
    || (basename($_GET["plugin"]) != $_GET["plugin"])) {
$Path = GLPI_PLUGIN_DOC_DIR."/".$_GET["plugin"]."/";
header("Content-type: image/png");
if (file_exists($Path.$_GET["name"])) {
   readfile($Path.$_GET["name"]);
   if (isset($_GET["clean"])) {
      unlink($Path.$_GET["name"]);
   }
Fixed code: - Ability to delete a plugin image using `/front/pluginimage.send.php` script.
if (!isset($_GET["name"]) || !isset($_GET["plugin"]) || !Plugin::isPluginActive($_GET["plugin"])) {
$dir = GLPI_PLUGIN_DOC_DIR."/".$_GET["plugin"]."/";
$filepath = $dir.$_GET["name"];

    || (basename($_GET["plugin"]) != $_GET["plugin"])
    || !Toolbox::startsWith(realpath($filepath), realpath(GLPI_PLUGIN_DOC_DIR))
    || !Document::isImage($filepath)) {
if (file_exists($filepath)) {
   header("Content-type: " . Toolbox::getMime($filepath));
   readfile($filepath);
   header("Content-type: image/png");