Skip to content

Instantly share code, notes, and snippets.

@glennkarlsen
Created October 13, 2015 07:35
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save glennkarlsen/dad99cd32a3104477341 to your computer and use it in GitHub Desktop.
Save glennkarlsen/dad99cd32a3104477341 to your computer and use it in GitHub Desktop.
Decrypt laravel_session outside of the Laravel app. This is just a proof of concept, cleanup and extend to your needs.
<?php
function decrypt_laravel_session() {
# Method 1. Autload Laravel into the application.
require '/home/vagrant/Code/minside-fjordclub/webroot/bootstrap/autoload.php';
$app = require_once '/home/vagrant/Code/minside-fjordclub/webroot/bootstrap/app.php';
$app->make('Illuminate\Contracts\Http\Kernel')->handle(Illuminate\Http\Request::capture());
$id = (isset($_COOKIE[$app['config']['session.cookie']]) ? $app['encrypter']->decrypt($_COOKIE[$app['config']['session.cookie']]) : null);
if ($id)
{
$app['session']->driver()->setId($id);
$app['session']->driver()->start();
$user_id = $app['auth']->driver()->getSession()->get('id');
}
# Method 2. Replicate the Laravel Decrypt code.
include('includes/laravel/LaravelCookieDecrypter.php');
$laravel_cookie_decrypter = new LaravelCookieDecrypter();
$session_id = $laravel_cookie_decrypter->decrypt($_COOKIE['laravel_session']); // Returns the session_id
// Require the session from the database/filesystem/cache etc with the session_id
}
<?php
class LaravelCookieDecrypter {
/**
* The encryption key.
*
* @var string
*/
protected $key = 'YOUR LARAVEL .ENV APP KEY';
/**
* The algorithm used for encryption.
*
* @var string
*/
protected $cipher = MCRYPT_RIJNDAEL_128;
/**
* The mode used for encryption.
*
* @var string
*/
protected $mode = MCRYPT_MODE_CBC;
/**
* The block size of the cipher.
*
* @var int
*/
protected $block = 16;
protected $stringUtils;
/**
* LaravelCookieDecrypter constructor.
*/
public function __construct()
{
require_once('SecureRandom.php');
require_once('StringUtils.php');
}
public function decrypt($payload)
{
$payload = $this->getJsonPayload($payload);
// We'll go ahead and remove the PKCS7 padding from the encrypted value before
// we decrypt it. Once we have the de-padded value, we will grab the vector
// and decrypt the data, passing back the unserialized from of the value.
$value = base64_decode($payload['value']);
$iv = base64_decode($payload['iv']);
return unserialize($this->stripPadding($this->mcryptDecrypt($value, $iv)));
}
protected function getJsonPayload($payload)
{
$payload = json_decode(base64_decode($payload), true);
// If the payload is not valid JSON or does not have the proper keys set we will
// assume it is invalid and bail out of the routine since we will not be able
// to decrypt the given value. We'll also check the MAC for this encryption.
if ( ! $payload || $this->invalidPayload($payload))
{
var_dump('Invalid data.');
}
if ( ! $this->validMac($payload))
{
var_dump('MAC is invalid.');
}
return $payload;
}
protected function validMac(array $payload)
{
$bytes = (new SecureRandom)->nextBytes(16);
$calcMac = hash_hmac('sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true);
return StringUtils::equals(hash_hmac('sha256', $payload['mac'], $bytes, true), $calcMac);
}
protected function stripPadding($value)
{
$pad = ord($value[($len = strlen($value)) - 1]);
return $this->paddingIsValid($pad, $value) ? substr($value, 0, $len - $pad) : $value;
}
protected function mcryptDecrypt($value, $iv)
{
try
{
return mcrypt_decrypt($this->cipher, $this->key, $value, $this->mode, $iv);
}
catch (Exception $e)
{
var_dump($e->getMessage());
die;
}
}
protected function invalidPayload($data)
{
return ! is_array($data) || ! isset($data['iv']) || ! isset($data['value']) || ! isset($data['mac']);
}
protected function paddingIsValid($pad, $value)
{
$beforePad = strlen($value) - $pad;
return substr($value, $beforePad) == str_repeat(substr($value, -1), $pad);
}
protected function hash($iv, $value)
{
return hash_hmac('sha256', $iv.$value, $this->key);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A secure random number generator implementation.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class SecureRandom
{
private $useOpenSsl;
private $seed;
private $seedUpdated;
private $seedLastUpdatedAt;
private $seedFile;
/**
* Constructor.
*
* Be aware that a guessable seed will severely compromise the PRNG
* algorithm that is employed.
*
* @param string $seedFile
*/
public function __construct($seedFile = null)
{
$this->seedFile = $seedFile;
// determine whether to use OpenSSL
if ('/' === DIRECTORY_SEPARATOR && PHP_VERSION_ID < 50304) {
$this->useOpenSsl = false;
} elseif (!function_exists('openssl_random_pseudo_bytes')) {
$this->useOpenSsl = false;
} else {
$this->useOpenSsl = true;
}
}
/**
* {@inheritdoc}
*/
public function nextBytes($nbBytes)
{
// try OpenSSL
if ($this->useOpenSsl) {
$bytes = openssl_random_pseudo_bytes($nbBytes, $strong);
if (false !== $bytes && true === $strong) {
return $bytes;
}
}
// initialize seed
if (null === $this->seed) {
if (null === $this->seedFile) {
throw new \RuntimeException('You need to specify a file path to store the seed.');
}
if (is_file($this->seedFile)) {
list($this->seed, $this->seedLastUpdatedAt) = $this->readSeed();
} else {
$this->seed = uniqid(mt_rand(), true);
$this->updateSeed();
}
}
$bytes = '';
while (strlen($bytes) < $nbBytes) {
static $incr = 1;
$bytes .= hash('sha512', $incr++.$this->seed.uniqid(mt_rand(), true).$nbBytes, true);
$this->seed = base64_encode(hash('sha512', $this->seed.$bytes.$nbBytes, true));
$this->updateSeed();
}
return substr($bytes, 0, $nbBytes);
}
private function readSeed()
{
return json_decode(file_get_contents($this->seedFile));
}
private function updateSeed()
{
if (!$this->seedUpdated && $this->seedLastUpdatedAt < time() - mt_rand(1, 10)) {
file_put_contents($this->seedFile, json_encode(array($this->seed, microtime(true))));
}
$this->seedUpdated = true;
}
}
<?php
class StringUtils
{
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* Compares two strings.
*
* This method implements a constant-time algorithm to compare strings.
* Regardless of the used implementation, it will leak length information.
*
* @param string $knownString The string of known length to compare against
* @param string $userInput The string that the user can control
*
* @return bool true if the two strings are the same, false otherwise
*/
public static function equals($knownString, $userInput)
{
// Avoid making unnecessary duplications of secret data
if (!is_string($knownString)) {
$knownString = (string) $knownString;
}
if (!is_string($userInput)) {
$userInput = (string) $userInput;
}
if (function_exists('hash_equals')) {
return hash_equals($knownString, $userInput);
}
$knownLen = self::safeStrlen($knownString);
$userLen = self::safeStrlen($userInput);
if ($userLen !== $knownLen) {
return false;
}
$result = 0;
for ($i = 0; $i < $knownLen; ++$i) {
$result |= (ord($knownString[$i]) ^ ord($userInput[$i]));
}
// They are only identical strings if $result is exactly 0...
return 0 === $result;
}
/**
* Returns the number of bytes in a string.
*
* @param string $string The string whose length we wish to obtain
*
* @return int
*/
public static function safeStrlen($string)
{
// Premature optimization
// Since this cannot be changed at runtime, we can cache it
static $funcExists = null;
if (null === $funcExists) {
$funcExists = function_exists('mb_strlen');
}
if ($funcExists) {
return mb_strlen($string, '8bit');
}
return strlen($string);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment