PDA

View Full Version : Neopets cookies?



maxderpy
10-10-2014, 08:21 PM
I didn't really find a forum specific to this question, but it's kind of programming related and kind of neopets hacking related so it goes here.

So when playing around with neopets, sending some cookies back to index.phtml, and I got the following headers:


[Only registered and activated users can see links] 200 OK
Date Sat, 11 Oct 2014 00:57:43 GMT
Server Apache/2.2.15 (CentOS)
X-Powered-By PHP/5.4.28
X-Cobalt loaded
X-XSS-Protection 0
Set-Cookie np_uniq=pending; expires=Sun, 11-Oct-2015 00:57:44 GMT; path=/; domain=.neopets.com
Set-Cookie nupi=0; expires=Fri, 10-Oct-2014 23:17:44 GMT; path=/; domain=.neopets.com
Set-Cookie nupid=0; expires=Fri, 10-Oct-2014 23:17:44 GMT; path=/; domain=.neopets.com
Set-Cookie npid=0; expires=Fri, 10-Oct-2014 23:17:44 GMT; path=/; domain=.neopets.com
Set-Cookie npuid=00000000000000000000000000000000000000000000 00000000000000000000; expires=Mon, 10-Nov-2014 00:57:44 GMT; path=/; domain=.neopets.com
Set-Cookie ld_=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=.neopets.com
Set-Cookie np_uniq_=2014-10-10; expires=Sun, 11-Oct-2015 00:57:44 GMT; path=/; domain=.neopets.com
Connection close
Transfer-Encoding chunked
Content-Type text/html; charset=UTF-8

What's up with the expiry times on these cookies being before the time the headers were sent? Is this the standard way http deletes cookies? Or is this neopets trying to detect programs that don't handle cookie expirations properly? Or what?

Zachafer
10-13-2014, 09:44 PM
I've seen that, not sure why they do that. The browser will delete those cookies when the page loads

DarkByte
10-13-2014, 10:15 PM
here is the source code to cookies.php


<?php

uselib('db/db_funcs_v1');
uselib('db/npflags_v1');
uselib('content/neofriend_func');
uselib('neopets/UserUtils');


uselib('neopets/flag_defs');
NeopetsSiteFlags::register();


