Upgrading PHP - Need to remove get_magic_quotes_gpc - php

We're upgrading from php 5.3 to 5.4, which is not backwards compatible for 'get_magic_quotes_gpc'. I understand the code will still work, sort of, but just bring back a FALSE each time.
However, I think the time has come to scrub this from our code.
Here's a typical example:
$product_id = "0";
if (isset($HTTP_GET_VARS["id"])) {
$rid = (get_magic_quotes_gpc()) ? $HTTP_GET_VARS["id"] : addslashes($HTTP_GET_VARS["id"]);
}
//then $product_id gets used all over the place in many different queries
I've been researching how to fix this and this is what I came up with:
$rid = "0";
if (isset($HTTP_GET_VARS["id"])) {
$rid = addslashes($HTTP_GET_VARS["id"]);
}
I'm a little over my head here. I know this all has to do with SQL injection and such. Is my solution an reasonable/acceptable one?
Thanks in advance.
<<<< EDIT - ADDITIONAL INFORMATION >>>>
Thanks for the replies. Actually, we did a bunch of conversion to PDO about 18 mos ago (mostly due to this type of advice on stackoverflow :)
So, I may have some reduntant, pointless code going on. Here's the full picture of what is happening below the code I posted above that gets the variable from the URL.
You'll see, there is the (get_magic_quuotes_gpc) that used to be there, now commented out and replaced by the (addslashes). But that variable is passed on to a PDO query.
$product_id = "0";
if (isset($HTTP_GET_VARS["id"])) {
//$product_id = (get_magic_quotes_gpc()) ? $HTTP_GET_VARS["id"] : addslashes($HTTP_GET_VARS["id"]);
$product_id = addslashes($HTTP_GET_VARS["id"]);
}
// NEW QUERIES - BEG xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
try {
# MySQL with PDO_MYSQL
$pdo = new PDO("mysql:host=$hostname_db;dbname=$database_db", $username_db, $password_db);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// Query 1: product details
$stmt = $pdo->prepare('SELECT
[a bunch of stuff here, fields, joins, etc]
WHERE product_id = ? ');
$stmt -> execute(array($rid));
$row_count_resto_details = $stmt->rowCount();
$row_resto_details = $stmt->fetch(PDO::FETCH_ASSOC);
}
// Error message for pdo
catch(PDOException $e) {
echo $e->getMessage();
}
// END QUERY xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Can I just get rid of all the stuff in the first 4-5 lines of code and just make it this:
$product_id = $HTTP_GET_VARS["id"];

No, it's not reasonable.
The problem is, the "magic quotes" feature was a bad idea in the first place, so trying to replicate it only means that you've relied on a broken solution until now, and your application is without a doubt vulnerable to SQL injection attacks.
"Why is magic quotes a broken solution?", you'd probably ask. And the answer is kind of hidden in the question itself - security and IT in general aren't and can't be magic. Whenever you see a solution that advertises itself or at least seems to work in a "magic" way, know that it is bad and you should never trust it.
What you need instead are context-aware solutions, and in the case of preventing SQL injections - that's parameterized queries. You should read this to learn more: How can I prevent SQL-injection in PHP?
I'd also urge you to upgrade straight to PHP 5.6, mostly for two reasons:
PHP 5.4 will reach its End Of Life in a month and that alone makes it a possible security risk.
You have no reason not to. Honestly, the upgrade path is a breeze and comes with very little backwards-compatibility concerns, if any at all. PHP is much more stable now, there are very few significant changes since 5.3 vs 5.4.
Update (to answer the extended question):
You not only can remove the addslashes() logic, but you MUST do that - if you leave it, it will add slashes to some of your input data and these slashes will be a part of the data itself.
What you want to do instead is to validate input data - check if it is in the proper format in the first place. For example, if you expect a numeric ID - check if it only contains digits before using it.
Also, $HTTP_GET_VARS has been deprecated since PHP 4.1 and you should be using $_GET instead.

Related

Short Solution for SQL Injection Attacks with MySQLI

I've gotten into the habit of writing the following sort of code:
$q = mysqli_query($mysqli,"SELECT * FROM table WHERE a='$a', b=$b;");
while ($row = mysqli_fetch_array($q)) {
// do something
}
Where $a is a string entered by the user (gotten through $_GET) and $b is a user-entered integer.
Obviously the code I have above is vulnerable to SQL injection attacks, so my habit is to rewrite it like this:
$q = mysqli_query($mysqli,"SELECT * FROM table WHERE a='".str_replace("'","",$a)."', b=".($b+0).";");
But this of course has problems if $a needs to have apostrophes (or quotation marks when quotation marks are used to mark the string).
Recently I learned about prepared statements in mysqli and started playing around with them. I wrote the following function to make it easier to make calls without having to change much of my code:
function safequery($a,$b,$c) {
global $mysqli;
$q = mysqli_prepare($mysqli,$a);
$e = "mysqli_stmt_bind_param(\$q,\$b";
$i = 0;
while ($i < count($c)) {
$e.=",";
$e.="\$c[$i]";
$i++;
}
$e.=");";
eval($e);
mysqli_stmt_execute($q);
return $q;
}
safequery("SELECT * FROM table WHERE a=? AND b=?;","si",array("unsafestring",37));
But what is returned from this function turns out not to be a mysqli_result and thus doesn't work with the first bit of code above. After some more research, I found an alternative, but it would require a complete rethink of how I write my code. Is this necessary or is it possible to protect against MySQL injection attacks with only small changes to the first bit of code (no new lines, same output style, etc.)?
I have looked around on StackOverflow and the rest of the web but I can't find a good simple solution; all of them require the edition of at least three more lines for every call and a different way of reading each row. I'd prefer to do this procedural-y...
Don't think half-measures are going to solve this problem. Commit to expunging all of the interpolation bugs from your code and be disciplined about using prepared statements. Your proposed fix only makes things worse, it gives you a false sense of security. It's also considerably more work than using prepared statements so I'm not sure why you'd even bother doing it this way.
One way to make this a lot easier to do is switch from using double quotes " to single quotes ' on your queries to disable interpolation. Any escaping errors become syntax problems, and if your editor highlights those you'll be able to spot them from across the room, and if something does by fluke work you'll be inserting harmless things like $a instead of actual data.
Another thing to consider is if you should be using an ORM like Doctrine or Propel given what you know about the sophistication of your application. These can make things considerably easier from an implementation perspective.
The code you have there is a ticking time bomb, get rid of it as soon as you can. Don't think replacing quotes is enough, that solves just one issue, there's actually a number of other methods your application can be vulnerable to injection bugs. Tools like SQLMap have an entire arsenal of things they can try to break your code and if you look at the list of things it can do if it finds a flaw you'll probably want to fix these problems right away.
One way you can find issues is using a tool like grep:
grep query `find -name '*.php'` | grep '\$'
That's not bulletproof, but it should probably turn up a lot of code you should fix right away.
Also as #ceejayoz suggests, purge that function with eval in it from your computer and never, ever do that again.
After talking with the commenters and looking at some of the other questions and things they linked to and suggested, I've rewritten the third code snippet to both solve the problem in question and fix the security holes that several commenters pointed out (if there are any remaining ones, please tell me).
First on my use of eval(). Though I can't see any immediate way it could cause problems (user strings are not executed as code in the eval()) it is certainly a round about and stupid way of solving my problem. #TadMan suggested call_user_func_array() which, once worked out, looks something like this:
function refValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0) //Reference is required for PHP 5.3+
{
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
return $arr;
}
function safequery($a,$b,$c) {
global $mysqli;
$q = mysqli_prepare($mysqli,$a);
call_user_func_array("mysqli_stmt_bind_param",refValues(array_merge(array($q,$b),$c)));
mysqli_stmt_execute($q);
return $q;
}
safequery("SELECT * FROM table WHERE a=? AND b=?;","si",array("unsafestring",37));
It turns out that mysqli_stmt_bind_param() takes only references, thus the new refValues() function (run into and solved before on StackOverflow: https://stackoverflow.com/a/16120923/5931472).
While this removes eval() and makes my code easier to understand, it still doesn't solve the original problem of returning the query response in a way that mysqli_fetch_array() can use. It turns out that the proper function to do this is mysqli_stmt_get_result() so the last line of safequery() is rewritten from:
return $q;
To:
return mysqli_stmt_get_result($q);
The result of safequery() is then a mysqli_result which can be used by mysqli_fetch_array().

Converting mysql_* to mysqli_* issue

I am using MySQLConverterTool to convert my web application,
first issue i faced is code getting to big i dont even understand what that means? It was very small code before and now i see this is too big.
//old code
$ask_id = mysql_real_escape_string($_POST['ask_id']);
//after convert
$ask_id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $_POST['ask_id']) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
Its working fine but i want to know if its correct way of mysqli_* or is there some issue or bug i need to fix in line?
I also want to know how i can make this part secure
if (isset($_POST['asking-money'])) {
$dailyBonus = 10000;
$update = mysqli_query($GLOBALS["___mysqli_ston"], "UPDATE users SET ask_time='$newtime', bonus='dailyBonus' WHERE id='$userid'");
// some more calculation
}
The first bit of code looks like it (grossly) added a giant ternary statement to check that the variables you were using were at least set, but other than that you should just be able to use:
mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $_POST['ask_id'])
As for security with the SQL query, try using Prepared Statements instead of directly querying with variables
mysqli_prepare
for the mysqli_* part, most of the things that used to be done with mysql_* remained almost the same with a new prefix, so, most likely there is no problem
and for the how to make it secure, just evaluate and prepare all the parameters being passed from the user before using them in the query, in other words, NEVER under any case, use the user input directly in a query. other than that the code seems very fine to me.
The first code is a ternary statement (short way for if/else). Way too much, my opinion.
I recommend PDO and it's prepared statements, if you're able to use it. It's very secure and easy to handle.
By the way: try to avoid the MySQLConverterTool. It's hard to get into this code after months. K eep i t s mart and s imple! :-)
Good luck!

php mysql adodb

I'm using PHP with adodb but come up against a massive problem. I'm using adodb to speed up development so I can do thing like:
$r["Name"] = $_POST['txtName'];
if ($_POST["ID"] != "")
$conn->AutoExecute("content", $r, 'UPDATE', 'AutoID = ' . $_POST["ID"]);
else
$conn->AutoExecute("content", $r, 'INSERT');
However if that name was to have a single quote in it saves into db with a slash! So if the name is Testimonial's it will save as Testimonial\'s which is causing me massive problems, is there anyway I can avoid this but still program like above as it's hell of a lot quicker than preparing insert / update statements.
Cheers
The correct and final solution to this issue is composed of two parts:
Disable all magic_quotes programmatically in your code. This ensures that you have a known configuration to work with, which cannot be broken if/when an admin changes these php.ini settings.
Validate/quote all incoming user input before accessing the database!
While the first part is good programming, the second is absolutely essential to write a secure application!
To quote the user input there are two ways you can go:
Manually (use AdoDB's qstr or Quote), in which case you must be very very careful to not miss anything. This can be quite doable for small projects, I have gone this way many times in the past.
Use prepared statements with bound variables to make your queries. This ensures that there will never be an SQL injection in your app as long as you specify the variable types correctly, and is way less error prone than the first option. This is what I am doing for some time now.
Update:
If you go with prepared statements, you may find that AdoDB doesn't buy you that much and you can use PDO for most of the work. When you need something "automagic", you can write a few functions specific to the application yourself. In my experience, that's just a little more work and overall better than including AdoDB.
Thanks for the input, I've decided to turn of magic quotes at runtime with this function:
if (get_magic_quotes_gpc()) {
function stripslashes_gpc(&$value)
{
$value = stripslashes($value);
}
array_walk_recursive($_GET, 'stripslashes_gpc');
array_walk_recursive($_POST, 'stripslashes_gpc');
array_walk_recursive($_COOKIE, 'stripslashes_gpc');
array_walk_recursive($_REQUEST, 'stripslashes_gpc');
}
However is that then prone to SQL injection?

Function to sanitize input values PHP

I use this:
function safeClean($n)
{
$n = trim($n);
if(get_magic_quotes_gpc())
{
$n = stripslashes($n);
}
$n = mysql_escape_string($n);
$n = htmlentities($n);
return $n;
}
To prevent any type of MySQL injection or anything like that. Whenever I use it to wrap around $_POST like this:
$username = safeClean($_POST['user']);
$password = md5(safeClean($_POST['password']));
$vpassword = md5(safeClean($_POST['verify']));
$email = safeClean($_POST['email']);
It doesn't even work, but I have attached functions.php and the directory is correct but doesn't work at all because it just shows a blank page... If I remove the safeClean() from each $_POST it works.
How come this isn't working at all?
In my opinion, this sort of general sanitization approach isn't the best way to think about things. For one thing, parameterized queries (probably most convenient using PDO) are a much better way to approach the SQL safety issue. But in general...
I know the developer impulse is to try and reduce the number of things you have to think about. So, naturally, you want to see if you can come up with an all-purpose sanitization function you can just hand all inputs over to and not have to worry any more. Inputs are one arena, though, where if you really want security, you need to think specifically about what each incoming piece of data is supposed to be and where it's going to end up. If you go on auto-pilot here, you will introduce a security issue at some point.
Try using mysql_real_escape_string() rather than mysql_escape_string().
Almost everything in your code is wrong.
get_magic_quotes_gpc is misplaced, htmlentities is misplaced and even term "sanitization" is misused.
As a matter of fact, you shouldn't sanitize anything for the database. But just follow syntax rules.
Take a look at the very similar question, I've explained SQL matters pretty well: In PHP when submitting strings to the database should I take care of illegal characters using htmlspecialchars() or use a regular expression?
And as of the blank page, you have to learn primer of debugging. You have to turn error reporting on to see error messages instead of blank page. To start with it you can refer to this article:
Link to start: http://www.ibm.com/developerworks/library/os-debug/
you can start from adding these lines at the top of your cript
ini_set('display_errors',1);
error_reporting(E_ALL);
and this code to the query execution:
$result = mysql_query($query);
if (!$result) trigger_error(mysql_error());

Is this PHP code secure?

Just a quick question: is the following PHP code secure? Also is there anything you think I could or should add?
$post = $_GET['post'];
if(is_numeric($post))
{
$post = mysql_real_escape_string($post);
}
else
{
die("NAUGHTY NAUGHTY");
}
mysql_select_db("****", $*****);
$content = mysql_query("SELECT * FROM tbl_***** WHERE Id='" . $post . "'");
In this particular case, I guess the is_numeric saves you from SQL injections (although you would still be able to break the SQL statement, cf Alex' answer). However, I really think you should consider using parameterized queries (aka. prepared statements) because:
They will protect you even when using parameters of non-numeric types
You do not risk forgetting input sanitation as you add more parameters
Your code will be a lot easier to write and read
Here is an example (where $db is a PDO connection):
$stmt = $db->prepare('SELECT * FROM tbl_Persons WHERE Id = :id');
$stmt->execute(array(':id' => $_GET['post']));
$rows = $stmt->fetchAll();
For more information about parameterized SQL statements in PHP see:
Best way to stop SQL Injection in PHP
It's a little rough, but I don't immediately see anything that will cause you any serious problems. You should note that hexidecimal notation is accepted within is_numeric() according to the documentation. You may want to use is_int() or cast it. And for clarity, I would suggest using parameterized queries:
$sql = sprintf("SELECT col1
FROM tbl
WHERE col2 = '%s'", mysql_real_escape_string($post));
In this case, $post would be passed in as the value of %s.
is_numeric will return true for hexadecimal numbers such as '0xFF'.
EDIT: To get around this you can do something like:
sprintf('%d', mysql_real_escape_string($post, $conn));
//If $post is not an int, it will become 0 by sprintf
Look at the snippet here on php.net for more info.
You have the right idea but is_numeric() may not behave as you intended.
Try this test:
<?php
$tests = Array(
"42",
1337,
"1e4",
"not numeric",
Array(),
9.1
);
foreach($tests as $element)
{
if(is_numeric($element))
{
echo "'{$element}' is numeric", PHP_EOL;
}
else
{
echo "'{$element}' is NOT numeric", PHP_EOL;
}
}
?>
The result is:
'42' is numeric
'1337' is numeric
'1e4' is numeric
'not numeric' is NOT numeric
'Array' is NOT numeric
'9.1' is numeric
1e4 may not be something your sql server understands if you're looking for what is commonly referred to as a numeric value. From an SQL injection standpoint you're fine.
You're not passing the connection resource to mysql_real_escape_string() (but you seemingly do so with mysql_select_db()). The connection resource amongst other things stores the connection charset setting which might affect the behavior of real_escape_string().
Either don't pass the resource anywhere or (preferably) pass it always but don't make it even worse than not passing the resource by mixing both.
"Security" in my book also encompasses whether the code is readable, "comprehensible" and does "straight-forward" things. In the example you would at least have to explain to me why you have the !numeric -> die branch at all when you treat the id as a string in a SELECT query. My counter-argument (as the example stands; could be wrong in your context) would be "Why bother? The SELECT just will not return any record for a non-numeric id" which reduces the code to
if ( isset($_GET['post']) ) {
$query = sprintf(
"SELECT x,y,z FROM foo WHERE id='%s'",
mysql_real_escape_string($_GET['post'], $mysqlconn)
);
...
}
That automagically eliminates the trouble you might run into because is_numeric() doesn't behave as you expect (as explained in other answers).
edit: And there's something to be said about using die() to often/to early in production code. It's fine for test/example code but in a live system you almost always want to give control back to the surrounding code instead of just exiting (so your application can handle the error gracefully). During the development phase you might want to bail out early or put more tests in. In that case take a look at http://docs.php.net/assert.
Your example might qualify for an assertion. It won't break if the assertion is deactivated but it might give a developer more information about why it's not working as intended (by this other developer) when a non-numeric argument is passed. But you have to be careful about separating necessary tests from assertions; they are not silver bullets.
If you feel is_numeric() to be an essential test your function(?) might return false, throw an exception or something to signal the condition. But to me an early die() is the easy way out, a bit like a clueless opossum, "I have no idea what to do now. If i play dead maybe no one will notice" ;-)
Obligatory hint to prepared statements: http://docs.php.net/pdo.prepared-statements
I think it looks ok.
When accessing databases I always use query binding, this avoids problems if I forget to escape my strings.

Categories