Skip to content

Instantly share code, notes, and snippets.

@eddmann
Created April 9, 2014 12:18
  • Star 81 You must be signed in to star a gist
  • Fork 32 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save eddmann/10262795 to your computer and use it in GitHub Desktop.
Secure session handler implementation.
<?php
class SecureSessionHandler extends SessionHandler {
protected $key, $name, $cookie;
public function __construct($key, $name = 'MY_SESSION', $cookie = [])
{
$this->key = $key;
$this->name = $name;
$this->cookie = $cookie;
$this->cookie += [
'lifetime' => 0,
'path' => ini_get('session.cookie_path'),
'domain' => ini_get('session.cookie_domain'),
'secure' => isset($_SERVER['HTTPS']),
'httponly' => true
];
$this->setup();
}
private function setup()
{
ini_set('session.use_cookies', 1);
ini_set('session.use_only_cookies', 1);
session_name($this->name);
session_set_cookie_params(
$this->cookie['lifetime'],
$this->cookie['path'],
$this->cookie['domain'],
$this->cookie['secure'],
$this->cookie['httponly']
);
}
public function start()
{
if (session_id() === '') {
if (session_start()) {
return mt_rand(0, 4) === 0 ? $this->refresh() : true; // 1/5
}
}
return false;
}
public function forget()
{
if (session_id() === '') {
return false;
}
$_SESSION = [];
setcookie(
$this->name,
'',
time() - 42000,
$this->cookie['path'],
$this->cookie['domain'],
$this->cookie['secure'],
$this->cookie['httponly']
);
return session_destroy();
}
public function refresh()
{
return session_regenerate_id(true);
}
public function read($id)
{
return mcrypt_decrypt(MCRYPT_3DES, $this->key, parent::read($id), MCRYPT_MODE_ECB);
}
public function write($id, $data)
{
return parent::write($id, mcrypt_encrypt(MCRYPT_3DES, $this->key, $data, MCRYPT_MODE_ECB));
}
public function isExpired($ttl = 30)
{
$last = isset($_SESSION['_last_activity'])
? $_SESSION['_last_activity']
: false;
if ($last !== false && time() - $last > $ttl * 60) {
return true;
}
$_SESSION['_last_activity'] = time();
return false;
}
public function isFingerprint()
{
$hash = md5(
$_SERVER['HTTP_USER_AGENT'] .
(ip2long($_SERVER['REMOTE_ADDR']) & ip2long('255.255.0.0'))
);
if (isset($_SESSION['_fingerprint'])) {
return $_SESSION['_fingerprint'] === $hash;
}
$_SESSION['_fingerprint'] = $hash;
return true;
}
public function isValid()
{
return ! $this->isExpired() && $this->isFingerprint();
}
public function get($name)
{
$parsed = explode('.', $name);
$result = $_SESSION;
while ($parsed) {
$next = array_shift($parsed);
if (isset($result[$next])) {
$result = $result[$next];
} else {
return null;
}
}
return $result;
}
public function put($name, $value)
{
$parsed = explode('.', $name);
$session =& $_SESSION;
while (count($parsed) > 1) {
$next = array_shift($parsed);
if ( ! isset($session[$next]) || ! is_array($session[$next])) {
$session[$next] = [];
}
$session =& $session[$next];
}
$session[array_shift($parsed)] = $value;
}
}
$session = new SecureSessionHandler('cheese');
ini_set('session.save_handler', 'files');
session_set_save_handler($session, true);
session_save_path(__DIR__ . '/sessions');
$session->start();
if ( ! $session->isValid(5)) {
$session->destroy();
}
$session->put('hello.world', 'bonjour');
echo $session->get('hello.world'); // bonjour
@kschroeder
Copy link

Enrico Zimuel also did something like this. https://github.com/ezimuel/PHP-Secure-Session/blob/master/SecureSession.php. There are some additional crypto pieces that he uses such as an IV and CBC to lock it down even more. If there is additional functionality that is needed you might be able to base it off of that.

@graste
Copy link

graste commented Apr 9, 2014

Nice example and good article of yours about the hows and whys. What I wonder after reading it is whether you considered ipv6 compatibility for your fingerprinting method.

@nyamsprod
Copy link

I'm curious since you are using PHP 5.4+ why did you not choose to use PHP native function session_status instead of session_id ?

@eddmann
Copy link
Author

eddmann commented Apr 11, 2014

Hey all, thanks for the feedback

I have yet to consider IPv6, if I'm correct they do not seem to use a mask instead a prefix length and ip2long will not handle the 128bit size, think maybe to check (using filter_var) and store the full IP in case of v6?

That is a very good point, no technical reason just an oversight on my part :)

@ramkibabu
Copy link

