Recovering from an invalid session id in the most elegant manner - php

Lately, I've been getting the following error(s) in my log:
PHP Warning: session_start(): The session id is too long or contains illegal characters, valid characters are a-z, A-Z, 0-9 and '-,' in [...] on line [..]
PHP Warning: Unknown: The session id is too long or contains illegal characters, valid characters are a-z, A-Z, 0-9 and '-,' in Unknown on line 0
PHP Warning: Unknown: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/var/lib/php5) in Unknown on line 0
The answers to this previous question suffice for detecting and diverting such scenarios, so that no error is generated, but I'm interested in the most elegant recovery; that is, ending up with a valid new (empty) session.
First, though, a few refinements to the detection & diversion code in that previous question, which is now six years old:
These days, sessions are more likely to be handled exclusively through cookies. The session.use_only_cookies flag in my php.ini is enabled, and since I don't remember changing it I assume this is the default value.
Which characters are used in a valid session id, and how many of those characters, depends on the session.hash_function and session.hash_bits_per_character values in php.ini. My values are 0 and 5, respectively, which (unless I'm mistaken) means my own session ids should match the regular expression /^[a-v0-9]{26}$/. I assume these are default values as well.
The name of the cookie used to store the session can be customized using session.name in php.ini. The correct value can always be retrieved using the session_name() function.
Given these, the most elegant means of diversion would (probably) be as follows:
function my_session_start() {
if (!isset($_COOKIE[session_name()]) || preg_match('/^[a-v0-9]{26}$/', $_COOKIE[session_name()]) !== 0) {
return session_start(); // since 5.3, returns TRUE on success, FALSE on failure.
}
else {
return false;
}
}
As for recovery (which would replace the return false; in the else block), one answer to the previous question suggested the following:
session_id(uniqid());
session_start();
session_regenerate_id();
I'm concerned, however, that this means of recovery, having received only two upvotes and no comments, is insufficiently reviewed.
The answer to this question suggests that the internal session_start() function relies directly on the value of $_COOKIE[session_name()], rather than some other internal representation of that value. Is this the case? If so, a my_session_start() function with detection and recovery could be as simple as:
function my_session_start() {
if (isset($_COOKIE[session_name()]) && preg_match('/^[a-v0-9]{26}$/', $_COOKIE[session_name()]) === 0) {
unset($_COOKIE[session_name()]);
}
return session_start();
}

I've only seen this happen when $_COOKIE['PHPSESSID'] === ''. This happens when someone uses Firebug to "clear cookie". I want to only handle this specific scenario; if the session ID is invalid in some other way, I want to be warned. Dealing with my specific use case is simple:
$session_name = session_name();
if (isset($_COOKIE[$session_name]) and empty($_COOKIE[$session_name])) {
// This happens when someone does "clear cookie" in Firebug; it causes session_start()
// to trigger a warning. session_start() relies on $_COOKIE[$session_name], thus:
unset($_COOKIE[$session_name]);
}
session_start();
Out of curiosity, do you actually need to deal with any scenario other than it being an empty string? It could only be something else if done maliciously (in order to trigger a warning), but since you have display_errors disabled in production, nobody can gain any information from that.

Related

How to avoid this sort of code bloat with PHP 8+ Warning for missing array keys

With PHP 8 The concept of an unfound array key was promoted from a NOTICE error level to a WARNING error level.
This causes us huge code and error_log bloats where we turn off notices and retain warnings on error logs which end up being filled with repetative warnings about array key existance.
For example; Some code to check if a SESSION value exists and if so then use it in some local code. SESSION value is set in some other part of the website and is dynamically set.
<?php
// This code is server side action example
include whatever_header.php //sets session start etc.
if($_SESSION['action'] === "submit"){
...
}
$localPageDataArray = $_SESSION['core']['data']?:['default' => 'no'];
Previous to PHP 8, if $_SESSION action was not set it didn't matter, because the if condition only executed if it was set to a specific value. To be clear; if $_SESSION['action'] was never set then the code would work in exactly the same way as if $_SESSION['action'] === "sleep" , ie various conditionals would still fire or not fire accordingly.
NOW, the error logs blow up with various warnings such as:
PHP Warning: Undefined array key "action" in /home/public_html/session_test_script.php on line 5
PHP Warning: Undefined array key "data" in /home/public_html/session_test_script.php on line 9
There are a couple of issues with this warning feedback:
It blows up and hugely inflates the error logs
It causes huge code bloat to workaround this issue:
Example of code bloat being to resolve the above warning would be:
<?php
// This code is server side action example
include whatever_header.php //sets session start etc.
if(array_key_exists('action',$_SESSION) && $_SESSION['action'] === "submit"){
...
}
Or even worse:
<?php
// This code is server side action example
include whatever_header.php //sets session start etc.
if(array_key_exists('action',$_SESSION) && $_SESSION['action'] === "submit"){
...
}
$localPageDataArray = ['default' => 'no']; //default value if below IF is false
if(array_key_exists($_SESSION,'core') && is_array($_SESSION['core'])
&& array_key_exists($_SESSION['core'],'data') {
$localPageDataArray = $_SESSION['core']['data']?:['default' => 'no'];
}
This is a huge bloat on the code for absolutely zero benefit aside from removing tedious warning messages from the error logs. Because the issue has been promoted to WARNING level then we can't realistically turn of WARNING logs as that's a bad idea for genuine warnings.
While we can slightly streamline the above to being an isset check thus:
if(isset($_SESSION['core']['data']) && is_array($_SESSION['core']['data'])) {
$localPageDataArray = $_SESSION['core']['data']?:['default' => 'no'];
}
This still greatly inflates the code size for zero practical improvment.
Question
Are there any workarounds to how to easily and efficiently avoid these Warning error log messages without having to deal with multiple lines of hard coding checking processes?
Note that these issues are due to $_SESSION (and sometimes other superglobal data) being dynamically generated so we can't always ensure that a value exists and that's built into the default checking mechanisms as illustrated here.
If we can do the above with one line, this would be a great compromise:
$localPageDataArray = isset($_SESSION['core']['data'])?$_SESSION['core']['data']:['default' => 'no'];
BUT This only sets the default value (['default' => 'no']) if the $_SESSION['core']['data'] is not set, rather than if it is set and is empty
Not a Dupe
This question is not a dupe because I'm not caring about speed (although it's nice isset is so fast) but asking about how to avoid the error_log Warning messages found in PHP 8+ which are a different issue to speed.
With the help of Cid a minimal coded solution would be to use ?? Null coalescing operator and/or isset to check values are set without getting strung up on intermediate array keys in multidimensional arrays.
e.g:
if(isset($_SESSION['action']) && $_SESSION['action'] === "submit"){
...
}
$localPageDataArray = $_SESSION['core']['data']??['default' => 'no'];
Neither of these tests will now produce Warnings about undefined array keys

What are the limits on session names in PHP?

The PHP docs on session_name() say:
It should contain only alphanumeric characters; it should be short and descriptive (i.e. for users with enabled cookie warnings). ... The session name can't consist of digits only, at least one letter must be present. Otherwise a new session id is generated every time.
So it's clear you must have something non-numeric in there, but it's not quite clear what characters you can't have. The cookie spec itself denies ()<>#,;:\"/[]?={}, but that still leaves others that might be permitted but are not strictly alphanumeric. This is important because cookie security prefixes use - and _ in names like __Secure-PHPSESSID. So I had a rummage in the PHP source code at the session_name function – but I can't see that it does anything other than check it's a string. In practice, it works fine, but I'd be more comfortable knowing precisely why! For example, this works:
session_name('__Secure-PHPSESSID');
session_start();
$_SESSION['test'] = $_SESSION['test'] . "\n" . rand(0,100);
var_dump($_SESSION);
So what are the actual limits on PHP session names?
I got a bit further with this. The rules for a session name are defined in this validation function, which permits [a-zA-Z0-9,-]{1,256} (but not numeric-only). You can have commas and dashes in session names in addition to alphanumerics, so the docs are wrong on that. This function is called from an internal session_create_id function, which triggers a warning if the session name doesn't pass that validation.
Despite this, no warning is triggered when passing in a session name containing _. This is demonstrable:
<?php
ini_set('display_errors', true);
error_reporting(E_ALL);
session_name('__Secure-MySession');
session_start();
if (!array_key_exists('test', $_SESSION)) {
$_SESSION['test'] = '';
}
$_SESSION['test'] .= "\n" . rand(0,100);
var_dump($_SESSION);
echo session_name();
This works perfectly, triggering no errors or warnings, and shows a growing list of random numbers (showing that the session storage is working and therefore the cookies are too), and the second session_name call with no params shows the session name that we set:
__Secure-MySession
And the HTTP headers show that the script sets a cookie called __Secure-MySession:
I also tried naming the session My_Session, just in case PHP looks for explicit __Session- prefix, but that works just fine too. Other characters like # or ( do not trigger an error either; in those cases the session name is URL-encoded, which looks remarkably like this bug that was fixed quite a while ago. As expected, 123, works, but also URL-encodes the comma.
So while this demonstrates that having _ in session names works fine, I can't tell you why. I've asked elsewhere too, and if I find out, I will update this question!
Coincidentally, draft 06 of RFC6265bis expires today.

PHP Warning: unpack(): Type n: not enough input - log entry

So I have found this error log
PHP Warning: unpack(): Type n: not enough input, need 2, have 0 in {{file}}{{line}}
the said file and line contains this code:
$answerHeader = unpack('ntype/nclass/Nttl/nlength', $answerHeaderBin);
This is part of a code which makes a query on a DNS.
My issue is that I don;t know how to debug this. If I try to dump the before mentioned header the information is correct. I tried unpack() with no arguments and it just returns false with no errors.
So I guess, my question is: In which situation would that warning appear in the error log?
Turns out that the warning appears when the input data is not correct, specifically if it expects more characters to unpack than the provided amount, in this case the unpack() function evaluates to false.

Validate session cookie value with regex

I'm trying to validate a session cookie value with regex, I have done some test, but without any success...could someone write and explain me how to match a non-ASCII character, spaces(\s), comma and semicolon (basically all the forbidden characters of a session value)? Obviously if one of those character is found the entire line is invalid.
At this moment my function is this:
session_name("RazorphynSupport");
if(isset($_COOKIE['RazorphynSupport']) && !is_string($_COOKIE['RazorphynSupport']) || !preg_match('/^[a-z0-9]{26,40}$/',$_COOKIE['RazorphynSupport'])){
setcookie(session_name(),'invalid',time()-3600);
//return error
exit();
}
session_start();
//Logout
if($_POST[$_SESSION['token']['act']]=='logout' && isset($_SESSION['status'])){
//Logout
}
//Session Check
if(isset($_SESSION['time']) && time()-$_SESSION['time']<=1800)
$_SESSION['time']=time();
else if(isset($_SESSION['id']) && !isset($_SESSION['time']) || isset($_SESSION['time']) && time()-$_SESSION['time']>1800){
//Destroy session; return error
exit();
}
else if(isset($_SESSION['ip']) && $_SESSION['ip']!=retrive_ip()){
//Destroy session; return error
exit();
}
else if(!isset($_POST[$_SESSION['token']['act']]) && !isset($_POST['act']) && $_POST['act']!='faq_rating' || $_POST['token']!=$_SESSION['token']['faq']){
//Destroy session; return error
exit();
}
Before the session_start(), but obviously it's wrong. My problem is that if the session contains any illegal character the session_start function return an error, so to prevent it I would like to check the "integrity" of the cookie
EDIT
form firebug cookie tab:
Cookie Name -> RazorphynSupport
Value -> ETpSx-T6VFuIYS3fejyaq0
I need to validate ETpSx-T6VFuIYS3fejyaq0 (that is a random generated string)
Instead of listing the things that you will deny, you should list the things that you will allow.
I am reminded of this TheDailyWTF story.
What do you expect in a session cookie? (By which I will assume you mean the value of a session cookie, which is a session id) Is it a hexadecimal string? How long? 32 bytes? 256? Something else entirely? Let's say your cookies are a 32 byte hexacimal string. Then the following regex will catch them:
/^[a-f0-9]{32}$/
Anything not matching that regex is known to be invalid.
However, there's a much more important step to take: you should check whether you actually have a session with that id that has not been expired yet.
I think you can use this pattern:
if ( preg_match('/^[^:; ]+$/i', 'ETpSx-T6VFuIYS3fejyaq0') ) {
// valid
}
this pattern allowed using digits, letters a-z in uppercase and lowercase, and also hyphen -.

Another Undefined Index (this one's defined though)

Alright, PHP is throwing this error (in the log) and it's quite detrimental to the application.
PHP Notice: Undefined index: sessid in methods.php on line 7
Yes, I'm aware of what this error means, what I cannot figure out is why it's obviously defined and saying it's undefined. Here is the relevant code in methods.php
$sessid = mysql_real_escape_string($_REQUEST['sessid']);
Now before you go off and say that "NO IT'S UNDEFINED!!!", here is the POST request to the methods.php (and yes, I'm also aware that $_REQUEST "can't be trusted").
method=r&room=general&sessid=d0sma94yw4r4cdckv2ufhb&qid=1276957562382
As you can see, the sessid is obviously defined and is being sent off to the methods.php. I just thought I'd throw in the relevant query here too.
mysql_query('UPDATE active SET time=\''.$timestamp.'\' WHERE sessid=\''.$sessid.'\'');
Yes, time is also defined as:
$time = time();
So, what is the issue here?
Barring typos etc, if you have a version >= 5.3.0, you might want to check what request_order (or variables_order if request_order is empty) ini-setting is set to. If in none of those two the 'P' is set, the $_POST array will not be in $_REQUEST (and not even set it the 'P' is not in variables_order afaik). See: http://www.php.net/manual/en/ini.core.php#ini.request-order
If those 2 are allright, I'd say you have a logical error somewhere else, var_dump() the $_POST and $_REQUEST superglobals to check.

Categories