Skip to content

Commit

Permalink
Fix RCE vulnerability in cookie session driver in laravel/framework
Browse files Browse the repository at this point in the history
  • Loading branch information
freescout-help-desk committed Sep 22, 2023
1 parent 6bb91df commit 822fb85
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 0 deletions.
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@
"Illuminate\\Auth\\Middleware\\": "overrides/laravel/framework/src/Illuminate/Auth/Middleware/",
"Illuminate\\Container\\": "overrides/laravel/framework/src/Illuminate/Container/",
"Illuminate\\Filesystem\\": "overrides/laravel/framework/src/Illuminate/Filesystem/",
"Illuminate\\Cookie\\": "overrides/laravel/framework/src/Illuminate/Cookie/",
"Illuminate\\Cookie\\Middleware\\": "overrides/laravel/framework/src/Illuminate/Cookie/Middleware/",
"Lord\\Laroute\\Routes\\": "overrides/lord/laroute/src/Routes/",
"TorMorten\\Eventy\\": "overrides/tormjens/eventy/src/",
"Symfony\\Component\\Debug\\": "overrides/symfony/debug/",
Expand Down Expand Up @@ -193,6 +195,7 @@
"vendor/laravel/framework/src/Illuminate/Container/Container.php",
"vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php",
"vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php",
"vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php",
"vendor/rachidlaasri/laravel-installer/src/Providers/LaravelInstallerServiceProvider.php",
"vendor/lord/laroute/src/Routes/Collection.php",
"vendor/tormjens/eventy/src/Filter.php",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Illuminate\Cookie;

use Illuminate\Support\Str;

class CookieValuePrefix
{
/**
* Create a new cookie value prefix for the given cookie name.
*
* @param string $cookieName
* @param string $key
* @return string
*/
public static function create($cookieName, $key)
{
return hash_hmac('sha1', $cookieName.'v2', $key).'|';
}

/**
* Remove the cookie value prefix.
*
* @param string $cookieValue
* @return string
*/
public static function remove($cookieValue)
{
return substr($cookieValue, 41);
}

/**
* Verify the provided cookie's value.
*
* @param string $name
* @param string $value
* @param string $key
* @return string|null
*/
public static function getVerifiedValue($name, $value, $key)
{
$verifiedValue = null;

if (Str::startsWith($value, static::create($name, $key))) {
$verifiedValue = static::remove($value);
}

return $verifiedValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<?php

namespace Illuminate\Cookie\Middleware;

use Closure;
use Illuminate\Support\Facades\Session;
use Illuminate\Cookie\CookieValuePrefix;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;

class EncryptCookies
{
/**
* The encrypter instance.
*
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
protected $encrypter;

/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [];

/**
* Create a new CookieGuard instance.
*
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @return void
*/
public function __construct(EncrypterContract $encrypter)
{
$this->encrypter = $encrypter;
}

/**
* Disable encryption for the given cookie name(s).
*
* @param string|array $cookieName
* @return void
*/
public function disableFor($cookieName)
{
$this->except = array_merge($this->except, (array) $cookieName);
}

/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
return $this->encrypt($next($this->decrypt($request)));
}

/**
* Decrypt the cookies on the request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @return \Symfony\Component\HttpFoundation\Request
*/
protected function decrypt(Request $request)
{
foreach ($request->cookies as $key => $cookie) {
if ($this->isDisabled($key)) {
continue;
}

try {
//$request->cookies->set($key, $this->decryptCookie($c));
$decryptedValue = $this->decryptCookie($cookie);

$value = CookieValuePrefix::getVerifiedValue($key, $decryptedValue, $this->encrypter->getKey());

if (empty($value) && $key === config('session.cookie') && Session::isValidId($decryptedValue)) {
$value = $decryptedValue;
}

$request->cookies->set($key, $value);
} catch (DecryptException $e) {
$request->cookies->set($key, null);
}
}

return $request;
}

/**
* Decrypt the given cookie and return the value.
*
* @param string|array $cookie
* @return string|array
*/
protected function decryptCookie($cookie)
{
return is_array($cookie)
? $this->decryptArray($cookie)
: $this->encrypter->decrypt($cookie);
}

/**
* Decrypt an array based cookie.
*
* @param array $cookie
* @return array
*/
protected function decryptArray(array $cookie)
{
$decrypted = [];

foreach ($cookie as $key => $value) {
if (is_string($value)) {
$decrypted[$key] = $this->encrypter->decrypt($value);
}
}

return $decrypted;
}

/**
* Encrypt the cookies on an outgoing response.
*
* @param \Symfony\Component\HttpFoundation\Response $response
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function encrypt(Response $response)
{
foreach ($response->headers->getCookies() as $cookie) {
if ($this->isDisabled($cookie->getName())) {
continue;
}

$prefix = '';

if ($cookie->getName() !== 'XSRF-TOKEN') {
$prefix = CookieValuePrefix::create($cookie->getName(), $this->encrypter->getKey());
}

$response->headers->setCookie($this->duplicate(
$cookie, $this->encrypter->encrypt($prefix.$cookie->getValue())
));
}

return $response;
}

/**
* Duplicate a cookie with a new value.
*
* @param \Symfony\Component\HttpFoundation\Cookie $c
* @param mixed $value
* @return \Symfony\Component\HttpFoundation\Cookie
*/
protected function duplicate(Cookie $c, $value)
{
return new Cookie(
$c->getName(), $value, $c->getExpiresTime(), $c->getPath(),
$c->getDomain(), $c->isSecure(), $c->isHttpOnly(), $c->isRaw(),
$c->getSameSite()
);
}

/**
* Determine whether encryption has been disabled for the given cookie.
*
* @param string $name
* @return bool
*/
public function isDisabled($name)
{
return in_array($name, $this->except);
}
}

0 comments on commit 822fb85

Please sign in to comment.