php warning: strtolower() expects parameter 1 to be string, array given - php

I have a querystring variable whitelist checker on my website. It checks the given url against permissible/allowed querystring variables (key and value). The first part checks the querystring key against allowed keys - if the key is not allowed (not in the whitelist) it will reject the querystring. the second part checks the "value" part of the querystring key/value to see if the value includes a bad word from a blacklist - if the value is in the blacklist it will reject the querystring.
This seems to work well but I notice from my server logs that the line that converts the querystring value to lowercase causes a PHP warning:
PHP Warning: strtolower() expects parameter 1 to be string, array
given
This is the code:
$rejectqstr = "N";
//loop through the querystring, check each querystring KEY against the three whitelist arrays. Any key found which is not in the list will set the reject variable to Y
foreach ($_GET as $key => $value){
if(in_array($key, $cmsnumparams) || in_array($key, $cmsstrparams) || in_array($key, $cmsboolparams)){
//do nothing if it found
} else {
$rejectqstr = "Y";
}
//loop through the blacklist values and check each querystring value against the list
$value = strtolower($value);
foreach($cmsblklstparams as $blklist){
if(strpos($value, $blklist) !== false){
$rejectqstr = "Y";
}
}
}
its the line $value = strtolower($value); that is logged as a warning in the server logs.
I can't see what is wrong with this. My blacklist array (cmsblklstparams) is all lowercase so I convert the querystring value to lowercase before seeing if it is in the blacklist array.
This warning is not listed all the time in the server log so I guess it could be caused if a user tried to "inject" something in the querystring (changing it from a string to an array)?
Is there a better way of doing this or should I check if $value is an array (if so, reject the querystring) before converting it to lowercase?
I've tried testing the code with normal querystrings and it seems to work ok so I'm not sure what it being added to the querystring to cause this warning in the server logs.
Many thanks.

I guess it could be caused if a user tried to "inject" something in the querystring (changing it from a string to an array)?
I would agree, either intentionally or unintentionally. Take for example the following HTML form:
<form action="my-request.php" method="GET">
<label for="checkbox-setting1">
Setting 1
<input type="checkbox" id="checkbox-setting1" name="setting[]" value="setting1" />
</label>
<label for="checkbox-setting2">
Setting 2
<input type="checkbox" id="checkbox-setting2" name="setting[]" value="setting2" />
</label>
<label for="checkbox-setting3">
Setting 3
<input type="checkbox" id="checkbox-setting3" name="setting[]" value="setting3" />
</label>
<button type="submit">Search</button>
</form>
Assume this form is submitted with every checkbox checked, it will make a request using the URL .../my-request.php?setting[]=setting1&setting[]=setting2&setting[]=setting3
This is a valid GET request and PHP will interpret the setting query string parameter as an array. The way your current code is setup is that it always assumes that the incoming query string parameters will be a String.
It sounds like you want to do a type check to check if the currently iterated query string parameter is an array. If it is and you do not allow for arrays, then do your rejection. If it is and you do allow for arrays, then setup an inner loop:
// do not allow query string parameter values
if (is_array($value))
{
$rejectqstr = "Y";
}
// or allow them
foreach($cmsblklstparams as $blklist)
{
if (is_array($value))
{
foreach($value as $innerValue)
{
if(strpos($innerValue, $blklist) !== false)
{
$rejectqstr = "Y";
}
}
}
else if(strpos($value, $blklist) !== false)
{
$rejectqstr = "Y";
}
}

Related

Check if $_POST is empty

I realize this question has been asked multiple times, but none have resolved the issue for me.
I want to check if any values in the $_POST array are empty except for two (sHostFirstName and sHostLastName), and throw a 400 error if there is an empty value. The only way I can get it to throw the 400 error is by putting a die() command after the header command, however I get the 400 error no matter if there are empty values or not.
foreach ($_POST as $key => $value) {
if (empty($value) || !isset($value) && $key != 'sHostFirstName' || 'sHostLastName') {
header("HTTP/1.1 400 Bad Request");
break;
}
}
isset() will return true if the variable has been initialised. If you have a form field with its name value set to userName, when that form is submitted the value will always be "set", although there may not be any data in it.
Instead, trim() the string and test its length
if("" == trim($_POST['userName'])){
$username = 'Anonymous';
}
There's no need to loop through your POST data. Store your POST data in a variable $postData and remove the keys sHostFirstName and sHostLastName since these two need not be checked.
This $postData contains all key value pairs which need to be checked for blank entries. Now, use array_filter() to filter out any blank entries and store it in $filteredArray.
Finally, if there were no blank entries, the length of $postData will be same as ``$filteredArray`. If lengths don't match, that will imply one or more array keys had been filtered.
$postData = $_POST;
unset($postData['sHostFirstName'], $postData['sHostLastName']); // $postData contanis all key value pairs which can't be empty
$filteredArray = array_filter($postData); // Filters out the blank entries
/* If the length of `$filteredArray` and `$postData` aren't the same, that means one or more fields had been filtered out */
if (count($filteredArray) != count($postData)) {
header("HTTP/1.1 400 Bad Request");
}
Something like this
//store and set default if not set - only needed for checkbox or radio buttons. for example, text fields are always submitted as an empty string.
$sHostFirstName = isset($_POST['sHostFirstName']) ? $_POST['sHostFirstName'] : false;
unset($_POST['sHostFirstName']); //remove so it's not part of our count
//store and set default if not set - only needed for checkbox or radio buttons.
$sHostLastName = isset($_POST['sHostLastName']) ? $_POST['sHostLastName'] : false;
unset($_POST['sHostLastName']); //remove so it's not part of our count
$post = array_filter(array_map('trim', $_POST)); //copy post remove empty items.
if( count( $post ) != count($_POST)
//if count $_POST is not the same as count $post ( empty removed via array_filter) then something was removed / empty
header('HTTP/1.1 400 Bad Request', true, 400);
exit();
}
should do after unset is used on them. See,
store them in variables
remove them from post
then remove empty from a copy of post
count copy ( - empties ) and compare to count of original
if the count is different then something was removed ( ie. empty )
no need for a loop.
Also you can send the optional 2nd and 3rd params for header:
http://php.net/manual/en/function.header.php
2nd = replace - The optional replace parameter indicates whether the header should replace a previous similar header, or add a second header of the same type. By default it will replace, but if you pass in FALSE as the second argument you can force multiple headers of the same type.
3rd = http_response_code - Forces the HTTP response code to the specified value. Note that this parameter only has an effect if the string is not empty.

Disable "square bracket to array" parsing of form value names

As you know, if you have a form containing
<select name="user">
<option value="a">a</option>
<option value="b">b</option>
</select>
<input type="text" name="user[name]" />
<input type="password" name="user[password]" />
And you submit that form, in PHP, $_REQUEST['user'] will automatically become an array containing the keys name and password, and the value of the select will be gone. The opposite occurs if the <select> is moved after the other two fields. $_REQUEST['user'] will contain the value of the select, and the values in the inputs will be gone, with nothing being set to either $_REQUEST['user[name]'] or $_REQUEST['user[password]'].
To my knowledge, the same applies to $_POST, $_GET and $_FILES.
Also, input streams are unavailable when the form's MIME is multipart/form-data.
So, is there any way to disable this automatic parsing?
A. change
<select name="user">
to
<select name="user[user]">
That way you have a key set for the user array. If you are going to use post arrays you have to make sure every single one has a key.
B. (the way i'd do it)
Forget about the arrays. Use a unique name for each one. Using the arrays give you no benefit. Call the first one user, the second one, user_name, and the 3rd "password"
I wish there was a way to disable PHP's arrayification of query parameter names, but I don't think there is. What you can do (except with enctype="multipart/form-data") is parse the query yourself using something like:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$qs = file_get_contents ('php://input');
} else {
$qs = $_SERVER['QUERY_STRING'];
}
if ($qs !== '') {
$params = explode ('&', $qs);
foreach ($params as $p) {
$nv = explode ('=', $p, 2);
$name = urldecode ($nv[0]);
$value = urldecode ($nv[1]);
echo htmlspecialchars ($name), ': "', htmlspecialchars ($value), "\"\n";
}
}
This also gets around PHP's annoying habit of replacing "illegal" characters in query parameter names, such as dots with underscores.
There's no way to do it as of version 5.6 main development branch at 20 June 2014
No, it is not possible.
If you don't want form arrays, don't use form arrays. Call your inputs something else. It's quite simple.
And apparently you are not interested in workable, conventional and meaningful solutions to your problem.

Is it possible to get empty array value via $_GET?

Is it possible to get empty array(array with 0 items) value via $_GET?
Is it possible with direct value setting?
count($_GET['param'])==0
You just need an empty value url = myform.php?param=&param2=
In form just let the value blank:
<input type='text' name='param' value ='' />
For an empty array:
url: myform.php?param[]=&param2[some_key]=
in form: <input type='text' name='param[]' value ='' />
From Ajax: (I remember this was so anti-intuitive and hard to search for):
ajax{
...
data: {'params[]':'','params2[some_key]':''}
}
Workaround:
Just edit the back-end and if there is no data for params or it is not an array (null, empty whatever ..) just assign an empty string to it:
$param = (isset($_GET['param']) && is_array($_GET['param']))? $_GET['param'] : array();
Update:
I did few tests and it seems there is no way to put "nothing" in the request ussing a form or ajax.
0, '', Null are valid values for the $_GET but empty array is not even created.
So to answer your question, it is NOT possible to get empty array value from the front-end.
There are few options to edit $_GET manually in the back-end:
<?php
if(!isset($_GET['param']) || !$_GET['param']){ //not set or (null,0,"")
$_GET['param'] = array();
}
if(count($_GET['param'])==0){...}; // 0 if no 'param' was provided.
If array is empty or containing values that doesn't matters. Just declare a variable and pass the $_GET[] to the variable.
for example,
$para=$_GET['param'];
and now
if(is_array($para))
{
//
}
else{
$para=new array();
}
passing empty array via GET is not possible under normal situation. That said, I can think of a really rare way that the checking you used will return true.
http://domain.com/receiver?param=a%3A0%3A%7B%7D
The above is basically a serialized and urlencoded empty array with the key 'param'
if the target server have some sort of filter that auto unserialize all incoming data, then it might happen.
(or something like the below)
foreach($_GET as $key => $value){
$_GET[$key] = unserialize($value);
}
count($_GET['param'])==0
I know this is a far fetch but it is possible, maybe some private test server that only handles serialized data but accidentally open to public e.t.c.
That said, it is still only passing a serialized empty array instead of a empty array itself. But let's be honest, this answer is more like a joke/fun answer that tries to point out under some very rare case
count($_GET['param'])==0
will return true (Without actively assigning values # server side)

PHP some $_POST values missing but are present in php://input

I have a very big html form (containing table with rows, which contain multiple inputs), which i need to submit to PHP script via POST request.
The problem is some values don't come through and are absent in PHP's $_POST superglobal.
I checked (using Firebug extension) that the values are actually sent to server by the browser.
$_POST gets populated, but some values are just missing.
I checked what is raw request using:
$raw_post = file_get_contents('php://input');
and the string returned has the values. They are just not parsed into $_POST array. The strange thing i noticed is, it seems that the php://input values are cut after some length, and rest of the string does not come through to $_POST.
I thought about post_max_size and memory_limit and set them to large values:
memory_limit = 256M
post_max_size = 150M
but according to php documentation $_POST should not contain any values if request made is bigger than post_max_size.
Due to big size of form and request I cannot post it here, but i can post php script i used to debug the problem:
var_dump($file = file_get_contents('php://input'));
var_dump($_POST);
//... then i parsed the $file
Server version: Apache/2.2.9 (Debian)
PHP version: PHP 5.3.2-0.dotdeb.2
Can enyone explain reason of such strange PHP behaviour, and what should i do (change php settings, code?) to use $_POST array while processing form?
EDIT: To be clear: not only the values are missing. $_POST does not contain these keys either.
e.x. fragment of raw post:
t_dodparam%5B198%5D=&t_dodparam2%5B198%5D=&t_kolejnosc%5B198%5D=199&n_indeks=201&n_wartosc=testtesttest
Key 't_dodparam' is in post and it has key 198. The rest of parameters are missing (e.x. t_dodparam2 is in post, but it has no such key as 198, and there is no such key as n_wartosc in $_POST)
PHP modifies fields containing the characters space, dot, open square bracket and others to be compatible with with the deprecated register_globals
you can find a lot of workarounds in the comments here:
PHP: Variables From External Sources
For Exampe (comment by POSTer):
<?php
//Function to fix up PHP's messing up POST input containing dots, etc.
function getRealPOST() {
$pairs = explode("&", file_get_contents("php://input"));
$vars = array();
foreach ($pairs as $pair) {
$nv = explode("=", $pair);
$name = urldecode($nv[0]);
$value = urldecode($nv[1]);
$vars[$name] = $value;
}
return $vars;
}
?>
I just fixed this issue by adding a value to max_input_vars in my PHP configuration file. According to this. it was introduced in 5.3.9, yet after some package upgrades I experienced the issue in 5.3.2.
The default for max_input_vars is 1000, which was too small for my form.
There are many different things that could be causing this. Best to check your error log. Many of the things that cause this symptom will put messages in the error log, but not display errors in your PHP application.
Some of the possible causes:
Suhosin
Suhosin is an extension for PHP designed to protect servers and users from known and unknown flaws in PHP applications and the PHP core. One of the things that it does is limit the size of $_POST, $_GET, $_REQUEST, and $_COOKIE. If your problem is Suhosin you will get an error in your log such as
ALERT - configured POST variable limit exceeded - dropped variable 'foo'
The solution is simple, just increase the maximum number of allowed variables in php.ini. If you don’t have a suhosin section, just create one. Like such:
[suhosin]
suhosin.request.max_vars = 1000 # Default is 200
suhosin.post.max_vars = 1000 # Default is 200
There are other suhosin settings that can cause this, but these are the most likely candidates.
Invalid form field names
Back in the old days PHP had a setting called register_globals (now depricated) that automatically converted GET and POST variables into PHP variables. So if your form had fields 'foo' and 'bar', when that form is submitted the variables $foo and $bar would be automatically created. However there are several characters that are not valid for use in PHP variable names (space, dot, open square bracket and others). Depending on what characters you used, the variable may have the invalid characters stripped, be missing its value, or be unset. Despite the fact that register_globals is no longer used, PHP still strips these characters when building $_POST, $_GET, $_REQUEST, and $_COOKIE. If you are unable to fix the values of the form fields you might try something like:
<?php
/**
* Converts raw POST data into an array.
*
* Does not work for hierarchical POST data.
*
* #return array
*/
function real_post() {
static $post;
if (!isset($post)) {
$pairs = explode("&", file_get_contents("php://input"));
$post = array();
foreach ($pairs as $pair) {
$x = explode("=", $pair);
$post[rawurldecode($x[0])] = rawurldecode($x[1]);
}
}
return $post;
}
?>
What about using "parse_str" to convert the query string into php structures? This funcion is the inverse of http_build_query.
$b = array();
parse_str(file_get_contents("php://input"), $b);
I am posting Jquery's .ajax() function. I was trying to retrieve my data from $_POST but it was incomplete. Then I found this post which put me on the right track. However, the getRealPOST() method described above wasn't working for me - it can't handle multi-dimentional and nested arrays very well. Instead I used PHP's parse_str() method which did the trick and was a bit cleaner:
$rawdata = file_get_contents('php://input');
$urldecoded = urldecode($rawdata);
parse_str($urldecoded, $parsed);
$data = $parsed['data'];
(In my JS I'm posting an object where the payload is in the data property. Yours would be likely be different.)
I also went into my php.ini and cranked up max_memory and max_input_vars, but it didn't seem to solve my issue. I also spent a while chasing a red herring because I was using my error log to print the raw data, and I forgot that error_log has a limit on how many characters it will print unless you remember to increase it.
Anyway, hope this helps to anyone who finds themselves battling this issue.
use this function for getting true data
function get_real_post() {
function set_nested_value(&$arr, &$keys, &$value) {
$key = array_shift($keys);
if (count($keys)) {
// Got deeper to go
if (!array_key_exists($key, $arr)) {
// Make sure we can get deeper if we've not hit this key before
$arr[$key] = array();
} elseif (!is_array($arr[$key])) {
// This should never be relevant for well formed input data
throw new Exception("Setting a value and an array with the same key: $key");
}
set_nested_value($arr[$key], $keys, $value);
} elseif (empty($key)) {
// Setting an Array
$arr[] = $value;
} else {
// Setting an Object
$arr[$key] = $value;
}
}
$input = array();
$parts = array();
$rawdata=file_get_contents("php://input");
$rawdata=urldecode($rawdata);
$pairs = explode("&", $rawdata);
foreach ($pairs as $pair) {
$key_value = explode("=", $pair, 2);
preg_match_all("/([a-zA-Z0-9_]*)(?:\[([^\[\]]*(?:(?R)[^\[\]]*)*)\])?/", urldecode($key_value[0]), $parts);
$keys = array($parts[1][0]);
if (isset($parts[2][0])&&$parts[2][0]!="") {
array_pop($parts[2]); // Remove the blank one on the end
$keys = array_merge($keys, $parts[2]);
}
$value = urldecode($key_value[1]);
if ($value == "true") {
$value = true;
} else if ($value == "false") {
$value = false;
} else if (is_numeric($value)) {
if (strpos($value, ".") !== false) {
$num = floatval($value);
} else {
$num = intval($value);
}
if (strval($num) === $value) {
$value = $num;
}
}
set_nested_value($input, $keys, $value);
}
return $input;
}
I found this answer via a search and feel I should offer an alternative issue. In my case, my post wasn't too large, and yet the value I was submitting in the field was not showing up in the $_POST array. As it turned out, I accidentally had another field further down in my form with the same name. So:
<form>
<input type="text" name="field1">
<input type="text" name="field2">
<input type="text" name="field3">
<input type="text" name="field1">
<input type="submit">
</form>
When the $_POST variable is populate with data from that form, the value in your first field will be overwritten with the value in that last field with the same name. If the first field is required and you fill in a value, but the last field is not required and gets submitted empty, you will see similar symptoms as this question because the value $_POST['field1'] will show the value of the last element in you form which is empty.
TLDR:
Make sure to check for duplicate field names in your form!
For future reference:
On an Ubuntu box, I've been struggling with this issue since a relatively long time and used to adopt a workaround similar as described above to save the day.
I now tracked the issue down in my php.ini and finally found it in line with
max_input_nesting_level = 0
I commented the line above, restarted apache and all's fixed.
I had this same issue and it turned out that I was using AJAX to dynamically modify the input field based on other inputs in the form. The ajax function re-created the input and failed to include an input name.
Came across this (admittedly old) post while trying to find a fix for the bug of Javascript Object keys including square brackets in their names and then PHP getting confused and acting like it's never even heard of nesting. #Dalin has a good basic response, but it doesn't make a nested array, nor does it convert value types to boolean / number - hence my version (get_real_post() is also on GitHub).
Despite the name, this should work identically for _GET (ie, anything that php://input grabs).
/**
* Gets the _POST data with correct handling of nested brackets:
* "path[to][data[nested]]=value"
* "path"
* -> "to"
* -> "data[nested]" = value
* #return array
*/
function get_real_post() {
function set_nested_value(&$arr, &$keys, &$value) {
$key = array_shift($keys);
if (count($keys)) {
// Got deeper to go
if (!array_key_exists($key, $arr)) {
// Make sure we can get deeper if we've not hit this key before
$arr[$key] = array();
} elseif (!is_array($arr[$key])) {
// This should never be relevant for well formed input data
throw new Exception("Setting a value and an array with the same key: $key");
}
set_nested_value($arr[$key], $keys, $value);
} elseif (empty($key)) {
// Setting an Array
$arr[] = $value;
} else {
// Setting an Object
$arr[$key] = $value;
}
}
$input = array();
$parts = array();
$pairs = explode("&", file_get_contents("php://input"));
foreach ($pairs as $pair) {
$key_value = explode("=", $pair, 2);
preg_match_all("/([a-zA-Z0-9]*)(?:\[([^\[\]]*(?:(?R)[^\[\]]*)*)\])?/", urldecode($key_value[0]), $parts);
$keys = array($parts[1][0]);
if (!empty($parts[2][0])) {
array_pop($parts[2]); // Remove the blank one on the end
$keys = array_merge($keys, $parts[2]);
}
$value = urldecode($key_value[1]);
if ($value == "true") {
$value = true;
} else if ($value == "false") {
$value = false;
} else if (is_numeric($value)) {
if (strpos($value, ".") !== false) {
$num = floatval($value);
} else {
$num = intval($value);
}
if (strval($num) === $value) {
$value = $num;
}
}
set_nested_value($input, $keys, $value);
}
return $input;
}
I faced the same problem. My form was creating the input elements in a loop. Upto 1000 input elements were getting posted and print_r($_POST); showed all the posted values.
When the input elements in the form numbered above 1000, print_r($_POST) just vanished. As if the form was not getting posted at all. I reached your page of stackoverflow.com by google search.
Yes increasing post_max_size, memory_limit etc did not solve anything. I have practical experience that increasing memory_limit beyond 128M can be dangerous too. Once our live Apache hosting server got hung up. I was the experimenter :-)
max_input_vars was the only limiting factor.
echo ini_get('max_input_vars');
showed that it was 1000 by default.
Strangely max_input_vars was absent in the php.ini.
Anyway I added
max_input_vars=10000
in my php.ini and restarted Apache.
Now echo ini_get('max_input_vars'); showed that it had increased to 10000 and my large form data could be trapped after posting. Problem solved.
I see that the max_input_vars is PHP_INI_PERDIR type. Which means I cannot change its value for the individual php page where I need a higher value using ini_set('max_input_vars', 10000);
I had a similar issue and modifying the MAX_INPUT_VARS fixed it.
It's worth checking what you send, and what is received. In my case, the AJAX call contained a formatted array of all the values but the VAR_DUMP($this->input->post()); revealed missing values.
So, the obvious answer was as someone here said - max_input_vars.
You need to change the max_input_vars value of the php.ini file
step 1: Locate the php.ini file
step 1(a): Print php info values to find out all the PHP predefined configurations.
echo phpinfo(); //will print the php info values
step 1(b): Make a note of the php.ini file location from the phpinfo values printed in the previous step as shown in the image below
step 2: Edit the php.ini file.
step 2(a): Find the variable max_input_vars and remove the semicolon before it and set the value to 10000 like below
max_input_vars = 10000
step 3: Restart apache
sudo service apache2 restart
I faced same problem and i found a very valuable solution without increasing any limit or size in .ini.
We know that max_input_vars = 1000 by default and this is prefect.
Just do one thing concatenate array values and try to precise your number of variables under 1000.
Each variable name count one not their values.
For example:
<code>
$i = 10; //counts 1
$i = array(1,2,3,4,5); //counts 5
</code>
You can do this:
<code>
$i = 1_2_3_4_5; // counts 1
</code>
Hope understanding.

Evaluate a dynamic array key

I have a form that allows the user to add information an their leisure. They can add locations via jQuery in my form so when recieving the data I may have 1 location or 10. Each location has attributes like phone, address, etc. In my form the input names are appended with _1 , _2, etc to show its a new set of data. That is working swimmingly and I just can't seem to find these keys when looping through the $_POST array
private function array_pluck($arr,$text)
{
foreach($arr as $key => $item)
{
if(stripos($key,$text) != 0)
{
$found[] = $item;
}
}
return $found;
}
As I understand it if my array has some keys "office_branch_phone_1, office_branch_phone_2" I should be able to put in "office_branch" in my $text param and it will spit out any keys with the "office_branch" in the name. This isn't working however and I'm a bit stumped.
Since stripos will return the index (and it is a 0-based index returned) != 0 is incorrect.
if (stripos($key,$text) !== false)
Would be the correct way to check it. Give that a shot.
EDIT
Note the use of !== instead of != since 0 tends to be considered false if loosely checked the !== will check the actual type, so 0 is a valid return. Just an extra tidbit of information

Categories