-
Notifications
You must be signed in to change notification settings - Fork 510
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix RCE vulnerability in cookie session driver in laravel/framework
- Loading branch information
1 parent
6bb91df
commit 822fb85
Showing
3 changed files
with
234 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
overrides/laravel/framework/src/Illuminate/Cookie/CookieValuePrefix.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
181 changes: 181 additions & 0 deletions
181
overrides/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |