Skip to content

Commit f87ffec

Browse files
committed
Removing hash ids from model for security reasons. For explanation and replacement see https://paragonie.com/blog/2015/09/comprehensive-guide-url-parameter-encryption-in-php
1 parent 744feee commit f87ffec

File tree

3 files changed

+0
-213
lines changed

3 files changed

+0
-213
lines changed

system/Model.php

Lines changed: 0 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -435,142 +435,6 @@ public function first()
435435

436436
//--------------------------------------------------------------------
437437

438-
/**
439-
* Finds a single record by a "hashed" primary key. Used in conjunction
440-
* with $this->getIDHash().
441-
*
442-
* THIS IS NOT VALID TO USE FOR SECURITY!
443-
*
444-
* @param string $hashedID
445-
*
446-
* @return array|null|object
447-
*/
448-
public function findByHashedID(string $hashedID)
449-
{
450-
return $this->find($this->decodeID($hashedID));
451-
}
452-
453-
//--------------------------------------------------------------------
454-
455-
/**
456-
* Returns a "hashed id", which isn't really hashed, but that's
457-
* become a fairly common term for this. Essentially creates
458-
* an obfuscated id, intended to be used to disguise the
459-
* ID from incrementing IDs to get access to things they shouldn't.
460-
*
461-
* THIS IS NOT VALID TO USE FOR SECURITY!
462-
*
463-
* Note, at some point we might want to move to something more
464-
* complex. The hashid library is good, but only works on integers.
465-
*
466-
* @see http://hashids.org/php/
467-
* @see http://raymorgan.net/web-development/how-to-obfuscate-integer-ids/
468-
*
469-
* @param int|string $id
470-
*
471-
* @return string|false
472-
*/
473-
public function encodeID($id)
474-
{
475-
// Strings don't currently have a secure
476-
// method, so simple base64 encoding will work for now.
477-
if ( ! is_numeric($id))
478-
{
479-
return '=_' . base64_encode($id);
480-
}
481-
482-
$id = (int) $id;
483-
if ($id < 1)
484-
{
485-
return false;
486-
}
487-
if ($id > pow(2, 31))
488-
{
489-
return false;
490-
}
491-
492-
$segment1 = $this->getHash($id, 16);
493-
$segment2 = $this->getHash($segment1, 8);
494-
$dec = (int) base_convert($segment2, 16, 10);
495-
$dec = ($dec > $id) ? $dec - $id : $dec + $id;
496-
$segment2 = base_convert($dec, 10, 16);
497-
$segment2 = str_pad($segment2, 8, '0', STR_PAD_LEFT);
498-
$segment3 = $this->getHash($segment1 . $segment2, 8);
499-
$hex = $segment1 . $segment2 . $segment3;
500-
$bin = pack('H*', $hex);
501-
$oid = base64_encode($bin);
502-
$oid = str_replace(['+', '/', '='], ['$', ':', ''], $oid);
503-
504-
return $oid;
505-
}
506-
507-
//--------------------------------------------------------------------
508-
509-
/**
510-
* Decodes our hashed id.
511-
*
512-
* @see http://raymorgan.net/web-development/how-to-obfuscate-integer-ids/
513-
*
514-
* @param string $hash
515-
*
516-
* @return mixed
517-
*/
518-
public function decodeID($hash)
519-
{
520-
// Was it a simple string we encoded?
521-
if (substr($hash, 0, 2) == '=_')
522-
{
523-
$hash = substr($hash, 2);
524-
525-
return base64_decode($hash);
526-
}
527-
528-
if ( ! preg_match('/^[A-Z0-9\:\$]{21,23}$/i', $hash))
529-
{
530-
return 0;
531-
}
532-
$hash = str_replace(['$', ':'], ['+', '/'], $hash);
533-
$bin = base64_decode($hash);
534-
$hex = unpack('H*', $bin);
535-
$hex = $hex[1];
536-
if ( ! preg_match('/^[0-9a-f]{32}$/', $hex))
537-
{
538-
return 0;
539-
}
540-
$segment1 = substr($hex, 0, 16);
541-
$segment2 = substr($hex, 16, 8);
542-
$segment3 = substr($hex, 24, 8);
543-
$exp2 = $this->getHash($segment1, 8);
544-
$exp3 = $this->getHash($segment1 . $segment2, 8);
545-
if ($segment3 != $exp3)
546-
{
547-
return 0;
548-
}
549-
$v1 = (int) base_convert($segment2, 16, 10);
550-
$v2 = (int) base_convert($exp2, 16, 10);
551-
$id = abs($v1 - $v2);
552-
553-
return $id;
554-
}
555-
556-
//--------------------------------------------------------------------
557-
558-
/**
559-
* Used for our hashed IDs. Requires $salt to be defined
560-
* within the Config\App file.
561-
*
562-
* @param string $str
563-
* @param int $len
564-
*
565-
* @return string
566-
*/
567-
protected function getHash($str, $len)
568-
{
569-
return substr(hash('sha256', $str . $this->salt), 0, $len);
570-
}
571-
572-
//--------------------------------------------------------------------
573-
574438
/**
575439
* A convenience method that will attempt to determine whether the
576440
* data should be inserted or updated. Will work with either

tests/system/Database/Live/ModelTest.php

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,48 +30,6 @@ public function setUp()
3030

3131
//--------------------------------------------------------------------
3232

33-
public function testHashIDsWithNumber()
34-
{
35-
$expected = '123';
36-
37-
$str = $this->model->encodeID($expected);
38-
39-
$this->assertNotEquals($expected, $str);
40-
41-
$this->assertEquals($expected, $this->model->decodeID($str));
42-
}
43-
44-
//--------------------------------------------------------------------
45-
46-
public function testHashIDsWithString()
47-
{
48-
$expected = 'my test hash';
49-
50-
$str = $this->model->encodeID($expected);
51-
52-
$this->assertNotEquals($expected, $str);
53-
54-
$this->assertEquals($expected, $this->model->decodeID($str));
55-
}
56-
57-
//--------------------------------------------------------------------
58-
59-
public function testHashedIdsWithFind()
60-
{
61-
$hash = $this->model->encodeId(4);
62-
63-
$this->model->setTable('job')
64-
->withDeleted();
65-
66-
$user = $this->model->asObject()
67-
->findByHashedID($hash);
68-
69-
$this->assertNotEmpty($user);
70-
$this->assertEquals(4, $user->id);
71-
}
72-
73-
//--------------------------------------------------------------------
74-
7533
public function testFindReturnsRow()
7634
{
7735
$model = new JobModel($this->db);

user_guide_src/source/database/model.rst

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -541,41 +541,6 @@ This is best used during cronjobs, data exports, or other large tasks.
541541
// $data is a single row of data.
542542
});
543543

544-
Obfuscating IDs in URLs
545-
-----------------------
546-
547-
Instead of displaying the resource's ID in the URL (i.e. /users/123), the model provides a simple
548-
way to obfuscate the ID. This provides some protection against attackers simply incrementing IDs in the
549-
URL to do bad things to your data.
550-
551-
This is not a valid security use, but another simple layer of protection. Determined attackers could very easily
552-
determine the actual ID.
553-
554-
The data is not stored in the database at any time, it is simply used to disguise the ID. When creating a URL
555-
you can use the **encodeID()** method to get the hashed ID.
556-
::
557-
558-
// Creates something like: http://exmample.com/users/MTIz
559-
$url = '/users/'. $model->encodeID($user->id);
560-
561-
When you need to grab the item in your controller, you can use the **findByHashedID()** method instead of the
562-
**find()** method.
563-
::
564-
565-
public function show($hashedID)
566-
{
567-
$user = $this->model->findByHashedID($hashedID);
568-
}
569-
570-
If you ever need to decode the hash, you may do so with the **decodeID()** method.
571-
::
572-
573-
$hash = $model->encodeID(123);
574-
$check = $model->decodeID($hash);
575-
576-
.. note:: While the name is "hashed id", this is not actually a hashed variable, but that term has become
577-
common in many circles to represent the encoding of an ID into a short, unique, identifier.
578-
579544
Model Events
580545
============
581546

0 commit comments

Comments
 (0)