Hey,
i m middle level developer. i have implemented your session class in my project. its creating session files. but its not deleting once it expired. how to delete those unwanted file since it make server side to over load. How you manage those file ???

@kamov
Copy link

kamov commented Sep 29, 2014

hi
thanks for tutorial.
I would like to ask why you change session path directory? for secure reason? if yes, why?

thanks

@radubl
Copy link

radubl commented Sep 29, 2014

@eddmann : On line 172 I believe you want $session->forget().
@kamov : The directory should be as out of reach as possible, supposedly out of the application root directory, so only the server would be able to reach it.

@smartcatdev
Copy link

is there a purpose for storing the session as a file ? is there a scenario this could be handy ?

thank you

Copy link

ghost commented Mar 16, 2015

Does anyone have any insight as to how using this compares to using Enrico Zimuel's, linked to above?

@pete-webpagetech
Copy link

Thank you Eddmann for the awesome tutorial. I like the read though you have up on it. I didn't see it included so I hope you don't mind if I share the article...

Securing Sessions in PHP

http://eddmann.com/posts/securing-sessions-in-php/

I was playing with a solution for IPv6. I included a private parameter _use_ip that is set to true if you pass true in the constructor in case a need for just using the user agent for hash is needed.

I'm using inet_pton which return a 4 digit binary string if a valid IPv4 or IPv6 address is given, False if not a valid IP. This should work as a replacement for ip2long in the md5 hash code.

another solution is to use inet_ntop with inet_pton to return a human readable ip if valid.
I included my changed code and what the hashed looked like in testing in my environment.

Example One with Human Readable being hashed
    $remoteAddress=($this->_use_ip) ? $_SERVER['REMOTE_ADDR'] : FALSE ;
    $hash = md5(
        $_SERVER['HTTP_USER_AGENT'] .
        (inet_ntop(inet_pton($remoteAddress)) & inet_ntop(inet_pton('255.255.0.0')))
    );
  • User Agent:
    • Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:36.0) Gecko/20100101 Firefox/36.0
  • address:
    • 127.0.0.1
  • netmask:
    • 255.255.0.0
Example Two with binary string being hashed
    $remoteAddress=($this->_use_ip) ? $_SERVER['REMOTE_ADDR'] : FALSE ;
    $hash = md5(
        $_SERVER['HTTP_USER_AGENT'] .
        (inet_pton($remoteAddress) & inet_pton('255.255.0.0'))
    );
  • User Agent:
    • Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:36.0) Gecko/20100101 Firefox/36.0
  • address:
    • <- 4 digit binary(not displayable)
  • netmask:
    • ÿÿ
  • Hash:
    *f1252cce10b87aabad494116619ebe64

Please let me know if this is a viable solution or not.

Thanks

@pete-webpagetech
Copy link

@jigglywhatsit
Yes, You can use both SecureSessionHandler.php and SecureSession.php together. Use SecureSessionHandler for starting/forgetting sessions and manipulating session vars. Then extend it to SecureSession to use as your session save handler.

First)
in SecureSessionHandler.php

Remove any methods that are already in SecureSession.php. You will extend the SecureSession class to use as your session save handler, so make sure that everything that SecureSession is doing is not done here in SecureSessionHandler.php.

mainly... remove all parameters(already set in parent SecureSession.php), constructor, setup, read, and write methods from SecureSessionHandler. There are some other small changes that need to be made to the methods in SecureSessionHandler. For instance SecureSession.php handles revoking the cookie so you can remove that from the forget method.

Second)
in SecureSession.php

remove the line"new SecureSession();" from the end of the file.

If you want to change session path you could add...

    $sessionPath = sys_get_temp_dir();
    session_save_path($sessionPath);
    ini_set('session.gc_probability', 1);

to the end of the constructor so that you don't have to do that manually after you construct your new object.

Third)
extend SecureSessionHandler to SecureSession like so.

class SecureSessionHandler extends SecureSession {

You could also replace the put and get methods in SecureSessionHandler if you like. They work well but I opted to use the magic methods __set __get __isset __unset so that I could more easily set/get session vars.

Usage would then look like...

    $session = new SecureSessionHandler();
    $session->start();
    if ( ! $session->isValid()) {
        $session->forget();
    }
    $session->foo="Hello World";
    echo $session->foo;

or in a class...

private $session;
public function __construct() {
    $this->session=new SecureSessionHandler();
}

public function someMethod(){
    $this->session->start();
    if ( !$this->session->isValid()) {
        $this->session->forget();
    }
    $this->session->foo="Hello World";
    echo $this->session->foo;
}

@AOsborn
Copy link

AOsborn commented May 1, 2015

Hello,
I'm trying to use this library but am running into issues. The problem seems to be that it doesn't re-use any sessions. Everytime I refresh the page I get another file and no session variables carry over.

This doesn't work (no persistent sessions and files keep getting regened):

