PHP Developers Network

A community of PHP developers offering assistance, advice, discussion, and friendship.
 
Loading
It is currently Sat Jul 22, 2017 3:45 am

All times are UTC - 5 hours




Post new topic Reply to topic  [ 11 posts ] 
Author Message
PostPosted: Sat Oct 08, 2011 5:53 pm 
Offline
Forum Regular
User avatar

Joined: Tue Sep 28, 2010 11:41 am
Posts: 983
Location: Columbus, Ohio
Ever go to a site, click on a link, and the URL contains something that looks like a Primary Key for a database, ex:
Syntax: [ Download ] [ Hide ]
http://www.domain.com/view-user.php?id=33

Ever been bored and tried to see what you get when you change that id number? Usually not such a big deal. But there are times, consider, what if that was the link you got was to view a users account when you are logged into site like a forum and it had contact info like their e-mail. Now image someone seeing this, and deciding to write a bot that loops through hitting id=1 id=2 id=3, etc, and captured all the user data... Be a nice way of getting a list of e-mails, and possibly info like last time they logged in, or signed up, letting them know how likely those e-mail addresses are still good valid ones.

Another example, I have a project now, where people will sign up for a service that is managed on our servers. They will include a line of javascript on their site that will do an ajax call to our site to load their information. That ajax call will include in it their ClientID. Well say someone else came across a site using my service, and thought they could do it better and/or charge less money. Well with a simple little script, if I was just passing it the ClientID value, they could just hit them all to find what all sites are using my service to try to get them as customers. Not good. (of course, I would write my program where nothing could be better..... [/ego] LOL)

So anyhow, these are some reasons where I like to leave any logic/programming behind a public viewable site/app as hard to guess as possible. One of the things I do in most of my apps is have two functions int2key() and key2int() which will take a positive integer value, and convert it over to a key that isn't very guessable, and then back from the key to the integer, so now where I would have had:
Syntax: [ Download ] [ Hide ]
<a href="/view-user.php?id=<?php echo $intUserID; ?>

I would now have:
Syntax: [ Download ] [ Hide ]
<a href="/view-user.php?id=<?php echo int2key($intUserID); ?>

Then to use it, I just use:
Syntax: [ Download ] [ Hide ]
$intUserID = (isset($_GET['id'])) ? key2int($_GET['id']) : 0;

And I will either end up with the correct user ID, or 0 (if it wasn't in the URL, or someone played with the key to something that didn't decode)

So now for the link example above, I would have something like this:
Syntax: [ Download ] [ Hide ]
http://www.domain.com/view-user.php?id=GZTM4ZDFPMDBkNg


Here is a sample set of functions that produced the above code, and like I said, I usually have something similar to this on most my apps. (I do things a little different for my own, I don't put exact hidden code public ;-)
Syntax: [ Download ] [ Hide ]
define ('KEY_CALC_1',47);
define ('KEY_CALC_2',91439);

function int2key($intNum) {
        $intRandomKey = rand(KEY_CALC_1,KEY_CALC_2);
        $intNum = $intNum * KEY_CALC_1 + KEY_CALC_2 + $intRandomKey;
        $intFlipper = ($intRandomKey%26)+65;
        $strKey = trim(base64_encode(strrev(dechex($intRandomKey).'O'.dechex($intNum))),'=');
        return chr($intFlipper).(($intFlipper % 2) ? $strKey : strrev($strKey));
}

function key2int($strKey) {
        $strFlipper = substr($strKey,0,1);
        if ($strFlipper < 'A' || $strFlipper > 'Z') { return 0; }
        $strKey = substr($strKey,1);
        if (ord($strFlipper)%2==0) { $strKey = strrev($strKey); }
        $strKey = strrev(base64_decode($strKey));
        if (!preg_match('/^([0-9a-z]+)O([0-9a-z]+)$/',$strKey,$parts)) { return 0; }
        $intNum = (hexdec($parts[2]) - hexdec($parts[1]) - KEY_CALC_2) / KEY_CALC_1;
        if ((int)$intNum != $intNum) { return 0; }
        return (int)$intNum;
}


These are usually in a main file of functions that get included, and I set the two constants to something different for each app. The first one I keep under 100, as is a multiplier, the second number gets added in, so I usually make it around what it is (to be honest, in this example, that was my father's birthday, the other one is the year my mom was born. SHHH don't tell her I told you her age!)

The main thing to keep in mind with these two numbers, is how they are used with your original integer you want to hide. Based upon these numbers, it limits how large of an integer you can pass to this function. (when it does the calculation, I don't want it making a number larger than what an integer can hold). Here is a sample line you can throw in when you first set it up to see what it will hold:

Syntax: [ Download ] [ Hide ]
echo "MAX NUMBER = ",(int)(((PHP_INT_MAX) - KEY_CALC_2 + KEY_CALC_2)/KEY_CALC_1),"\n";


In this case, on my machine, I can use numbers up to 45,691,141. I admit, I haven't done anything that used more than 45 million keys, and if I did do something that ended up even getting close (ie, hit 20 million), I could modify the two functions to handle it differently.

Now you may have noticed the rand() function used, and the fact above when I gave the sample URL, I said "I would have something like this", not that I would have that exact key. That is because it also randomizes some of it. As a matter of fact, ALL of these keys will return the number 33 using the constants given above:
Syntax: [ Download ] [ Hide ]
XEDNhdTOPJjY1I2N
IODI3NzFPYWVi
GMGU2YzFPMmFiNQ
FAOjRzNPFjZ3gTN
GNGEwOTFPNjY1Mg
BAOyYTOPFTZkF2N
ONjRmYzJPODA0NjE
RQO5QWNPJDM1EzM
GZTM4ZDFPMDBkNg
SYTgzNzFPYzQ4
AZTA3OTJPMGRiMjE
BEzMlRDZPJTY5gjY
KNGQxNTJPNjk2ZQ


Like I said, I like to keep it as least guessable as possible. Also, unlike what some people do, in my code I apply the trim(xxxx,'='); to the result of my base64_encode() One big give away for people who know programming, is the tell tail ='s after a string to hint at base64. Nice thing about base64_decode() is that it does NOT need (at least on all environments I have tested it) the trailing ='s. Without doing this, my 33 keys would be like this:
Syntax: [ Download ] [ Hide ]
QY2E3YTJPZTZjMzE=
J==QNkRTNPFzY4gzM
B==gNhdTMPFDZ1EmZ
KNDllNzFPNjUzMQ==
WMjAwMjJPNGM0Yg==
YMmJkYTJPNDcyNDE=
H==QMlZTNPFDO5E2M
AZTY5ZjFPMDNlOA==
So if someone were trying to guess this (and they got one where the ='s were on the right), they would first try base64_decode(). Well that fails right off the bat, because I have an extra letter at the beigning to indicate if the key was reversed or not. So even if they figure this out, and trim it off the first character, and can base64_decode it, they would get something like this:
Syntax: [ Download ] [ Hide ]
a9dd1Oc527
e7912O04ea
58022O745b
63562O8f9f
3a282O56711
44162O606f


So they might say, "hmm, that looks like hex, so lets do hexdec() to it." Well there are two problems with this. Depending on the font displaying their output, they will miss that that is NOT a hex code. ex, the font on this form, you have to look closely that they are different, check out that second one closely to tell that there is a capitol letter o in there, looks like a zero huh. I use a specific font on PhpED that I love cause you can easily tell the difference, zeros have a dot in the middle. (Bitstream Vera Sans Mono dunno where I got it, but some app installed it at one point on my system and I love it for a fixed width font!) Oh, and the second issue with just doing hexdec() is that the string is reversed!

So now, at this point, after they somehow guessed it was reversed, it comes down to guessing that the O is a separator between the random number, and the actual number from calculations. Then from there, knowing which number is the actual key, and how to calculate that key back down to the actual integer!

At this point, I think you can agree, it is gonna be quite a pain in the rump to try to guess what id=RgMwczNPNTN1YDN is.

So hope this helps. there are plenty of ways to do all of this, this is just the method I have used over the years, which has always worked well.

-Greg

PS. I give credit to someone who can tell me what integer RgMwczNPNTN1YDN represents. I used the exact functions above, the only thing I changed was the two constants at the top. If you can't get it from there, as a good hacker, you may also have this knowledge available, that for your ID (2924), the key you see viewing your own profile was VQMmZWOPNjNklTN....


Top
 Profile  
 
PostPosted: Sun Oct 09, 2011 8:59 am 
Offline
DevNet Master
User avatar

Joined: Sun Feb 15, 2009 12:08 pm
Posts: 2794
Location: .za
Glad to see someone who also think along those lines ;) Yeah i've often had similar ideas about a malicious user being able to view data not meant to be seen by anyone else or unless said data has been cleared for public consumption. My method of combating this (you probably have similar checks in place) is to see if whoever visits the page is 'priviliged' to see the information and if the record(s) selected can indeed be displayed to a user.

That said i think what you have here is an excellent addition to the above method. I've often found myself thinking how to 'hide' the id because even if page.php?id=5 would only bring up an already existing article that the general public can find somewhere else on the site, i prefer users to get there the 'legal' way; not by manipulating urls and in the process potentially exposing other non-public information. Also, i like to keep information about my databases (specifically the structure) as secret as i possibly can.

_________________
“Don’t worry if it doesn’t work right. If everything did, you’d be out of a job.” - Mosher’s Law of Software Engineering


Top
 Profile  
 
PostPosted: Sun Oct 09, 2011 11:16 am 
Offline
Moderator
User avatar

Joined: Mon Nov 03, 2003 7:13 pm
Posts: 5975
Location: Odessa, Ukraine
Having random unique ids would have served you better (hint: uuid v4, uniqid()).


Top
 Profile  
 
PostPosted: Sun Oct 09, 2011 3:55 pm 
Offline
Forum Regular
User avatar

Joined: Tue Sep 28, 2010 11:41 am
Posts: 983
Location: Columbus, Ohio
Thanks for the info on the UUID, knew of similar for PK's from MS SQL.

Looking at the function: http://dev.mysql.com/doc/refman/5.0/en/ ... ction_uuid

Syntax: [ Download ] [ Hide ]
mysql> SELECT UUID();
        -> '6ccd780c-baba-1026-9564-0040f4311e29'


So, if I were to use this for PK on a table, I'd set it to char(36) for the field. How is this performance wise in lookup and joins for FK's? I admit, I am not very good on the better use and practice of indexes and such, (ie, best methods and such).

Also, on the original post, I also use this for other items besides PK's, in fact I wrote this thread as I found I was explaining it so much for another thread, where it was just to "hide" a number, that I decided to make this "sample and explanation" thread and on the other link to it :-)

Quote:
i like to keep information about my databases (specifically the structure) as secret as i possibly can

I am the same way, while open source systems like Drupal/Joomla/WP are great for how advanced and well tested they are, that is a down side that if someone does find a way to hack the site, it is well known the structure behind it.

-Greg


Top
 Profile  
 
PostPosted: Mon Oct 10, 2011 4:00 am 
Offline
Moderator
User avatar

Joined: Mon Nov 03, 2003 7:13 pm
Posts: 5975
Location: Odessa, Ukraine
Quote:
So, if I were to use this for PK on a table, I'd set it to char(36) for the field.

binary(16) - uuid is a 128bit int in fact.


Top
 Profile  
 
PostPosted: Mon Nov 07, 2011 7:10 pm 
Offline
Forum Regular
User avatar

Joined: Tue Sep 28, 2010 11:41 am
Posts: 983
Location: Columbus, Ohio
Weirdan wrote:
binary(16) - uuid is a 128bit int in fact.

I just gave this a try on something I was working on, and found binary(16) was (at least on my server) was cutting off the UUID, just had to expand it a bit to get it to work, but in the end, dropped it back just an auto increased unsigned int. Just more used to it ;-)


Top
 Profile  
 
PostPosted: Tue Nov 08, 2011 11:13 am 
Offline
Moderator
User avatar

Joined: Mon Nov 03, 2003 7:13 pm
Posts: 5975
Location: Odessa, Ukraine
twinedev wrote:
Weirdan wrote:
binary(16) - uuid is a 128bit int in fact.

found binary(16) was (at least on my server) was cutting off the UUID

that's because (most likely) you generated string representation of UUID rather than binary. binary uuids are always 128bits.


Top
 Profile  
 
PostPosted: Tue Nov 08, 2011 3:06 pm 
Offline
Forum Regular
User avatar

Joined: Tue Sep 28, 2010 11:41 am
Posts: 983
Location: Columbus, Ohio
I think I am missing something here.....
Syntax: [ Download ] [ Hide ]
mysql> CREATE TABLE `testid` (`id` BINARY(16) NOT NULL, PRIMARY KEY (`id`));
Query OK, 0 rows affected

mysql> INSERT INTO `testid` VALUES (UUID());
Query OK, 1 row affected

mysql> SELECT * FROM `testid`;
+------------------+
| id               |
+------------------+
| 9e858d9a-0a44-11 |
+------------------+
1 row in set

mysql> ALTER TABLE `testid` CHANGE `id` `id` BINARY(36);
Query OK, 1 row affected
Records: 1  Duplicates: 0  Warnings: 0

mysql> INSERT INTO `testid` VALUES (UUID());
Query OK, 1 row affected

mysql> SELECT * FROM `testid`;
+--------------------------------------+
| id                                   |
+--------------------------------------+
| 9e858d9a-0a44-11
| ae93bf5e-0a44-11e1-a80e-0019998b9bf0 |
+--------------------------------------+
2 rows in set
mysql>

If I'm going to use the UUID for things such as query strings, I need to be able to retrieve it from the database. Like I said, I think I'm missing something. Never worked with Binary field before.

-Greg


Top
 Profile  
 
PostPosted: Wed Nov 09, 2011 10:45 am 
Offline
Moderator
User avatar

Joined: Mon Nov 03, 2003 7:13 pm
Posts: 5975
Location: Odessa, Ukraine
mysql's UUID() function returns text representation. If you need more compact uuids, you'd have to generate it outside of mysql (using something like pecl/uuid)


Top
 Profile  
 
PostPosted: Wed Nov 09, 2011 8:51 pm 
Offline
Forum Regular
User avatar

Joined: Tue Sep 28, 2010 11:41 am
Posts: 983
Location: Columbus, Ohio
Ahh, so for storing what mySQL's UUID() function produces, might as well store it as CHAR(36)?

I think that is where I was misunderstanding; I was using their function and though that was what you meant to store as BINARY(16).

Will look some more into this later when I get a chance then.

-Greg


Top
 Profile  
 
PostPosted: Sat Dec 06, 2014 1:57 pm 
Offline
Forum Regular
User avatar

Joined: Tue Sep 28, 2010 11:41 am
Posts: 983
Location: Columbus, Ohio
Well I was doing an update, and figured I'd post the update here.

The update allows for both positive and negative integers, all the way to the maximum integer size for the PHP install. (before, due to calculations, it couldn't handle the full size). Also this one lets you specify which characters to use for for the keys, and they can be in any order.

The constant for the character set can be rearranged between different apps.

Syntax: [ Download ] [ Hide ]
// For use with the int2key and key2int functions. needs to be the same across the
// same app. This set should be at LEAST 4 typable characters, in any order
// (for use in URL query strings, should only be [A-Za-z0-9_-.])
define ('INT_KEY_SET', 'dQ1mvkXPVhefScay6tpb9jKzHWMRY8s3BEU0lL25rInZ7goxAi4uJqTOCwFNDG' );

/**
* Function that goes with int2key and key2int functions for getting the base
* number conversion over to the set of characters. This is only for positive
* numbers.
*
* @param integer $intNumber A positive only integer
* @return string The key for the intger
*/

function pint2char( $intNumber ) {
        static $strSet = INT_KEY_SET;
        static $intSetLen = null; if ( is_null( $intSetLen ) ) { $intSetLen = strlen( $strSet ); }
        if ( !is_int( $intNumber) ) { return false; }
        if ( $intNumber == 0 ) { $strNumber = $strSet{0}; }
        else {
                $strNumber = '';
                while( $intNumber > 0 ) {
                        $strNumber .= $strSet{ $intNumber % $intSetLen };
                        $intNumber = floor( $intNumber / $intSetLen );
        }       }
        return $strNumber;
}

/**
 * Converts given integer over to a string of typeable characters to mask the
 * true value. It is mixed with either the given or a random seed.
 *
 * @param integer $intNumber The number to convert, positive or negative
 * @param integer $intSeed Option positive only seed for mask. Defaults to random
 * @return string The key that equates to the given iteger
 */

function int2key( $intNumber, $intSeed = 0 ) {
        static $strSet = INT_KEY_SET;
        if ( !is_int( $intNumber) ) { return false; }
        if ( $intSeed > 0 ) { $strSeed = pint2char( $intSeed ); $intRev = $intSeed % 2; }
        else { $strSeed = pint2char( mt_rand( 8291971, PHP_INT_MAX ) ); $intRev = mt_rand( 0, 1 ); }
        while ( strlen($strSeed) < 10 ) { $strSeed .= $strSeed; }
        $intControl = ( ord( $strSeed{0} ) % ( strlen( $strSet ) / 4 ) * 4 );
        $intCtrlAdj = 0;
        if ( $intNumber < 0 ) { $intNumber = abs( $intNumber ); $intCtrlAdj++; }
        $strNumber = pint2char( $intNumber );  
        $intNumberLen = strlen( $strNumber );
        if ( $intNumberLen == 1 ) { $strNumber .= $strSet{0}; $intNumberLen = 2; }
        $strOut = '';
        for( $c = 0; $c < $intNumberLen; $c++ ) { $strOut .= $strNumber{$c} . $strSeed{ $c }; }
        if ( $intRev == 1 ) { $intCtrlAdj += 2; $strOut = strrev( $strOut ); }
        return $strSet{ $intControl + $intCtrlAdj } . $strOut;
}

/**
 * Converts a string from generated by the int2key function back over to the
 * original integer value
 *
 * @param string $strKey The already existing key to be converted
 * @return integer The original integer value
 */

function key2int( $strKey ) {
        static $strPregSet = null; if ( is_null( $strPregSet ) ) { $strPregSet = '/^[' . preg_quote( INT_KEY_SET ) . ']{3,}$/'; }
        static $arySet = array() ; if ( count( $arySet ) == 0 ) { $arySet = array_flip( str_split( INT_KEY_SET ) ); }
       
        if ( !preg_match( $strPregSet, $strKey ) ) { return false; }
        $intControl = $arySet[ $strKey{ 0 } ] % 4;
        if ( $intControl < 2) { $strKey = substr( $strKey, 1); }
        else { $strKey = strrev( substr( $strKey, 1) ); }
        $intKeyLen = strlen( $strKey ) / 2;
        for ($c = 0; $c < $intKeyLen; $c++ ) {
                if ( $c < 1) { $intReturn  = $arySet[ $strKey{ $c * 2 } ]; }
                else { $intReturn += $arySet[ $strKey{ $c * 2 } ] * pow( count( $arySet ), $c ); }
        }
        if ( $intControl % 2 ) { $intReturn = 0 - $intReturn; }
        if ( (int)$intReturn != $intReturn ) { return false; }
        return (int)$intReturn;
}
 


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 11 posts ] 

All times are UTC - 5 hours


Who is online

Users browsing this forum: Bing [Bot] and 21 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  
Powered by phpBB® Forum Software © phpBB Group