Suite CRM version 7.14.2 allows including local php files. This is possible because the application is vulnerable to LFI.
Vulnerability
A local file inclusion (LFI) vulnerability has been identified in Suite CRM that, under certain conditions, could allow an attacker to obtain remote command execution. The attacker must have minimum privileges.
Vulnerable path
Below I will show you the vulnerable path in code, from the source to the sink.
// Index.php
use App\Kernel;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;
[...]
$legacyRoute = $kernel->getLegacyRoute($request);
if (!empty($legacyRoute)) {
$path = './legacy';
if (!empty($legacyRoute['dir'])) {
$path .= '/' . $legacyRoute['dir'];
}
chdir($path);
$access = $legacyRoute['access'] ?? false;
if ($access === false) {
http_response_code(404);
exit;
}
if (file_exists($legacyRoute['file'])) {
/* @noinspection PhpIncludeInspection */
require $legacyRoute['file']; // Vulnerability Here
} else {
http_response_code(404);
exit;
}
}
[...]
// Index.php
use App\Kernel;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;
[...]
$legacyRoute = $kernel->getLegacyRoute($request);
if (!empty($legacyRoute)) {
$path = './legacy';
if (!empty($legacyRoute['dir'])) {
$path .= '/' . $legacyRoute['dir'];
}
chdir($path);
$access = $legacyRoute['access'] ?? false;
if ($access === false) {
http_response_code(404);
exit;
}
if (file_exists($legacyRoute['file'])) {
/* @noinspection PhpIncludeInspection */
require $legacyRoute['file']; // Vulnerability Here
} else {
http_response_code(404);
exit;
}
}
[...]
// Index.php
use App\Kernel;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;
[...]
$legacyRoute = $kernel->getLegacyRoute($request);
if (!empty($legacyRoute)) {
$path = './legacy';
if (!empty($legacyRoute['dir'])) {
$path .= '/' . $legacyRoute['dir'];
}
chdir($path);
$access = $legacyRoute['access'] ?? false;
if ($access === false) {
http_response_code(404);
exit;
}
if (file_exists($legacyRoute['file'])) {
/* @noinspection PhpIncludeInspection */
require $legacyRoute['file']; // Vulnerability Here
} else {
http_response_code(404);
exit;
}
}
[...]
// Index.php
use App\Kernel;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;
[...]
$legacyRoute = $kernel->getLegacyRoute($request);
if (!empty($legacyRoute)) {
$path = './legacy';
if (!empty($legacyRoute['dir'])) {
$path .= '/' . $legacyRoute['dir'];
}
chdir($path);
$access = $legacyRoute['access'] ?? false;
if ($access === false) {
http_response_code(404);
exit;
}
if (file_exists($legacyRoute['file'])) {
/* @noinspection PhpIncludeInspection */
require $legacyRoute['file']; // Vulnerability Here
} else {
http_response_code(404);
exit;
}
}
[...]
// Kernel.php
public function getLegacyRoute(Request $request): array {
$this->initializeBundles();
$this->initializeContainer();
if ($this->container->has('legacy.route.handler')) {
return $this->container->get('legacy.route.handler')->getLegacyRoute($request);
}
return [];
}
// Kernel.php
public function getLegacyRoute(Request $request): array {
$this->initializeBundles();
$this->initializeContainer();
if ($this->container->has('legacy.route.handler')) {
return $this->container->get('legacy.route.handler')->getLegacyRoute($request);
}
return [];
}
// Kernel.php
public function getLegacyRoute(Request $request): array {
$this->initializeBundles();
$this->initializeContainer();
if ($this->container->has('legacy.route.handler')) {
return $this->container->get('legacy.route.handler')->getLegacyRoute($request);
}
return [];
}
// Kernel.php
public function getLegacyRoute(Request $request): array {
$this->initializeBundles();
$this->initializeContainer();
if ($this->container->has('legacy.route.handler')) {
return $this->container->get('legacy.route.handler')->getLegacyRoute($request);
}
return [];
}
// LegacyRouteHandler.php
public function getLegacyRoute(Request $request): array {
[...]
if ($this->isLegacyNonViewActionRoute($request)) {
return $this->legacyNonViewActionRedirectHandler->getIncludeFile($request);
}
[...]
}
// LegacyRouteHandler.php
public function getLegacyRoute(Request $request): array {
[...]
if ($this->isLegacyNonViewActionRoute($request)) {
return $this->legacyNonViewActionRedirectHandler->getIncludeFile($request);
}
[...]
}
// LegacyRouteHandler.php
public function getLegacyRoute(Request $request): array {
[...]
if ($this->isLegacyNonViewActionRoute($request)) {
return $this->legacyNonViewActionRedirectHandler->getIncludeFile($request);
}
[...]
}
// LegacyRouteHandler.php
public function getLegacyRoute(Request $request): array {
[...]
if ($this->isLegacyNonViewActionRoute($request)) {
return $this->legacyNonViewActionRedirectHandler->getIncludeFile($request);
}
[...]
}
// LegacyRedirectHandler.php
public function getIncludeFile(Request $request): array {
$baseUrl = $request->getPathInfo(); // Anything after the index.php file
$baseUrl = substr($baseUrl, 1);
if (strpos($baseUrl, '.php') === false) {
$baseUrl .= 'index.php';
}
return [
'dir' => '',
'file' => $baseUrl, // Arbitrary path (with custom wrapper)
'access' => true
];
}
// LegacyRedirectHandler.php
public function getIncludeFile(Request $request): array {
$baseUrl = $request->getPathInfo(); // Anything after the index.php file
$baseUrl = substr($baseUrl, 1);
if (strpos($baseUrl, '.php') === false) {
$baseUrl .= 'index.php';
}
return [
'dir' => '',
'file' => $baseUrl, // Arbitrary path (with custom wrapper)
'access' => true
];
}
// LegacyRedirectHandler.php
public function getIncludeFile(Request $request): array {
$baseUrl = $request->getPathInfo(); // Anything after the index.php file
$baseUrl = substr($baseUrl, 1);
if (strpos($baseUrl, '.php') === false) {
$baseUrl .= 'index.php';
}
return [
'dir' => '',
'file' => $baseUrl, // Arbitrary path (with custom wrapper)
'access' => true
];
}
// LegacyRedirectHandler.php
public function getIncludeFile(Request $request): array {
$baseUrl = $request->getPathInfo(); // Anything after the index.php file
$baseUrl = substr($baseUrl, 1);
if (strpos($baseUrl, '.php') === false) {
$baseUrl .= 'index.php';
}
return [
'dir' => '',
'file' => $baseUrl, // Arbitrary path (with custom wrapper)
'access' => true
];
}
Evidence of exploitation
Create a product and upload a malicious image
Include the image from the previous step with the LFI to achieve RCE
Our security policy
We have reserved the ID CVE-2024-1644 to refer to this issue from now on.