  include('SecureSessionHandler.php');
  $session = new SecureSessionHandler('DA2FC336BD9E3');
  ini_set('session.use_cookies', 1);
  ini_set('session.save_handler', 'files');
  session_set_save_handler($session, true);
  session_save_path(__DIR__ . '/sessions');
  $session->start();
  echo 'Secret 1: '.$session->get('secret1').'<p>';
  $session->put('secret1', 'ExtraSecret');
  echo 'Secret 2: '.$session->get('secret1');

This works fine (single file and persistent sessions, although no encryption):

  ini_set('session.save_handler', 'files');
  session_save_path(__DIR__ . '/sessions');
  session_start();
  echo 'Secret 1: '.$_SESSION['secret2'].'<p>';
  $_SESSION['secret2'] = 'SuperSecret';
  echo 'Secret 2: '.$_SESSION['secret2'];

SecureSessionHandler.php is your code above (up to line 162).

@dertin
Copy link

dertin commented Mar 9, 2016

Prevent the session is started before using the method put or get:

public function get($name)
{
        // prevent the session is started
        if (session_id() === '') {$this->start();}

        $parsed = explode('.', $name);
        $result = $_SESSION;
        while ($parsed) {
            $next = array_shift($parsed);
            if (isset($result[$next])) {
                $result = $result[$next];
            } else {
                return null;
            }
        }
        return $result;
}
public function put($name, $value)
{
        // prevent the session is started
        if (session_id() === '') {$this->start();}

        $parsed = explode('.', $name);
        $session =& $_SESSION;
        while (count($parsed) > 1) {
            $next = array_shift($parsed);
            if ( ! isset($session[$next]) || ! is_array($session[$next])) {
                $session[$next] = [];
            }
            $session =& $session[$next];
        }
        $session[array_shift($parsed)] = $value;
}

@mpyw
Copy link

mpyw commented Aug 2, 2016

mcrypt_* will be abondoned soon so that such functions should be completely replaced with openssl_*.

@HMAZonderland
Copy link

Why isn't there a php-package for this?

@slangdog
Copy link

Has anyone successfully used this class with PHP 7? Or could anyone point me in the direction of something else?

@simonlussi
Copy link

simonlussi commented Mar 5, 2017

@BrettMeyer
I also ran into trouble using PHP7. The reason is that the methods mcrypt_decrypt and mcrypt_encrypt are depreciated since PHP7.1.0.
I changed the code a little bit to use the methods openssl_encrypt and openssl_decrypt instead.

The following 4 changes are required:

  1. line 9, change $this->key = $key; with:
    $this->key = substr(hash('sha256', $key), 0, 32);

  2. line 79, change return mcrypt_decrypt(MCRYPT_3DES, $this->key, parent::read($id), MCRYPT_MODE_ECB); with
    return (string)openssl_decrypt (parent::read($id) , "aes-256-cbc", $this->key);

  3. line 84, change return parent::write($id, mcrypt_encrypt(MCRYPT_3DES, $this->key, $data, MCRYPT_MODE_ECB)); with
    return parent::write($id, openssl_encrypt($data, "aes-256-cbc", $this->key));

This should fix it for PHP7.1 and up

@Mickroz
Copy link

Mickroz commented Mar 28, 2017

should be 'aes-256-ecb' or you get a warning about not using a IV.

@mauendara
Copy link

Did anyone using this had any trouble with this:

return mt_rand(0, 4) === 0 ? $this->refresh() : true; // 1/5

What is the purpose of refreshing the session one out of five times?

@tunsnel
Copy link

tunsnel commented Sep 11, 2018

Can someone please help me, I am trying to write php handler but I want to add it to html file.

My php handler is:

Please how can I use it in html file

@tunsnel
Copy link

tunsnel commented Sep 11, 2018

Can someone please help me, I am trying to write php handler but I want to add it to html file.

My php handler is:
`<?php echo session_start();
$email=$_SESSION['User'];
$ip = $_SERVER['REMOTE_ADDR'];
$Hash = hash(md5, $_SESSION['User']);
$logo = file_get_contents("https://");
$bg = file_get_contents("https://");

?> `
Please how can I use it in html file

@dertin
Copy link

dertin commented Sep 19, 2018

I have problems with:
session_regenerate_id(true);
https://gist.github.com/eddmann/10262795#file-securesessionhandler-php-L74

I lose the information of the current session and the files of the previous sessions are not deleted from my temporary directory.

Copy link

ghost commented May 5, 2019

Do not use mcrypt_* or openssl_*. Mcrypt is insecure, and OpenSSL is hard to get right, only cryptography experts should use it.

Everyone should be using Libsodium.

DO NOT USE THIS CODE. IT IS INSECURE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment