Skip to content

Commit 822fb85

Browse files
Fix RCE vulnerability in cookie session driver in laravel/framework
1 parent 6bb91df commit 822fb85

File tree

3 files changed

+234
-0
lines changed

3 files changed

+234
-0
lines changed

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@
113113
"Illuminate\\Auth\\Middleware\\": "overrides/laravel/framework/src/Illuminate/Auth/Middleware/",
114114
"Illuminate\\Container\\": "overrides/laravel/framework/src/Illuminate/Container/",
115115
"Illuminate\\Filesystem\\": "overrides/laravel/framework/src/Illuminate/Filesystem/",
116+
"Illuminate\\Cookie\\": "overrides/laravel/framework/src/Illuminate/Cookie/",
117+
"Illuminate\\Cookie\\Middleware\\": "overrides/laravel/framework/src/Illuminate/Cookie/Middleware/",
116118
"Lord\\Laroute\\Routes\\": "overrides/lord/laroute/src/Routes/",
117119
"TorMorten\\Eventy\\": "overrides/tormjens/eventy/src/",
118120
"Symfony\\Component\\Debug\\": "overrides/symfony/debug/",
@@ -193,6 +195,7 @@
193195
"vendor/laravel/framework/src/Illuminate/Container/Container.php",
194196
"vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php",
195197
"vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php",
198+
"vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php",
196199
"vendor/rachidlaasri/laravel-installer/src/Providers/LaravelInstallerServiceProvider.php",
197200
"vendor/lord/laroute/src/Routes/Collection.php",
198201
"vendor/tormjens/eventy/src/Filter.php",
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Illuminate\Cookie;
4+
5+
use Illuminate\Support\Str;
6+
7+
class CookieValuePrefix
8+
{
9+
/**
10+
* Create a new cookie value prefix for the given cookie name.
11+
*
12+
* @param string $cookieName
13+
* @param string $key
14+
* @return string
15+
*/
16+
public static function create($cookieName, $key)
17+
{
18+
return hash_hmac('sha1', $cookieName.'v2', $key).'|';
19+
}
20+
21+
/**
22+
* Remove the cookie value prefix.
23+
*
24+
* @param string $cookieValue
25+
* @return string
26+
*/
27+
public static function remove($cookieValue)
28+
{
29+
return substr($cookieValue, 41);
30+
}
31+
32+
/**
33+
* Verify the provided cookie's value.
34+
*
35+
* @param string $name
36+
* @param string $value
37+
* @param string $key
38+
* @return string|null
39+
*/
40+
public static function getVerifiedValue($name, $value, $key)
41+
{
42+
$verifiedValue = null;
43+
44+
if (Str::startsWith($value, static::create($name, $key))) {
45+
$verifiedValue = static::remove($value);
46+
}
47+
48+
return $verifiedValue;
49+
}
50+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<?php
2+
3+
namespace Illuminate\Cookie\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Support\Facades\Session;
7+
use Illuminate\Cookie\CookieValuePrefix;
8+
use Symfony\Component\HttpFoundation\Cookie;
9+
use Symfony\Component\HttpFoundation\Request;
10+
use Symfony\Component\HttpFoundation\Response;
11+
use Illuminate\Contracts\Encryption\DecryptException;
12+
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
13+
14+
class EncryptCookies
15+
{
16+
/**
17+
* The encrypter instance.
18+
*
19+
* @var \Illuminate\Contracts\Encryption\Encrypter
20+
*/
21+
protected $encrypter;
22+
23+
/**
24+
* The names of the cookies that should not be encrypted.
25+
*
26+
* @var array
27+
*/
28+
protected $except = [];
29+
30+
/**
31+
* Create a new CookieGuard instance.
32+
*
33+
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
34+
* @return void
35+
*/
36+
public function __construct(EncrypterContract $encrypter)
37+
{
38+
$this->encrypter = $encrypter;
39+
}
40+
41+
/**
42+
* Disable encryption for the given cookie name(s).
43+
*
44+
* @param string|array $cookieName
45+
* @return void
46+
*/
47+
public function disableFor($cookieName)
48+
{
49+
$this->except = array_merge($this->except, (array) $cookieName);
50+
}
51+
52+
/**
53+
* Handle an incoming request.
54+
*
55+
* @param \Illuminate\Http\Request $request
56+
* @param \Closure $next
57+
* @return mixed
58+
*/
59+
public function handle($request, Closure $next)
60+
{
61+
return $this->encrypt($next($this->decrypt($request)));
62+
}
63+
64+
/**
65+
* Decrypt the cookies on the request.
66+
*
67+
* @param \Symfony\Component\HttpFoundation\Request $request
68+
* @return \Symfony\Component\HttpFoundation\Request
69+
*/
70+
protected function decrypt(Request $request)
71+
{
72+
foreach ($request->cookies as $key => $cookie) {
73+
if ($this->isDisabled($key)) {
74+
continue;
75+
}
76+
77+
try {
78+
//$request->cookies->set($key, $this->decryptCookie($c));
79+
$decryptedValue = $this->decryptCookie($cookie);
80+
81+
$value = CookieValuePrefix::getVerifiedValue($key, $decryptedValue, $this->encrypter->getKey());
82+
83+
if (empty($value) && $key === config('session.cookie') && Session::isValidId($decryptedValue)) {
84+
$value = $decryptedValue;
85+
}
86+
87+
$request->cookies->set($key, $value);
88+
} catch (DecryptException $e) {
89+
$request->cookies->set($key, null);
90+
}
91+
}
92+
93+
return $request;
94+
}
95+
96+
/**
97+
* Decrypt the given cookie and return the value.
98+
*
99+
* @param string|array $cookie
100+
* @return string|array
101+
*/
102+
protected function decryptCookie($cookie)
103+
{
104+
return is_array($cookie)
105+
? $this->decryptArray($cookie)
106+
: $this->encrypter->decrypt($cookie);
107+
}
108+
109+
/**
110+
* Decrypt an array based cookie.
111+
*
112+
* @param array $cookie
113+
* @return array
114+
*/
115+
protected function decryptArray(array $cookie)
116+
{
117+
$decrypted = [];
118+
119+
foreach ($cookie as $key => $value) {
120+
if (is_string($value)) {
121+
$decrypted[$key] = $this->encrypter->decrypt($value);
122+
}
123+
}
124+
125+
return $decrypted;
126+
}
127+
128+
/**
129+
* Encrypt the cookies on an outgoing response.
130+
*
131+
* @param \Symfony\Component\HttpFoundation\Response $response
132+
* @return \Symfony\Component\HttpFoundation\Response
133+
*/
134+
protected function encrypt(Response $response)
135+
{
136+
foreach ($response->headers->getCookies() as $cookie) {
137+
if ($this->isDisabled($cookie->getName())) {
138+
continue;
139+
}
140+
141+
$prefix = '';
142+
143+
if ($cookie->getName() !== 'XSRF-TOKEN') {
144+
$prefix = CookieValuePrefix::create($cookie->getName(), $this->encrypter->getKey());
145+
}
146+
147+
$response->headers->setCookie($this->duplicate(
148+
$cookie, $this->encrypter->encrypt($prefix.$cookie->getValue())
149+
));
150+
}
151+
152+
return $response;
153+
}
154+
155+
/**
156+
* Duplicate a cookie with a new value.
157+
*
158+
* @param \Symfony\Component\HttpFoundation\Cookie $c
159+
* @param mixed $value
160+
* @return \Symfony\Component\HttpFoundation\Cookie
161+
*/
162+
protected function duplicate(Cookie $c, $value)
163+
{
164+
return new Cookie(
165+
$c->getName(), $value, $c->getExpiresTime(), $c->getPath(),
166+
$c->getDomain(), $c->isSecure(), $c->isHttpOnly(), $c->isRaw(),
167+
$c->getSameSite()
168+
);
169+
}
170+
171+
/**
172+
* Determine whether encryption has been disabled for the given cookie.
173+
*
174+
* @param string $name
175+
* @return bool
176+
*/
177+
public function isDisabled($name)
178+
{
179+
return in_array($name, $this->except);
180+
}
181+
}

0 commit comments

Comments
 (0)