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
Related
I'm using following function to protect my db from injection attacks and etc. for gets.
function filter($data) {
global $db;
$data = trim(htmlentities(strip_tags($data)));
if (get_magic_quotes_gpc())
$data = stripslashes($data);
$data = $db->real_escape_string($data);
return $data;
}
foreach($_GET as $key => $value) {
$data[$key] = filter($value);
}
Question is, i want to filter not only $_GET but $_POST too. How to do that?
And can I reassign value to $_GET or $_POST after filtering? I mean $_GET[$key] = filter($value); instead of $data[$key] = filter($value);..
Don't pre-escape your variables, escape them only at the time you need to escape them.
If you prematurely escape your variable, you'll never know which variable is escaped, and which is not
You'll have to unescape your variables before doing string manipulations, and re-escape them after
Variables coming from different sources (like from an API, from a file or even from your database) won't be escaped. You'll forget to escape them.
You'll have to un-escape all your variables before printing them (you don't want to print the \', I guess)
You can't escape a variable for every possible situation. What about escaping them with escapeshellcmd too ?
PHP did this in the past. It was called magic_quotes_gpc.
But it's so bad practice that it's now deprecated, and it will be removed from the next version of PHP.
It's better to just escape everything at the time you need to. You print a variable ? escape it. You don't have to remember if it's already escaped or not: it's not.
this function makes no sense.
and it doesn't filter anything.
and shouldn't be used this way.
to protect your db from injection attacks you shouldn't do most of the things present in this function and should do many things not present there.
to protect only strings (data chunks enclosed in quotes) from injection attacks you have to use $db->real_escape_string and nothing else.
to protect other query parts you have to use other procedures, as real_escape_string become utterly useless for them
to protect your app from "etc attacks" you have to define what is this "etc" first.
array_walk($_GET,'filter');
array_walk($_POST,'filter');
array_walk($_COOKIE,'filter');
You should probably filter the $key too in case you use it in the query later, but if possible you should use mysql prepared statements and bind variables.
http://www.ultramegatech.com/blog/2009/07/using-mysql-prepared-statements-in-php/
You can change $_GET and $_POST.
Consider file a.php:
<?php
echo $_GET['a'];
?>
a.php?a=abcd' prints abcd\'.
I think PHP auto escape quotes, but I couldn't find a document about this.
Is it true? Because I want to make sure - I'm quite lazy, so I didn't prevent SQL injection in my PHP source code...
It depends on whether magic_quotes_gpc in On or not on your php.ini configuration file.
This configuration directive was introduced to help developers secure themselves against SQL injection attacks. If you don't know what they are, I suggest you read the Wikipedia entry very carefully.
The thing is, relying only on magic_quotes is insecure and unpredictable, and this feature will also be removed in future PHP versions. The best way to secure against SQL injection is to either use prepared statements (with PDO or MySQLi) or use the specific escaping function for your database:
PDO::quote()
mysql_escape_string() or better yet mysql_real_escape_string()
However, if magic_quotes is on and you try to escape your input data chances are you will get a double escaped string (which is not good), to counteract this we usually detect if magic_quotes is On and, if it is, remove the added slashes via stripslashes().
Some do this before calling the database escaping function / method, but personally I prefer to do it upon page load since this will make the data much more consistent and less prone to errors. If you're using PHP 5.3+ the following snippet should disable magic_quotes:
if (get_magic_quotes_gpc() === 1)
{
$_GET = json_decode(stripslashes(preg_replace('~\\\(?:0|a|b|f|n|r|t|v)~', '\\\$0', json_encode($_GET, JSON_HEX_APOS | JSON_HEX_QUOT))), true);
$_POST = json_decode(stripslashes(preg_replace('~\\\(?:0|a|b|f|n|r|t|v)~', '\\\$0', json_encode($_POST, JSON_HEX_APOS | JSON_HEX_QUOT))), true);
$_COOKIE = json_decode(stripslashes(preg_replace('~\\\(?:0|a|b|f|n|r|t|v)~', '\\\$0', json_encode($_COOKIE, JSON_HEX_APOS | JSON_HEX_QUOT))), true);
$_REQUEST = json_decode(stripslashes(preg_replace('~\\\(?:0|a|b|f|n|r|t|v)~', '\\\$0', json_encode($_REQUEST, JSON_HEX_APOS | JSON_HEX_QUOT))), true);
}
If you are running an older version of PHP, the PHP Manual has a fairly good snippet too:
if (get_magic_quotes_gpc()) {
$process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
while (list($key, $val) = each($process)) {
foreach ($val as $k => $v) {
unset($process[$key][$k]);
if (is_array($v)) {
$process[$key][stripslashes($k)] = $v;
$process[] = &$process[$key][stripslashes($k)];
} else {
$process[$key][stripslashes($k)] = stripslashes($v);
}
}
}
unset($process);
}
PHP does this if magic_quotes_gpc is active in the configuration. This functionality is deprecated and will disappear in the future. It's not good (actually, it's outright stupid) to rely on deprecated functionality when writing new code.
Note that magic quotes is not enough to protect against SQL injections since it doesn't handle all character sets correctly. I recommend that you invert the magic quoting and use mysql_real_escape_string (or similar in other database extensions) instead.
You need to update your code to do proper escaping of your SQL input. Or even better, use PDO so you can pass query arguments separately from the query.
While PHP has magic_quotes_gpc, it's disabled by default and will be removed in PHP 6. Besides that, it's a horrible mess leading to potentially insecure or faulty code and thus it's better to remove automatically added quotes if get_magic_quotes_gpc() is true.
mysqli_real_escape_string() is a good start.
I have done some research and still confused, This is my outcome of that research. Can someone please comment and advise to how I can make these better or if there is a rock solid implementation already out there I can use?
Method 1:
array_map('trim', $_GET);
array_map('stripslashes', $_GET);
array_map('mysql_real_escape_string', $_GET);
Method 2:
function filter($data) {
$data = trim(htmlentities(strip_tags($data)));
if (get_magic_quotes_gpc())
$data = stripslashes($data);
$data = mysql_real_escape_string($data);
return $data;
}
foreach($_GET as $key => $value) {
$data[$key] = filter($value);
}
Both methods you show are not recommendable
Blanket "sanitizing" data is counter-productive, because data needs to be sanitised in different ways depending on how it is going to be used: Using it in a database query needs different sanitation from outputting it in HTML, or from using it as parameters in a command line call, etc. etc.
The best way to do sanitation is immediately before the data is being used. That way, it is easy for the programmer to see whether all data is actually getting sanitized.
If you use the mysql_* family of functions, do a mysql_real_escape_string() on every argument you use in a query:
$safe_name = mysql_real_escape_string($_POST["name"]);
$safe_address = mysql_real_escape_string($_POST["address"]);
$result = mysql_query ("INSERT INTO table VALUES '$safe_name', '$safe_address'");
If you use the PDO or mysqli families of functions, you can make use of parametrized queries, which eliminate most of the SQL injection woes - all everyday ones at any rate.
It is perfectly possible to write safe queries with mysql_*, and it is also perfectly possible to introduce a security hole in a PDO query, but if you are starting from scratch, consider using PDO or mysqli straight away.
Use strip_tags() only if you are planning to output user entered data in HTML; note that you need to do an additional htmlspecialchars() to reliably prevent XSS attacks.
The only blanket sanitation method that has some merit is the
if (get_magic_quotes_gpc())
$data = stripslashes($data);
call which filters out the layer of escaping added by the now-deprecated "magic quotes" feature of earlier versions of PHP.
I think this is enough (EDIT: with $data we mean here e.g. one text field from a form, $_POST['example'] etc):
if (get_magic_quotes_gpc())
$data = stripslashes($data);
$data = mysql_real_escape_string($data);
I usually do the first when receiving the $_POST or $_GET data (before input testing) and the latter right before - or during - composing of the sql query.
Additionaly trim it, if you want (maybe you don't always want that). However the best solution would be using some libraries for working with database.
You can either:
Escape all user input supposed for the DB using mysql_real_escape_string (or the mysqli_variant)
Use prepared statements (with mysqli_ or PDO)
Note that you should turn off magic_quotes if possible. If it's on, just use stripslashes (like in your example) as it's deprecated and you should not rely on it.
Also note that you should not do this for data which is not supposed to be inserted into the database as it's completely useless.
I like type-casting whenever you're dealing with ints;
e.g.
$id=(int)$_GET['id'];
if($id<1){//I generally don't pass around 0 or negative numbers
//Injection Attempt
die('Go Away');
}
//Continue with the number I **know** is a number
Well, among your codes only mysql_real_escape_string() function is SQL injection related. However, being completely misplaced.
And it's not enough.
In fact, this function should be used with quoted strings only.
And it's useless for the everything else. So, in any other case other precautions should be taken.
For the complete explanation see my earlier answer: In PHP when submitting strings to the database should I take care of illegal characters using htmlspecialchars() or use a regular expression?
All other functions you're using have no relation to SQL at all.
Note that using both strip_tags and htmlentities is redundant. and htmlentities is obsolete.
I'd use only htmlspecialchars() instead of these two, and not before inserting into database but right before sending it to browser.
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();
I have one "go" script that fetches any other script requested and this is what I wrote to sanitize user input:
foreach ($_REQUEST as $key => $value){
if (get_magic_quotes_gpc())
$_REQUEST[$key] = mysql_real_escape_string(stripslashes($value));
else
$_REQUEST[$key] = mysql_real_escape_string($value);
}
I haven't seen anyone else use this approach. Is there any reason not to?
EDIT - amended for to work for arrays:
function mysql_escape($thing) {
if (is_array($thing)) {
$escaped = array();
foreach ($thing as $key => $value) {
$escaped[$key] = mysql_escape($value);
}
return $escaped;
}
// else
if (get_magic_quotes_gpc()) $thing = stripslashes($thing);
return mysql_real_escape_string($thing);
}
foreach ($_REQUEST as $key => $value){
$_REQUEST[$key] = mysql_escape($value);
}
I find it much better to escape the data at the time it is used, not on the way in. You might want to use that data in JSON, XML, Shell, MySQL, Curl, or HTML and each will have it's own way of escaping the data.
Lets have a quick review of WHY escaping is needed in different contexts:
If you are in a quote delimited string, you need to be able to escape the quotes.
If you are in xml, then you need to separate "content" from "markup"
If you are in SQL, you need to separate "commands" from "data"
If you are on the command line, you need to separate "commands" from "data"
This is a really basic aspect of computing in general. Because the syntax that delimits data can occur IN THE DATA, there needs to be a way to differentiate the DATA from the SYNTAX, hence, escaping.
In web programming, the common escaping cases are:
1. Outputting text into HTML
2. Outputting data into HTML attributes
3. Outputting HTML into HTML
4. Inserting data into Javascript
5. Inserting data into SQL
6. Inserting data into a shell command
Each one has a different security implications if handled incorrectly. THIS IS REALLY IMPORTANT! Let's review this in the context of PHP:
Text into HTML:
htmlspecialchars(...)
Data into HTML attributes
htmlspecialchars(..., ENT_QUOTES)
HTML into HTML
Use a library such as HTMLPurifier to ENSURE that only valid tags are present.
Data into Javascript
I prefer json_encode. If you are placing it in an attribute, you still need to use #2, such as
Inserting data into SQL
Each driver has an escape() function of some sort. It is best. If you are running in a normal latin1 character set, addslashes(...) is suitable. Don't forget the quotes AROUND the addslashes() call:
"INSERT INTO table1 SET field1 = '" . addslashes($data) . "'"
Data on the command line
escapeshellarg() and escapeshellcmd() -- read the manual
--
Take these to heart, and you will eliminate 95%* of common web security risks! (* a guess)
If you have arrays in your $_REQUEST their values won't be sanitized.
I've made and use this one:
<?php
function _clean($var){
$pattern = array("/0x27/","/%0a/","/%0A/","/%0d/","/%0D/","/0x3a/",
"/union/i","/concat/i","/delete/i","/truncate/i","/alter/i","/information_schema/i",
"/unhex/i","/load_file/i","/outfile/i","/0xbf27/");
$value = addslashes(preg_replace($pattern, "", $var));
return $value;
}
if(isset($_GET)){
foreach($_GET as $k => $v){
$_GET[$k] = _clean($v);
}
}
if(isset($_POST)){
foreach($_POST as $k => $v){
$_POST[$k] = _clean($v);
}
}
?>
Your approach tries to sanitize all the request data for insertion into the database, but what if you just wanted to output it? You will have unnecessary backslashes in your output. Also, escaping is not a good strategy to protect from SQL exceptions anyway. By using parametrized queries (e.g. in PDO or in MySQLi) you "pass" the problem of escaping to the abstraction layer.
Apart from the lack of recursion into arrays and the unnecessary escaping of, say, integers, this approach encodes data for use in an SQL statement before sanitization. mysql_real_escape_string() escapes data, it doesn't sanitize it -- escaping and sanitizing aren't the same thing.
Sanitization is the task many PHP scripts have of scrutinizing input data for acceptability before using it. I think this is better done on data that hasn't been escaped. I generally don't escape data until it goes into the SQL. Those who prefer to use Prepared Statements achieve the same that way.
One more thing: If input data can include utf8 strings, it seems these ought to be validated before escaping. I often use a recursive utf8 cleaner on $_POST before sanitization.