class NPCookies {
const LOGIN_COOKIE_NAME = 'neologin';
const TOOLBAR_COOKIE_NAME = 'toolbar';


const SALT_NORMAL = '88f5065ac4';
const SALT_STEALTH = 'e044aa45d2';


/**
* Constrain the cookie code value to a legal range. The lower limit is
* 1000, not 1, because neologin_decode() returns 0 if the ciphertext is
* malformed. Since we allow cookie codes to match within +/- 2 of the
* actual value, this would cause a false authentication if someone's
* cookie_code was 1 or 2, and the cookie decrypted to 0.
*
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return int
*/
public static function clamp_cookie_code($val) {
return min(max($val, 1000), 999999999);
}


/**
* Break the login cookie into its component parts. Returns false if the
* cookie is malformed.
*
* static
[Only registered and activated users can see links]
* Return array|bool
*/
public static function parse_login_cookie() {
$out = false;


if (!isset($_COOKIE[self::LOGIN_COOKIE_NAME])) return false;


if (preg_match("/^([^+]+)\+([^+]+)$/", $_COOKIE[self::LOGIN_COOKIE_NAME], $matches)) {
$out = array(
'cookie' => $_COOKIE[self::LOGIN_COOKIE_NAME],
'username' => strtolower(substr($matches[1], 0, 20)), // make sure it's max 20 chars long and is all lowercase
'ciphertext' => $matches[2]
);
}


return $out;
}


/**
* Break the toolbar cookie into its component parts. Returns false if the
* cookie is malformed.
*
* static
[Only registered and activated users can see links]
* Return array|bool
*/
public static function parse_toolbar_cookie() {
$out = false;


if (!isset($_COOKIE[self::TOOLBAR_COOKIE_NAME])) return false;


if (preg_match("/^([^+]+)\+([^+]+)\+([^+]+)$/", $_COOKIE[self::TOOLBAR_COOKIE_NAME], $matches)) {
$out = array(
'cookie' => $_COOKIE[self::TOOLBAR_COOKIE_NAME],
'username' => strtolower(substr($matches[1], 0, 20)), // make sure it's max 20 chars long and is all lowercase
'userclass' => $matches[2],
'ciphertext' => $matches[3]
);
}


return $out;
}


/**
* Return the standard formatted input for the cookie hash function to use.
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return string
*/
public static function get_normal_key($username, $password) {
return "{$username}{$password}" . self::SALT_NORMAL;
}


/**
* Same as get_normal_key(), but with the stealth salt.
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return string
*/
public static function get_stealth_key($username, $password) {
return "{$username}{$password}" . self::SALT_STEALTH;
}


/**
* Basic function for hashing a cookie value. $key is their password, and
* $plaintext is the cookie_code value.
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return string
*/
public static function cookie_hash_encrypt($key, $plaintext) {
// Do not change the message string below or it'll log everyone out.
// Conversely, if you want to log everyone out, change the message string below. :)
$message = "npc_{$key}_{$plaintext}_biglongrandomstringhahalol thecakeisalie";
return sha1($message);
}


/**
* Set the login cookie.
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return void
*/
public static function set_login_cookie($username, $key, $plaintext, $expire) {
// make sure the cookie is never set for frozen users - NEADMN-34 - adamb / 2010-06-24
if (is_frozen($username)) {
return false;
}
$cookie_value = "{$username}+" . self::cookie_hash_encrypt($key, $plaintext);
setcookie(self::LOGIN_COOKIE_NAME, $cookie_value, $expire, "/", ".neopets.com");
$_COOKIE[self::LOGIN_COOKIE_NAME] = $cookie_value;
}


/**
* Set the toolbar cookie. This is used for the VSI/premium toolbar.
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
[Only registered and activated users can see links]
[Only registered and activated users can see links]
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return void
*/
public static function set_toolbar_cookie($username, $key, $plaintext, $expire, $password, $dob) {
// make sure the cookie is never set for frozen users - NEADMN-34 - adamb / 2010-06-24
if (is_frozen($username)) {
return false;
}
$hash = self::cookie_hash_encrypt($key, $plaintext);
$permission = npflag_check($username, NPFLAG_PARENTAL_PERMISSION);
$agecode = self::toolbar_cookie_agecode($dob, $permission);
$cookie_value = "{$username}+{$agecode}+{$hash}";
setcookie(self::TOOLBAR_COOKIE_NAME, $cookie_value, $expire, "/", ".neopets.com");
$_COOKIE[self::TOOLBAR_COOKIE_NAME] = $cookie_value;
}


/**
* Calculate the agecode to put into the toolbar cookie.
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return string
*/
public static function toolbar_cookie_agecode($dob, $permission) {
if ($dob == '') $age = 0;
else $age = UserUtils::ageFromDob($dob);


if ($age < 13) {
if (!$permission) {
$agecode = 'A';
} else {
$agecode = 'D';
}
} else if ($age < 18) {
$agecode = 'B';
} else {
$agecode = 'C';
}


return $agecode;
}


/**
* Update last_logged_time and cookie_code.
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return void
*/
public static function update_llt_cc($username, $time, $cookie_code) {
$sql = "UPDATE personal SET last_logged_time = '{$time}', cookie_code = '{$cookie_code}' WHERE username = '{$username}'";
$rsUpdate = myoci("", $sql);
return $rsUpdate;
}


/**
* Try to decode a cookie with both the regular and stealth salts, and
* return which one worked (if any).
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return array
*/
public static function decrypt_login_cookie($username, $password, $cookie_code) {
$parts = self::parse_login_cookie();
if ($parts == false) return false;


$out = array(
'parts' => $parts,
'normal' => false,
'stealth' => false,
'pt_normal' => '',
'pt_stealth' => '',
);


$key_normal = self::get_normal_key($username, $password);
$key_stealth = self::get_stealth_key($username, $password);


// The order here is from most likely to least likely. Usually the 0 offset will match;
// if not, then it's likely that they're 1 behind. If not, then 2 behind. Failing that,
// we try +1 and +2 which will almost never succeed.
$offsets = array(0, -1, -2, 1, 2);


foreach ($offsets as $offset) {
// For each offset, we need to try encrypting with the cookie_code plus that offset and see if it matches.

$normal_hash = self::cookie_hash_encrypt($key_normal, $cookie_code + $offset);
if ($normal_hash == $parts['ciphertext']) $out['normal'] = true;
else {
$stealth_hash = self::cookie_hash_encrypt($key_stealth, $cookie_code + $offset);
if ($stealth_hash == $parts['ciphertext']) $out['stealth'] = true;
}


// If we found the correct hash, there's no reason to check the rest.
if ($out['normal'] == true || $out['stealth'] == true) break;
}


return $out;
}


/**
* Same as decrypt_login_cookie, but for the toolbar cookie.
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return array
*/
public static function decrypt_toolbar_cookie($username, $password, $cookie_code) {
$parts = self::parse_toolbar_cookie();
if ($parts == false) return false;


$out = array(
'parts' => $parts,
'normal' => false,
'stealth' => false,
'pt_normal' => '',
'pt_stealth' => '',
);


$key_normal = self::get_normal_key($username, $password);
$key_stealth = self::get_stealth_key($username, $password);


// The order here is from most likely to least likely. Usually the 0 offset will match;
// if not, then it's likely that they're 1 behind. If not, then 2 behind. Failing that,
// we try +1 and +2 which will almost never succeed.
$offsets = array(0, -1, -2, 1, 2);


// Try to decrypt it the old way. If it works, yay. If not, try the new way.
// Once there are no more cookies encrypted the old way, we can get rid of this.


// For the toolbar cookie, the "old way" was a simple MD5 hash of the password. Clever.
if ($parts['ciphertext'] == md5($password)) {
$out['normal'] = true;
return $out;
} else {


// Okay, it didn't work using the old method. Try the new method.


foreach ($offsets as $offset) {
// For each offset, we need to try encrypting with the cookie_code plus that offset and see if it matches.


$normal_hash = self::cookie_hash_encrypt($key_normal, $cookie_code + $offset);
if ($normal_hash == $parts['ciphertext']) $out['normal'] = true;
else {
$stealth_hash = self::cookie_hash_encrypt($key_stealth, $cookie_code + $offset);
if ($stealth_hash == $parts['ciphertext']) $out['stealth'] = true;
}


// If we found the correct hash, there's no reason to check the rest.
if ($out['normal'] == true || $out['stealth'] == true) break;
}


}


return $out;
}


/**
* The main cookie manipulation method. Can be used to set, update, check,
* or clear the login cookie.
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return bool
*/
public static function update_login_cookie($NPUser, $action, $lang) {


// Convenience vars
$new_code = intval($NPUser->cookie_code);


// Make sure cookie_code is in the valid range.
$new_code = self::clamp_cookie_code($new_code);


$now = time();


// set cookie expiration time, admins only get 12 hours
$expire = ($NPUser->is_admin) ? ($now + (3600 * 12)) : ($now + (3600 * 24 * 365));


if ($action == 'login' || $action == 'stealth') {


if ($action == 'login') {


// Anyone whose password isn't a 20-char hex string gets it changed forcibly here.
// This block can be deleted on or after 2012-03-01. - waggonem 2012-01-11
if (!preg_match('/^[a-f0-9]{20}$/', $NPUser->password)) {
$token = self::resetLoginToken($NPUser->username);
$NPUser->password = $token;
}


$key = self::get_normal_key($NPUser->username, $NPUser->password);


// Regular logins cause the last_logged_time and cookie_code to be updated;
// stealth logins do not.
$new_code = self::clamp_cookie_code($new_code + 1);
self::update_llt_cc($NPUser->username, $now, $new_code);


// Also do session tracking. TODO: Shouldn't this use $now?
self::session_tracker($NPUser, $lang);


} else if ($action == 'stealth') {
$key = self::get_stealth_key($NPUser->username, $NPUser->password);
}


// Set the login cookie.
self::set_login_cookie($NPUser->username, $key, $new_code, $expire);


// Set the old login cookie, too.
self::set_toolbar_cookie($NPUser->username, $key, $new_code, $expire, $NPUser->password, $NPUser->dob);


} else if ($action == 'logout') {


// Log out. This causes all the login cookies to be cleared from the
// user's computer. If the user is currently normally logged in, then
// we also update their llt and cookie_code, which will invalidate any
// OTHER cookies (that, e.g. might have gotten grabbed).


// Stealth logouts do not cause an llt/cc update, because we don't want
// to interfere with the legitimately logged-in user.


$decrypt = self::decrypt_login_cookie($NPUser->username, $NPUser->password, $new_code);


if ($decrypt != false && $decrypt['normal'] == true) {


$new_code = self::clamp_cookie_code($new_code + 5);
self::update_llt_cc($NPUser->username, $now, $new_code);


// Track session info.
self::session_tracker($NPUser, $lang);
}


// Clear all the login cookies.
setcookie(self::LOGIN_COOKIE_NAME, '', 0, "/", ".neopets.com");
setcookie(self::TOOLBAR_COOKIE_NAME, '', 0, "/", ".neopets.com");


} else if ($action == 'check' || $action == 'check_no_update') {


// Validate the cookie. This is used to determine whether the user is actually logged in.
// The 'check_no_update' action means that we only examine the cookie, we don't even bother
// trying to update it.


$decrypt = self::decrypt_login_cookie($NPUser->username, $NPUser->password, $new_code);


// The cookie was malformed, or the ciphertext did not decrypt with either key.
if ($decrypt == false || ($decrypt['normal'] == false && $decrypt['stealth'] == false)) {
return false;
}


// If it's been more than 5 minutes since the last update, we want to
// increment the cookie_code counter and resave the cookie.


if ($action == 'check' && $now - $NPUser->last_logged_time > 300) {


// If it's a normal login, then we update llt and cookie_code.
if ($decrypt['normal'] == true) {
// Update their last_logged_time and cookie_code.
$new_code = self::clamp_cookie_code($new_code + 1);
self::update_llt_cc($NPUser->username, $now, $new_code);


// track session info
self::session_tracker($NPUser, $lang);


$key = self::get_normal_key($NPUser->username, $NPUser->password);
} else if ($decrypt['stealth'] == true) {
$key = self::get_stealth_key($NPUser->username, $NPUser->password);
}


// Refresh the cookie for normal AND stealthed users.
self::set_login_cookie($NPUser->username, $key, $new_code, $expire);


// Refresh the old cookie, too.
self::set_toolbar_cookie($NPUser->username, $key, $new_code, $expire, $NPUser->password, $NPUser->dob);
}


// Whether or not we updated llt/cc and refreshed the cookie, either the old one
// or the new one matched. Validated! Yay!
//return true;


// return false if the user is frozen - NEADMN-34 - adamb / 2010-06-24
return is_frozen($NPUser->username) ? false : true;
}


// Return false in all other cases.
return false;
}


/**
* Track user sessions. A horrible abomination.
*
[Only registered and activated users can see links]
[Only registered and activated users can see links]
* static
[Only registered and activated users can see links]
* Return void
*/
public static function session_tracker($NPUser, $lang) {
// This is horrible. We have to stop using this somehow. But who can
// save us?
global $xt6Yr4e33D;


if ($xt6Yr4e33D != '') {


$code = $xt6Yr4e33D;
$now = time();
$cutoff = $now - 1800;


if ($NPUser->last_logged_time < $cutoff) {
$sql = "INSERT IGNORE INTO expired_sessions SELECT * FROM active_sessions WHERE code = '{$code}' AND end_time < '{$cutoff}'";
myoci('', $sql);


$sql = "INSERT INTO active_sessions (code, start_time, end_time, ip, lang) VALUES ('{$code}', '{$now}', '{$now}', '{$_SERVER['REMOTE_ADDR']}', '{$lang}') ON DUPLICATE KEY UPDATE end_time = '{$now}', start_time = '{$now}', ip = '{$_SERVER['REMOTE_ADDR']}', lang = '{$lang}'";
myoci('', $sql);
} else {
$sql = "INSERT INTO active_sessions (code, start_time, end_time, ip, lang) VALUES ('{$code}', '{$now}', '{$now}', '{$_SERVER['REMOTE_ADDR']}', '{$lang}') ON DUPLICATE KEY UPDATE end_time = '{$now}'";
myoci('', $sql);
}


if ($NPUser->username != '') {
$sql = "UPDATE active_sessions SET username = '{$NPUser->username}', userflag = '{$NPUser->flags}', gender = '{$NPUser->sex}', dob = '{$NPUser->dob}', age = '{$NPUser->age}', dma = '{$NPUser->dma}', lang = '{$lang}', signup_country = '{$NPUser->country}', ip_country = '{$NPUser->geoip_country}', zip = '{$NPUser->zipcode}' WHERE code = '{$code}'";
myoci('', $sql);
}
}
}




/**
* We need to be able to generate a random 20-char string to put into the
* password field, now that it is no longer containing the user's canonical
* password.
*/
public static function genPasswordToken() {
mt_srand();
return substr(sha1(uniqid('', true)), 0, 20);
}




/**
* Reset a user's login token.
*/
public static function resetLoginToken($username) {
$token = self::genPasswordToken();
$sql = "UPDATE personal SET password = '{$token}' WHERE username = '{$username}' LIMIT 1";
$rsUpdate = myoci('', $sql);
return $token;
}


}

txtsd
10-13-2014, 10:39 PM
DarkByte wrap it in [CODE] tags, noob :P

DarkByte
10-13-2014, 10:45 PM
I did but it all messed up and went to single lines xD


Edit:

Ok noob status confirmed , [code] works i was usin [php] :D

maxderpy
10-14-2014, 05:06 PM
here is the source code to cookies.php


code expunged for brevity.

Thanks, that's actually very helpful. Do you have the php source for all of neopets?

DarkByte
10-14-2014, 07:04 PM
Yep pretty much :P

Josh
10-14-2014, 10:17 PM
DarkByte - You're ridiculous. Lol.