Created
February 29, 2012 20:52
-
-
Save katanacrimson/1944275 to your computer and use it in GitHub Desktop.
Session hijack detection with IP "chunk" validation...
This file contains hidden or 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
<?php | |
// licensed: MIT license | |
// very OOP'd in-lib, just a test case | |
echo fingerprint('1.2.3.4', 6, 3) . ' - ' . fingerprint('1.2.3.3', 6, 3) . "<br>\n"; | |
echo fingerprint('1:2:3:4:5:6:7:8', 6, 3) . ' - ' . fingerprint('1:2:3:4:5:6::8', 6, 3) . "<br>\n"; | |
echo fingerprint('1:2:3:4:5:6::8', 6, 3) . ' - ' . fingerprint('1:2:3:4:5::8', 6, 3) . "<br>\n"; | |
function expandIPv6(array $ip_chunks) | |
{ | |
if($ip_chunks === array('', '', '')) | |
{ | |
return array_fill(0, 8, '0'); // basically 0:0:0:0:0:0:0:0 | |
} | |
else | |
{ | |
$chunk_count = count($ip_chunks); | |
for($i = 0; $i < 8; $i++) | |
{ | |
if($ip_chunks[$i] == '') | |
{ | |
if($i == 0) // at the start - ::ip | |
{ | |
return array_merge(array_fill(0, 2, 0), array_slice($ip_chunks, 2)); | |
} | |
elseif($i == 7) // at the end - ip:: | |
{ | |
return array_merge(array_slice($ip_chunks, 2), array_fill(0, 2, '0')); | |
} | |
else // in-between, ip::ip | |
{ | |
// do the splits, drop the empty chunk in the middle, and use DARK MAGICK. | |
// @note: (8 - ($chunk_count - 1)) is the number of IP chunks that the :: is covering for. | |
array_splice($ip_chunks, $i, 1, array_fill($i, (8 - ($chunk_count - 1)), '0')); | |
return $ip_chunks; | |
} | |
} | |
} | |
} | |
} | |
function fingerprint($ip, $ipv6_val, $ipv4_val) | |
{ | |
$is_ipv6 = (strpos($ip, ':') !== false) ? true : false; | |
if($is_ipv6) | |
{ | |
$validation_level = $ipv6_val; | |
$expand_ipv6 = (strpos($ip, '::') !== false) ? true : false; | |
} | |
else | |
{ | |
$validation_level = $ipv4_val; | |
} | |
$chunks = explode((($is_ipv6) ? ':' : '.'), $ip); | |
if($is_ipv6 && $expand_ipv6) | |
{ | |
$chunks = expandIPv6($chunks); | |
} | |
return hash('sha256', 'omgwtfbbq 1.0 random ass browser' /*useragent*/ . implode('', array_slice($chunks, 0, $validation_level)) . 'abcdef'/*random seed*/); | |
} |
This file contains hidden or 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
<?php | |
namespace codebite\tsundere\Session; | |
use \R; | |
use \sigmabb\sigmabb\WebKernel as Sigma; | |
// licensed: MIT license | |
class Session | |
{ | |
private $app, $_sid_entry, $sid, $session, $ipv4_validation_level, $ipv6_validation_level; | |
public function __construct() | |
{ | |
$this->app = Sigma::getInstance(); | |
$this->app->cookie->setCookiePrefix('tsun'); | |
$this->setIdentifier('COOKIE::' . $this->app->cookie->getCookiePrefix() . 'sid'); | |
$this->setIPv4ValidationLevel(4); | |
$this->setIPv6ValidationLevel(6); | |
} | |
private function sid() | |
{ | |
if(empty($this->sid)) | |
{ | |
$this->sid = $this->app->input->getInput($this->_sid_entry, ''); | |
} | |
return $this->sid; | |
} | |
public function setIPv4ValidationLevel($level) | |
{ | |
// maximum validation level is 4, because IPv4 has 4 chunks total. | |
if($level < 1) | |
{ | |
$level = 1; | |
} | |
elseif($level > 4) | |
{ | |
$level = 4; | |
} | |
$this->ipv4_validation_level = (int) $level; | |
} | |
public function setIPv6ValidationLevel($level) | |
{ | |
// maximum validation level is 8, because IPv6 has 8 chunks total. | |
if($level < 1) | |
{ | |
$level = 1; | |
} | |
elseif($level > 8) | |
{ | |
$level = 8; | |
} | |
$this->ipv6_validation_level = (int) $level; | |
} | |
public function setIdentifier($sid) | |
{ | |
$this->_sid_entry = $sid; | |
$this->sid = NULL; | |
} | |
public function hasSession() | |
{ | |
return $this->sid()->getWasSet(); | |
} | |
public function loadSession() | |
{ | |
if($this->hasSession()) | |
{ | |
$this->session = R::findOne('tsunsession', 'sid = ? ORDER BY last LIMIT 1', array($this->sid())); | |
} | |
// validate session... | |
if(!empty($this->session)) | |
{ | |
// check fingerprints.. | |
if($this->session->fingerprint != $this->getFingerprint()) | |
{ | |
$this->session = NULL; | |
} | |
} | |
if(empty($this->session)) | |
{ | |
$seed = $this->app->seeder->buildRandomString(14); | |
$this->session = R::dispense('tsunsession'); | |
$this->session->seed = $seed; | |
$this->session->sid = $this->app->seeder->buildRandomString(32, $seed); | |
$this->session->fingerprint = $this->getFingerprint(); | |
$this->session->useragent = $this->app->request->getUseragent(); | |
$this->session->ip = $this->app->request->getIP(); | |
R::store($this->session); | |
$this->app->cookie->setCookie('sid') | |
->setCookieValue($this->session->sid); | |
} | |
} | |
public function getSID() | |
{ | |
if(empty($this->session)) | |
{ | |
$this->loadSession(); | |
} | |
return $this->session->sid; | |
} | |
public function getSessionSeed() | |
{ | |
if(empty($this->session)) | |
{ | |
$this->loadSession(); | |
} | |
return $this->session->seed; | |
} | |
private function getFingerprint() | |
{ | |
$ip = $this->app->request->getIP(); | |
$is_ipv6 = (strpos($ip, ':') !== false) ? true : false; | |
if($is_ipv6) | |
{ | |
$validation_level = $this->ipv6_validation_level; | |
$expand_ipv6 = (strpos($ip, '::') !== false) ? true : false; | |
} | |
else | |
{ | |
$validation_level = $this->ipv4_validation_level; | |
} | |
$chunks = explode((($is_ipv6) ? ':' : '.'), $ip); | |
if($is_ipv6 && $expand_ipv6) | |
{ | |
$chunks = $this->expandIPv6($chunks); | |
} | |
return hash('sha256', $this->app->request->getUseragent() . implode('', array_slice($chunks, 0, $validation_level)) . $this->app['app.seed']); | |
} | |
private function expandIPv6($ip_chunks) | |
{ | |
if($ip_chunks === array('', '', '')) | |
{ | |
return array_fill(0, 8, '0'); // basically 0:0:0:0:0:0:0:0 | |
} | |
else | |
{ | |
$chunk_count = count($ip_chunks); | |
for($i = 0; $i < 8; $i++) | |
{ | |
if($ip_chunks[$i] == '') | |
{ | |
if($i == 0) // at the start - ::ip | |
{ | |
return array_merge(array_fill(0, 2, 0), array_slice($ip_chunks, 2)); | |
} | |
elseif($i == 7) // at the end - ip:: | |
{ | |
return array_merge(array_slice($ip_chunks, 2), array_fill(0, 2, '0')); | |
} | |
else // in-between, ip::ip | |
{ | |
// do the splits, drop the empty chunk in the middle, and use DARK MAGICK. | |
// @note: (8 - ($chunk_count - 1)) is the number of IP chunks that the :: is covering for. | |
array_splice($ip_chunks, $i, 1, array_fill($i, (8 - ($chunk_count - 1)), '0')); | |
return $ip_chunks; | |
} | |
} | |
} | |
return $ip_chunks; // wtf bad data. | |
} | |
} | |
public function __destruct() | |
{ | |
R::store($this->session); | |
} | |
public function offsetExists($offset) | |
{ | |
return isset($this->session->data[$offset]) ? true : false; | |
} | |
public function offsetGet($offset) | |
{ | |
if(!isset($this->session->data[$offset])) | |
{ | |
return NULL; | |
} | |
return $this->session->data[$offset]; | |
} | |
public function offsetSet($offset, $value) | |
{ | |
$this->session->data[$offset] = $value; | |
} | |
public function offsetUnset($offset) | |
{ | |
unset($this->session->data[$offset]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment