I've search different forms but didn't understand how to solve the problem first found here (first link, second link) but its giving me the error in eval. I can't figure it out how to solve in that code in foreach loop.
foreach($_POST as $key => $value) {
if(!strstr($key, 'removeFile')){
//initialize variables using eval
eval("$" . $key . " = '" . sanitize($value) . "';");
}
}
First, the issues I have with your code:
eval is very, very rarely needed, and extremely dangerous, use with caution. I've been developing in PHP for over 10 years, and never really encountered a situation that needed eval. This is no exception. Eval is not required
You're sanitizing the entire $_POST array. That's great, but there are special functions for doing that, like: filter_input_array, array_filter and many, many more... not to mention ready-made, open source projects and frameworks that already contain a solid request validation component.
Always check the return values of functions, which you seem to be doing with strstr, but be weary of functions that return different types (like strstr: it returns false if the needle isn't found, but returns 0 if the needle is found at the start of the haystack string). Your if statement might not work as intended.
You're assuming sanitize($value) values will not contain any single quotes. Why? Because if they do, you'll end up with a syntax error in your evaled string
Be that as it may, you could easily write your code using variable variables and add a simple check not to step on existing variables in scope:
$sanitized = array_filter($_POST, 'sanitize');//call sanitize on all values
foreach ($sanitized as $key => $value)
{
if (!isset($$key) && strstr($key, 'removeFile') === false)
$$key = $value;
}
But really, $_POST values belong together, they are part of the request, and should remain grouped... either in an array, or in an object of some sort. Don't assign each value to its own variable, because pretty soon you'll loose track of what variables are set and which are not. Using an unset variable creates that variable, assigning value null, so what you have now makes for very error-prone code:
//request 1: POST => id=123&foo=bar
foreach ($sanitized as $k => $v)
$$k = $v;
$query = 'SELECT x, y, z FROM tbl WHERE id = ?';//using posted ID as value
$stmt = $db->prepare($query);
$stmt->execute(array($id));
All is well, because $id was set, but never trust the network, don't assume that, just because $_POST is set, all the keys will be set, and their values will be correct:
//request 2: POST => foo=bar&page=2
foreach ($sanitized as $k => $v)
$$k = $v;
$query = 'SELECT x, y, z FROM tbl WHERE id = ?';//using posted ID as value
$stmt = $db->prepare($query);
$stmt->execute(array($id));//id is null
Now we have a problem. This is just one example of how your code might cause issues. Imagine the script grows a bit, and look at this:
//request 3: POST => id=123&foo=bar&page=2
foreach ($sanitized as $k => $v)
$$k = $v;
//$id is 123, $foo is bar and $page = 2
$query = 'SELECT x, y, z FROM tbl WHERE id = ? LIMIT 10';//using posted ID as value
//a lot more code containing this statement:
$page = someFunc();
$log->write('someFunc returned log: '.$page);
//more code
$offset = 10*($page-1);//<-- page is not what we expected it to be
$query .= sprintf(' OFFSET %d', $offset);
$stmt = $db->prepare($query);
$stmt->execute(array($id));
Now this may seem far-fetched, and idiotic, but believe me: all of these things happen, more than I care to know. Adding some code that accidentally overwrites an existing variable that is used further down happens all the time. Especially in procedural code. Don't just blindly unpack an array. Keep that single variable, and use the keys to avoid:
grey hair
sudden, dramatic baldness
loss of sanity
bleeding ulcers
In a work environment: catastrophic loss of data
Sudden loss of job
... because code like this makes unicorns cry, and bronies will hunt you down
As the first answer to the post you linked to, the problem is that when using double quotes PHP thinks your eval() code starts with a variable. As that is not the case you have two options. Use single quotes and remember to escape the single quotes declaring a string in the code or escape the dollar sign.
Bonus note
There exist more elegant solutions to the problem you are trying to solve. The best solution I can think of is using the extract function. This gives to two main benefits.
It works with all associative arrays
You can specify different flags that can help you distinguish the extracted variables apart and avoid variable injection.
One flag you can use is EXTR_PREFIX_ALL. This will prefix all the extracted variables with your own prefix. You would then access the variables with a prefix of 'PREFIX_' like the following:
$array = [
'variable1' => 'foo',
'variable2' => 'bar'
];
extract($array, EXTR_PREFIX_ALL, 'PREFIX_');
$value1 = $PREFIX_variable1; // Equals: foo
$value2 = $PREFIX_variable2; // Equals: bar
A little on code injection
Suppose you have some code:
$login = false;
The $login variable determines if a user is logged in or not.
Then somewhere you use the ´extract´ function with an array of the following without using any flags.
$array = [
'login' => true,
'foo' => 'bar'
];
extract($array);
Now your $login variable would be set to true and the user posting the data would have overwritten the initial setting and gained access to your website without a valid login. Bear in mind this is a over simplified example, but nonetheless valid.
To overcome this you can use the flag EXTR_SKIP or prefix them like I previously showed. The EXTR_SKIP flag will skip the array element if a variable with the same name already is defined. So now your code would not overwrite your $login variable.
extract($array, EXTR_SKIP); // Skip existing variables
// Or
extract($array, EXTR_PREFIX_ALL, 'prefix'); // Prefix them all.
Hope this can guide to the right choice for your needs.
Regards.
Related
First, I'm still fairly new to PHP, so please forgive my lack of understanding. I'm working on a form that takes in options from multiple selects, and provides the user results based on these options. This form also stores these search queries so that they can be exported later on.
Initially, the form only had four parameters needing to be stored, but now I've added a fifth which is making things not so cooperative. I've searched, and tried multiple different things but I've hit a wall and am seeking assistance.
What is intended to happen is the user selects one, or all, of the available options. The first four selects are simple Yes/No questions with values of 1 for Yes, 0 for No. The fifth is a series of county names with the values being set to their id in the database. The county select options are populated dynamically via Smarty.
Here's the PHP used to store these values.
public function recordFilter($args)
{
$db = DBManager::getConnection();
$_POST['type'] = 'f'; // type of search, find providers
foreach($args as $key => $a)
{
if(isset($_GET['county']) && is_numeric($_GET['county'])
{
$_POST[$key] = $a ? (int)$_GET['county'] : 0; // store value of last parameter, or 0 if not set
}
$_POST[$key] = $a ? "y" : "n"; // store 'y' if $a is set, 'n' if not
var_dump($_POST[$key]);
}
parent::save();
}
Currently what is happening is I'm able to get all the values into this function, and iterate through them. But since I've introduced this fifth field (and through the different approaches I've tried to piece this together) is either my fifth parameter gets set to 'y', which won't store in the database as it's field is an int(2), or the set values of the first four parameters take on the value of the fifth parameter, and wind up having the id associated with the county in their fields.
What I'm looking to learn is what better approach is there to handle this type of problem? I thought perhaps a while loop would be appropriate to iterate through the first four parameters and handle the fifth after those are complete, but figuring out the syntax for that is a bit beyond me. I also tried a switch statement but that simply didn't work. Having the if statement seems to be the big wrench in the situation, as it throws the whole loop off if 'county' is set. Any assistance will be greatly appreciated.
Since writing code doesn't look properly on comment, I post my guess here. If you describe more of your code, such as what $args is, or how you handle the request, it'll help people to understand your problem.
Treat the last request separately
Since it's a $_GET request, you can't iterate it with $_POST
foreach ($args as $key => $a) {
$_POST[$key] = $a ? "y" : "n"; // store 'y' if $a is set, 'n' if not
}
if (isset($_GET['county']) && is_numeric($_GET['county']) {
$_POST['county'] = $a ? (int)$_GET['county'] : 0; // store value of last parameter, or 0 if not set
}
And the second, I think this is better approach
Don't rely on super global variable
Assign them to another variable and don't forget to refactor the code on parent::save() method
public function recordFilter($args)
{
$db = DBManager::getConnection();
$request = [];
foreach ($args as $key => $a) {
//Sounds like you forgot to check if the $_POST[$key] is set
$request[$key] = isset($_POST[$key], $a) ? "y" : "n";
}
//You don't have to assign a value to super global $_POST with key of type
$request['type'] = 'f';
//Or you may need if statement if this field doesn't a required field
$request['county'] = (int) $_GET['county'];
parent::save($request);
}
And somewhere in your parent class
protected function save(array $request)
{
//this is your bussiness
}
Since I'm just guessing on what $args is, this is better than the better one
Assign it to local variable with filter_input() and filter_input_array()
public function recordFilter($args)
{
$db = DBManager::getConnection();
//This array will be used for validating the input $_POST.
//First, I grab the key of $args then assign the value
//to $validate with the same key to return it as bool
$validate = [];
foreach(array_keys($args) as $key) {
$validate[$key] = FILTER_VALIDATE_BOOLEAN;
}
//Get the post request value
$request = filter_input_array(INPUT_POST, $validate);
//You don't have to assign a value to super global $_POST with key of type
$request['type'] = 'f';
//Receive 'county' only if it's int and greater than 1 and lower than 99
$options = ['options' => ['min_range' => 1, 'max_range' => 99]];
$request['county'] = filter_input(INPUT_GET, 'county', FILTER_VALIDATE_INT, $options);
parent::save($request);
}
Again, the parent::save() method needs to be refactored.
First of all I'm very sorry for my irrelevant question title, as I did not know how to write it better.
To start with, I am a PHP beginner. I am solving some PHP exercise and I came upon a question I don't know where to start with:
function q3() {
// I am supposed to write stuff here and not change anything to get the question right.
}
function a3($admin = false) {
assertion_group("Question 3");
foreach ($GLOBALS as $k => $v) $$k = $v;
if ($admin) {
$file = q3("edsi.pem");
}
$key = #file_get_contents($file);
$key = substr($key, 0, 4);
assert($key == substr(file_get_contents(__FILE__), 0, 4));
return $key;
}
First of all, I understand what $GLOBALS does, but why assign the $$k to $v (so the $k value to the $v value)? And does $GLOBALS get the values within functions?
How can I set $admin = true? I believe through q3(), but I don't see how...
Next thing that confuses me most is: $file = q3("edsi.pem"). As my q3 function doesn't have any arguments, and that I'm not supposed to add one, how can I use that?!
Thank you all very much in advance for your answer. My apologies again for the very vague question...
EDIT:
With the help of #mario to better understand this whole mess, basically what I had to put in q3 was:
if ($info == 'edsi.pem') {
$info = __FILE__;
return $info;
}
plus add an argument for q3 (q3($info)) and add ?admin=true in the header...
Many thanks again!
First of all, I understand what $GLOBALS does, but why assign the $$k to $v (so the $k value to the $v value)? And does $GLOBALS get the values within functions?
What that foreach … $$k = $v; snippet does is basically extract($GLOBALS);
This isn't a very useful way to pass arguments around. Using global vars only makes sense if they have somewhat descriptive names, and if they're not misused as state flags between different code sections.
And no, globals aren't available in all functions right away. Read up on variable scope.
How can I set $admin = true? I believe through q3(), but I don't see how...
You are confusing the function names here (precisely because they aren't rather useful function names to begin with). You can pass the $admin parameter when calling a3() instead:
a3(true);
Next thing that confuses me most is: $file = q3("edsi.pem"). As my q3 function doesn't have any arguments, and that I'm not supposed to add one, how can I use that?!
The only way to get the argument in q3 is per func_get_arg().
And again really, unless this is an exercise about how not to write code or a tutorial about weird use cases, you shouldn't really bother further.
I will be reusing a Drupal db_query result set unpacking function many, many times in my code for a variety of different queries - I am using O-O and as such I want to reuse it and be as 'DRY' as possible.
Therefore I have tried to strip it down to the most generic functions so that as long as the $columns supplied match the columns used in the query and similarly in the $resultset, I can loop and assign values to keys, as is shown, and return a $rows[].
I've not yet come across the issue of trying to use a variable's value as a variable name (the $key), if it's just something I should avoid entirely, please say.
foreach($this->resultSet as $aRecord) {
$c = 0;
while (isset($this->columns[$c])) {
$value = $this->columns[$c];
$rows[$i] = array(
$key[$this->columns[$c]] => $aRecord->$value,
);
$c++;
}
$i++;
}
I've read through the following and am beginning to think this is just knowledge I'm missing in my PHP experience so far.
Can I use a generated variable name in PHP?
PHP use function return value as array
https://wiki.php.net/rfc/functionarraydereferencing
It felt wrong, and someone once told me that if you have to start writing complex functions in PHP you've probably missed an available function PHP already offers.. so true... thanks to (at the time of writing...) 'MrCode' for this suggestion.
$this->sql = "SELECT foo, bar FROM foobar";
$this->result = db_query($this->sql);
if ($this->result->rowCount() > 0) {
while ($row = $this->result->fetchAssoc()) {
$this->resultArray[] = $row;
}
}
I was checking my script for vulnerabilities and was shocked the way i used to do in the past which is extremely insecure:
foreach ($_GET as $key => $value){
$$key = $value;
}
or shorter
extract( $_GET );
I altered with firebug some POST/GET variables to match a name i used in my script. they can be overwritten if the name would be guessed correctly.
So i thought i had to do it individually naming like this:
$allowed_vars =
$allowed_vars = array("time","hotfile","netload","megaupload","user","pfda","xyz","sara","amount_needed");
foreach ($_GET as $key => $value)
{
if (in_array($key,$allowed_vars))
{
$$key = $value;
}
}
This way saves some time than naming them individually.
What kind of automation have to be used for this?
I don't use any automatism of the kind.
I see no point in assigning request variables to global variables automatically.
If it's one or two variables, I could deal with them manually.
If there are more, I'd rather keep them as array members for the convenient handling.
Yet I am using some sort of whitelisting approach similar to yours.
but not to create global variables out of POST data but to add that data into SQL query.
Like in this simple helper function to produce SET statement:
function dbSet($fields) {
$set='';
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$set.="`$field`='".mysql_real_escape_string($_POST[$field])."', ";
}
}
return substr($set, 0, -2);
}
$id = intval($_POST['id']);
$fields = explode(" ","name surname lastname address zip fax phone");
$query = "UPDATE $table SET ".dbSet($fields)." stamp=NOW() WHERE id=$id";
You can save even more time by not extrating them at all. Just use them from the $_GET array. The advantages of this are not just avoiding collision with script variables (or worse) but also that you don't have to update that "automatism" when you add request parameters.
When I am working with POST data, as from a form, I often process each explicitly:
$data = array();
$data['field1'] = someSaniFunction($_POST['field1']);
$data['field2'] = someOtherFunction($_POST['field2']);
...
In this way I ensure that each field is properly handled, and only the fields I expect are touched.
In my experience, you shouldn't transform data in the $_REQUEST array into variables using $$ as it gives the possibility of overwriting variables held within the current scope.
Instead, you should consider having a request object or array in which you filter the data and only access named variables that you require. This way, you do not have to keep extending your allowed variable names and still maintain security.
The ZF for example has a request object, and they recommend using a input filter when working on this data:
http://framework.zend.com/manual/en/zend.filter.input.html
You can use the extract function in a more secure way:
extract($_REQUEST, EXTR_SKIP);
This will not overwrite variables that already exist in your code. See here for other parameters you can use
I want to create 1 variable name, but part of the name is the value stored in $i. Same for the GET result:
$Site.$i = $_GET['site'.$i]; // Should look something like $Site1 = $GET['site1'];
Please help me understand how to do this.
If you want a set of related variables, use an array:
$site[ $i ] = $_GET['site'.$i];
Even better, your GET parameters can also be an array
HTML
<input name="site[foo]" value="bar" />
PHP
$site = $_GET[ "site" ];
print_r( $site );
output
$site = array(
"foo" => "bar"
)
If you want the indexes for the array to decided automatically then you can do
<input name="site[]" value="foo" />
<input name="site[]" value="bar" />
<input name="site[]" value="baz" />
and get $_GET[ "site" ] out as
$site = array(
0 => "foo",
1 => "bar",
2 => "baz"
);
Direct Answer to Question
This is how you can do it. Not the best idea however.
$var = "$Site$i";
$$var = $_GET['site'.$i];
This makes use of variable variables.
Alternative Maintaining Current URL Structure
Alternatively perhaps something like this might work for you:
$vars = array();
foreach($_GET as $key => $value) {
if(0 === strpos($key, 'site')) { // Only grab value if the key is prefaced by the string 'site'
// You must sanitise the value some way here eg:
// $value = filter_var($value, FILTER_SANITIZE_STRING);
$vars[] = $value;
}
}
See filter_var() man page for more information on PHP filters and sanitisation/validation.
Revised URL Structure
I think this probably best solved however by making use of HTML arrays at the point your URL is generated. For more information on HTML arrays please see the PHP man page.
This allows you to access your information like the following:
$site1 = $_GET['site'][0];
$site2 = $_GET['site'][4];
This is the most logical method of dealing with this situation.
Update also see #Mat's answer for more information on this.
This is a bad idea for several reasons:
You have to loop through $_GET to find all variables (there's no language construct to pattern-match them)
Dynamic variables names are confusing, and may open security holes.
You will find that using an array will solve the second point, and also make it a lot easier to work with the code.
The first point can be solved by only using variable names you know. Send a variable containing a count how how many "sites" there are, for example:
site1=example&site2=example2&sitecount=2
This way you know that you only need to read site1 and site2, and you donät need to examine any other GET variables.
you van use $ as $_GLOBAL like this.
${'Site' . $i} = $_GET['site' . $i];
or you can use extract
please read the warnings about exract.
You can use variable variables like this:
$varname = $Site.$i;
$$varname = $_GET['site'.$i];
Doing this is discouraged however, because this is a huge security risk. You may write classes with fields representing your values from $_GET and validating them within the class.