There is some risk in using the function extract in the superglobal variables as $_POST and $_GET, I work of the following way.
There is risk of SQL INJECTION or there is an alternative to extract
if(get_magic_quotes_gpc()) {
$_GET = stripslashes($_GET);
$_POST =stripslashes($_POST);
}
function vars_globals($value = '') {
if(is_array($value))
$r = &$value;
else
parse_str($value, $r);
return $r;
}
$r = vars_globals($_GET);
extract($r, EXTR_SKIP);
Yes there is a risk. You don't want to blindly import user input into your symbol table. You should take the time to validate and/or sanitize user input. The filter_var function can help with this.
When inserting into a database, use the driver's escape mechanism to eliminate the possibility of injection. If you're using mysql_* functions, you'd use mysql_real_escape_string. However, it is much better to use PDO and parameterized queries for this.
Related
In my web app is a config file which includes i.e. database connection settings ans is always loaded at the first line of a PHP script. I would like to include a function which cleans all POST and GET data for maybe existing XSS and SQL Injection risks.
I am not sure if that function is really enough
function make_safe($variable)
{
$variable = strip_tags(mysql_real_escape_string(trim($variable)));
return $variable;
}
foreach ($_POST as $key => $value) {
$_POST[$key] = make_safe($value);
}
//Same for $_GET & $_SESSION
Do you have recommendation for this problem?
This function:
function make_safe($variable)
{
$variable = strip_tags(mysql_real_escape_string(trim($variable)));
return $variable;
}
Will not work
SQL injection and XSS are two different beasts. Because they each require different escaping you need to use each escape function strip_tags and mysql_real_escape_string separatly.
Joining them up will defeat the security of each.
Use the standard mysql_real_escape_string() when inputting data into the database.
Use strip_tags() when querying stuff out of the database before outputting them to the screen.
Why combining the two function is dangerous
From the horses mouth: http://php.net/manual/en/function.strip-tags.php
Because strip_tags() does not actually validate the HTML, partial or broken tags can result in the removal of more text/data than expected.
So by inputting malformed html into a database field a smart attacker can use your naive implementation to defeat mysql_real_escape_string() in your combo.
Does this protect against SQL injection attacks?
function sanitize($value) {
// Stripslashes
if (is_array($value)) {
if (get_magic_quotes_gpc()) {
$value = array_map("stripslashes", $value);
}
$value = array_map("mysql_real_escape_string", $value);
} else {
if (get_magic_quotes_gpc()) {
$value = stripslashes($value);
}
$value = mysql_real_escape_string($value);
}
return $value;
}
$_REQUEST = array_map('sanitize', $_REQUEST);
$_GET = array_map('sanitize', $_GET);
$_POST = array_map('sanitize', $_POST);
$_COOKIE = array_map('sanitize', $_COOKIE);
What could I add to sanitize() to protect against cross-site scripting?
What other channels would allow attackers to insert malicious code?
The one-word answer would be "yes". However:
If $value is an array that contains other arrays it won't be handled correctly. You should loop over $value make a recursive call to sanitize for each array you find.
It's preferable to use prepared statements instead of doing this. Of course, if you already have a complete application and are not building from scratch this can be problematic.
Finally, the other ways in which someone can subvert your application are cross-site scripting (aka CSS or XSS) and cross-site request forgeries (CSRF). There are lots of resources here on SO and on the internet you can use to get up to speed. As a starting point, protection against XSS involves calling htmlspecialchars on anything you output, while protection against CSRF involves requiring a session-specific id code for each operation your privileged users are allowed to perform on your site.
Array-safe sanitize version
function sanitize($value) {
if (is_array($value)) {
foreach($value as &$item) {
$item = sanitize($item);
}
} else {
if (get_magic_quotes_gpc()) {
$value = stripslashes($value);
}
$value = mysql_real_escape_string($value);
}
return $value;
}
Update:
For higher visibility: Bjoern's link to this question ( What's the best method for sanitizing user input with PHP? ) is really good.
No.
Use PHP Data Objects Or... Use a Database Abstraction Layer Or... Some framework that does this.
Don't write your own because:
Someone else has
Their code works fine
You can use their code for free
They thought of all the issues you don't know about yet.
It's a lot of work to do this, it's already been done, just spend twenty minutes and figure out someone else's code that does this.
If it is applied after the database connection was established, then it escapes the initial input data correctly.
Now you will have problems using such escaped values for HTML output however. And it does not protect against second order SQL injection (querying the database, then using those values as-is for a second query). And more importantly, most applications work on the input values. If you do any sort of rewriting or string matching, you might undo some of the escaping.
Hencewhy it is often recommended to apply the escaping right before the query is assembled. Nevertheless, the code itself is functional for the general case and advisable if you can't rewrite heaps of legacy code.
You should add html_entities. Most of the time you put $_POST variables into a textbox, like:
<textarea><?php echo $_POST['field']; ?></textarea>
They can mess up your HTML by filling in and do anything they want.
Is there an easier way of safely extracting submitted variables other than the following?
if(isset($_REQUEST['kkld'])) $kkld=mysql_real_escape_string($_REQUEST['kkld']);
if(isset($_REQUEST['info'])) $info=mysql_real_escape_string($_REQUEST['info']);
if(isset($_REQUEST['freq'])) $freq=mysql_real_escape_string($_REQUEST['freq']);
(And: would you use isset() in this context?)
To escape all variables in one go:
$escapedGet = array_map('mysql_real_escape_string', $_GET);
To extract all variables into the current namespace (i.e. $foo = $_GET['foo']):
extract($escapedGet);
Please do not do this last step though. There's no need to, just leave the values in an array. Extracting variables can lead to name clashes and overwriting of existing variables, which is not only a hassle and a source of bugs but also a security risk. Also, as #BoltClock says, stick to $_GET or $_POST. Also2, as #zerkms points out, there's no point in mysql_real_escaping variables that are not supposed to be used in a database query, it may even lead to further problems.
Note that really none of this is a particularly good idea at all, you're just reincarnating magic_quotes and global_vars, which were horrible PHP practices from ages past. Use prepared statements with bound parameters via mysqli or PDO and use values through $_GET or filter_input. See http://www.phptherightway.com.
You can also use a recursive function like this to accomplish that
function sanitate($array) {
foreach($array as $key=>$value) {
if(is_array($value)) { sanitate($value); }
else { $array[$key] = mysql_real_escape_string($value); }
}
return $array;
}
sanitate($_POST);
To sanitize or validate any INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV, you can use
filter_input_array — Gets external variables and optionally filters them
Filtering can be done with a callback, so you could supply mysql_real_escape_string.
This method does not allow filtering for $_REQUEST, because you should not work with $_REQUEST when the data is available in any of the other superglobals. It's potentially insecure.
The method also requires you to name the input keys, so it's not a generic batch filtering. If you want generic batch filtering, use array_map or array_walk or array_filter as shown elsewhere on this page.
Also, why are you using the old mysql extension instead of the mysqli (i for improved) extension. The mysqli extension will give you support for transactions, multiqueries and prepared statements (which eliminates the need for escaping) All features that can make your DB code much more reliable and secure.
As far as I'm concerned Starx' and Ryan's answer from Nov 19 '10 is the best solution here as I just needed this, too.
When you have multiple input fields with one name (e.g. names[]), meaning they will be saved into an array within the $_POST-array, you have to use a recursive function, as mysql_real_escape_string does not work for arrays.
So this is the only solution to escape such a $_POST variable.
function sanitate($array) {
foreach($array as $key=>$value) {
if(is_array($value)) { sanitate($value); }
else { $array[$key] = mysql_real_escape_string($value); }
}
return $array;
}
sanitate($_POST);
If you use mysqli extension and you like to escape all GET variables:
$escaped_get = array_map(array($mysqli, 'real_escape_string'), $_GET);
As an alternative, I can advise you to use PHP7 input filters, which provides a shortcut to sql escaping. I'd not recommend it per se, but it spares creating localized variables:
$_REQUEST->sql['kkld']
Which can be used inline in SQL query strings, and give an extra warning should you forget it:
mysql_query("SELECT x FROM y WHERE z = '{$_REQUEST->sql['kkld']}'");
It's syntactically questionable, but allows you escaping only those variables that really need it. Or to emulate what you asked for, use $_REQUEST->sql->always();
Is it a good, or stupid idea to sanitize all the data that could be sqlinjected? I wrote a function that should do it, but I've never seen it done and was wondering if it was a poor idea.
The function I wrote:
function sanitizeData()
{
$_SERVER['HTTP_USER_AGENT'] = mysql_real_escape_string($_SERVER['HTTP_USER_AGENT']);
foreach(array_keys($_COOKIE) as $key)
{
$_COOKIE[$key] = mysql_real_escape_string($_COOKIE[$key]);
}
foreach(array_keys($_POST) as $key)
{
$_POST[$key] = mysql_real_escape_string($_POST[$key]);
}
foreach(array_keys($_GET) as $key)
{
$_GET[$key] = mysql_real_escape_string($_GET[$key]);
}
}
A bad idea; this is basically another version of the deprecated magic_quotes. Most of that data probably won't end up going into the database, so you'll end up escaping unnecessarily, and potentially double-escaping.
Instead, use prepared statements as needed. Look at mysqli_stmt (part of mysqli) and PDOStatement (part of PDO).
It is also very important to understand that mysql_real_escape_string do not sanitize anything.
By applying this function you do not make any data safe. That's very widespread misunderstanding.
This function merely escaping string delimiters. So, it works only for strings, quote delimited ones.
Thus, real sanitization could be only like this:
$_GET[$key] = "'".mysql_real_escape_string($_GET[$key])."'";
And even this one isn't suffice.
But as already Matt mentioned it would be very bad practice. And even more: as a matter of fact, not only input data should be properly formatted/paramertized. It's database function, not user input one! It has nothing to do with user input. Some data may come not from user input but from a file or other query or some service - it all should be properly formatted as well. That's very important to understand.
Also you are using an odd way to iterate arrays.
this one is more common:
foreach($_GET as $key => $value)
{
$_GET[$key] = mysql_real_escape_string($value);
}
According to the PHP manual, in order to make code more portable, they recommend using something like the following for escaping data:
if (!get_magic_quotes_gpc()) {
$lastname = addslashes($_POST['lastname']);
} else {
$lastname = $_POST['lastname'];
}
I have other validation checks that I will be performing, but how secure is the above strictly in terms of escaping data? I also saw that magic quotes will be deprecated in PHP 6. How will that affect the above code? I would prefer not to have to rely on a database-specific escaping function like mysql_real_escape_string().
Magic quotes are inherently broken. They were meant to sanitize input to the PHP script, but without knowing how that input will be used it's impossible to sanitize correctly. If anything, you're better off checking if magic quotes are enabled, then calling stripslashes() on $_GET/$_POST/$_COOKIES/$_REQUEST, and then sanitizing your variables at the point where you're using it somewhere. E.g. urlencode() if you're using it in a URL, htmlentities() if you're printing it back to a web page, or using your database driver's escaping function if you're storing it to a database. Note those input arrays could contain sub-arrays so you might need to write a function can recurse into the sub-arrays to strip those slashes too.
The PHP man page on magic quotes agrees:
"This feature has been DEPRECATED as
of PHP 5.3.0 and REMOVED as of PHP
5.4.0. Relying on this feature is highly discouraged. Magic Quotes is a
process that automagically escapes
incoming data to the PHP script. It's
preferred to code with magic quotes
off and to instead escape the data at
runtime, as needed."
Magic quotes were a design error. Their use is incompatible with retainnig your sanity.
I prefer:
if (get_magic_quotes_gpc()) {
throw new Exception("Turn magic quotes off now!");
}
Don't write code for compatibility with inherently broken setups. Instead defend aginst their use by having your code FAIL FAST.
I use the following code in the header file of my website to reverse the effects of magic_quotes:
<?php
// Strips slashes recursively only up to 3 levels to prevent attackers from
// causing a stack overflow error.
function stripslashes_array(&$array, $iterations=0) {
if ($iterations < 3) {
foreach ($array as $key => $value) {
if (is_array($value)) {
stripslashes_array($array[$key], $iterations + 1);
} else {
$array[$key] = stripslashes($array[$key]);
}
}
}
}
if (get_magic_quotes_gpc()) {
stripslashes_array($_GET);
stripslashes_array($_POST);
stripslashes_array($_COOKIE);
}
?>
Then I can write the rest of my code as if magic_quotes never existed.
"I would prefer not to have to rely on a database-specific escaping function like mysql_real_escape_string()"
Then use something like PDO. But you have to reverse the damage done by magic quotes anyway.
Put a requirement of PHP 5.2 or higher on your code and use the filter API. The filter_* functions access the raw input data directly (they don't ever touch $_POST etc.) so they're completely unaffected by magic_quotes_gpc.
Then this example:
if (!get_magic_quotes_gpc()) {
$lastname = addslashes($_POST['lastname']);
} else {
$lastname = $_POST['lastname'];
}
Can become this:
$lastname = filter_input(INPUT_POST, 'lastname');
Right, it's not the best way to do it and not the most secure. Escaping is best done in relation to what you are escaping for. If it is to store in a mysql database, use mysql_real_escape_string which takes into account other locales, character sets. For HTML, htmlentities. For use in code, escapeshellcmd, escapeshellarg. Yes, you probably need to stirpslashes first if magic quotes is on. But best not to count on it or use it.
Regarding using a database specific escaping function, you pretty much need to. I have found just using addslashes() to fail in rare cases with MySQL. You can write a function to escape which determines which DB you are using and then use the approriate escape function.
You may try this:
if (get_magic_quotes_gpc()) {
$_REQUEST = array_map('stripslashes', $_REQUEST);
$_GET = array_map('stripslashes', $_GET);
$_POST = array_map('stripslashes', $_POST);
$_GET = array_map('stripslashes', $_COOKIES);
}
"I would prefer not to have to rely on a database-specific escaping function like mysql_real_escape_string()"
Also addslashes can be tricked as well check out this post:
http://shiflett.org/blog/2006/jan/addslashes-versus-mysql-real-escape-string
Your sample code is backwards, you should be doing the following:
if (get_magic_quotes_gpc()) {
$lastname = stripslashes($_POST['lastname']);
} else {
$lastname = $_POST['lastname'];
}
Note that this leaves your input data in a 'raw' state exactly as the user typed it - no extra backslashes and potentially loaded with SQL Injection and XSRF attacks - and that's exactly what you want. Then, you make sure you always use one of the following:
When echoing the variable into HTML, wrap it in htmlentities()
When putting it into mysql, use prepared statements or else mysql_real_escape_string() as a minimum.
When echoing the variable into Javascritpt code, use json_encode()
Joel Spolsky has some good starting advice in Making Wrong Code Look Wrong
Just found this over on the PHP manual pages, looks like a pretty clever way to strip em (deals with keys and values...):
if (get_magic_quotes_gpc())
{
$_GET = json_decode(stripslashes(json_encode($_GET, JSON_HEX_APOS)), true);
$_POST = json_decode(stripslashes(json_encode($_POST, JSON_HEX_APOS)), true);
$_COOKIE = json_decode(stripslashes(json_encode($_COOKIE, JSON_HEX_APOS)), true);
$_REQUEST = json_decode(stripslashes(json_encode($_REQUEST, JSON_HEX_APOS)), true);
ini_set('magic_quotes_gpc', 0);
}
Prepared statements of PDO and Mysqli are the better way to prevent SQL injection.
But if you are migrating a legacy code which is base on Magic Quotes for every SQL queries, you can refer yidas/php-magic-quotes for implementing Magic Quotes on the environment with PHP 5.4 above version.
https://github.com/yidas/php-magic-quotes