'PHP thinks my string is also an associative array. Thinks array is string. Trying to extract a CSRF Token from it

I'm currently rewriting a CSRF Token verification function for a client. They have this odd system where ALL of the session information for a user is saved to an sql database field as a string.

$D = {'email' => '[email protected]','userid' => 69,'uid' => 69,'scanstatus' => 'S','CSRFToken' => 'tokenGENexample==','_SESSION_ATIME' => 1643289149,'abbr' => 'TheDude','scantype' => 0,'_SESSION_REMOTE_ADDR' => 'IP Address','_SESSION_CTIME' => 1643282918,'scanexpiration' => 1643289447,'_SESSION_ETIME' => 1800,'_SESSION_ID' => 'sessionGENexample','scanmode' => 100};;$D

It is stored exactly how you I pasted it.

Then, this verification function uses str_replace to turn it into a line of code that assigns $D to an array:

$D = array('email' => '[email protected]','userid' => 69,'uid' => 69,'scanstatus' => 'S','CSRFToken' => 'tokenGENexample==','_SESSION_ATIME' => 1643289149,'abbr' => 'TheDude','scantype' => 0,'_SESSION_REMOTE_ADDR' => 'IP Address','_SESSION_CTIME' => 1643282918,'scanexpiration' => 1643289447,'_SESSION_ETIME' => 1800,'_SESSION_ID' => 'sessionGENexample','scanmode' => 100);

It then runs this in an eval() function, and needless to say, is a security risk. I don't even feel like its wise to hold that much sensitive information in a single field, but I digress.

$D is then passed back to our main CSFR Token function, it checks for the data associated with 'CSRFToken' and goes along its merry way.

My idea to removed the eval() was this, and please let me know if this is a terrible idea: Take the string, parse it for JUST the tokenGENexample==, assign that string directly to $D, and then just rewrite our main CSRF function to look for a value, rather than the associative array value.

To do so, I was exploring a few sub ideas, and that's where I ran into this weird PHP error that I'm asking about right now. Sorry for taking a while to get to the point, but I needed assistance with the parser too, and figured I'd add context.

I decided to use parse_str() BEFORE str_replace() is called, to see if it would spit out all of the values to an output.

My log spit this out: PHP Notice: Array to string conversion...

Well how the heck is it being read as an array, if its clearly still just a string?

Alright, if it wants to be an array, I figured I'd use array_values() to spit me out my values I need.

Welp, then it spits out this warning PHP Warning: array_values() expects parameter 1 to be array...

Now I'm at a loss. It thinks a string is in array, and an array is a string. I think it has to do with it being an associative array.

I went down this huge rabbit hole, simply because the client has some old unsafe code that would run an eval() function to make $D equal that array.

Kind fellows of Stackoverflow, I'm at a darn loss here. Does PHP have built in functions I can use to JUST get the CSRF token out of this string (or convert it to an array?) and assign it $D without that goofy eval() call?



Solution 1:[1]

Why oh why would one serialize with var_export() and then mangle it up a bit? And then back-mangle and use eval() to get the data back? I'd recommend batch-converting the lot into JSON, or serialize(), or any other format that can be unserialized without eval(). If you stored it as JSON, you could also query its values if you're running a modern MySQL version.

Now the serialization you have there isn't too far from valid JSON as it is, and can be regexed to conform... I emphasize, this is not best or even good practice!. However if it's for a one-off job, woe is the things we do to get the job done, and moving onward. Here's a "parser":

$str = <<<'DUH'
{'email' => '[email protected]','userid' => 69,'uid' => 69,'scanstatus' => 'S','CSRFToken' => 'tokenGENexample==','_SESSION_ATIME' => 1643289149,'abbr' => 'TheDude','scantype' => 0,'_SESSION_REMOTE_ADDR' => 'IP Address','_SESSION_CTIME' => 1643282918,'scanexpiration' => 1643289447,'_SESSION_ETIME' => 1800,'_SESSION_ID' => 'sessionGENexample','scanmode' => 100};;$D
DUH;

// Fix ' -quoted keys and values:
$str = preg_replace("~'([^']+)'~", '"\1"', $str);

// Convert => to :
$str = str_replace(' => ', ':', $str);

// Trim the tail:
$str = preg_replace('~;;.+$~', '', $str);

$data = json_decode($str, true);

That could probably be more elegant. This results in:

array(14) {
    ["email"] · string(19) "[email protected]"
    ["userid"] · int(69)
    ["uid"] · int(69)
    ["scanstatus"] · string(1) "S"
    ["CSRFToken"] · string(17) "tokenGENexample=="
    ["_SESSION_ATIME"] · int(1643289149)
    ["abbr"] · string(7) "TheDude"
    ["scantype"] · int(0)
    ["_SESSION_REMOTE_ADDR"] · string(10) "IP Address"
    ["_SESSION_CTIME"] · int(1643282918)
    ["scanexpiration"] · int(1643289447)
    ["_SESSION_ETIME"] · int(1800)
    ["_SESSION_ID"] · string(17) "sessionGENexample"
    ["scanmode"] · int(100)
}

Which is a valid PHP array without resorting to evil(). N.B. This will break if you have escaped \' inside keys/values. Need a negative lookbehind for \ before ' if that's the case. If you have 'abbr' => 'O\'Dude', for example. At any rate, it's a good idea to fetch all of the serialized sessions and test to see whether everything parses gracefully.

Good practice: Don't throw this in as a permanent feature. Use it to convert the serialization into coherent data and store it in a more sensible format.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1