I'm sure someone asked this before but I just can't find a post similar.
how necessary is it to validate an ID field from $_GET variable?
I'm using is_numeric() to make sure I'm getting a number at least but am I just putting in unnecessary code?
ex.
www.test.com/user.php?user_id=5
if (isset($_GET['user_id']) && is_numeric($_GET['user_id'])) {
*PDO query for user information*
}
is the is_numeric() necessary?
is there a possibility of an attack by changing user_id in the address?
The best way to sanitize a numeric id is by using an (int) cast.
$id = (int) $_GET['ID'];
with strings you just never know.
Is the is_int() necessary?
You are probably looking for retrieving data by id. Therefore convert the string to an int is the simplest way to go. On a side note is_int will always return false if applied to a string.
Is there a possibility of an attack by changing user_id in the address?
Well, strings are always dirty. You never know what strange characters an user might input and how that will effect the query. For example, I don't know if it can be applied in this case but, you should take a look at NULL bytes attacks.
If you want to properly validate an integer before performing the query, you should use filter_input(); the outcome is either a valid integer, false if it's not a valid integer or null if the parameter wasn't passed at all.
if (is_int($userId = filter_input(INPUT_GET, 'user_id', FILTER_VALIDATE_INT))) {
*PDO query for user information*
}
If you're using prepared statements this won't really matter so much, but if you wish to return a failure response based on whether the input conforms to what's expected, you can use the above.
If you don't want to use prepared statements, PDO::quote should be the correct function:
Returns a quoted string that is theoretically safe to pass into an SQL statement.
is_int will not work, because GET variables are always passed as strings.
Personally, I like to test for a valid integer with:
if(strval(intval($_GET['user_id'])) === $_GET['user_id'])
However, this can be overkill. After all, if you're using prepared statements then there's no need to handle any escaping, and searching for a row that doesn't exist will just return no results. I'd throw in intval($_GET['user_id']), but only to really make it clear to future coders that the ID is a number.
is_int check type of variable. But $_GET['id'] will be always a string. Better use filter_var.
But you must use prepared statement anyway.
P.S. With prepared statements you can not use validation. DB will tell that nothing was found. But if you want to warn user about bad request you must validate it before querying.
Related
Before i make my query, i check if the variable that is to be used in that query is an integer using this code: filter_var($_POST["user_id"], FILTER_VALIDATE_INT) !== false.
My question is, should i use PDO or do any escaping if the above function returns true only if my value is an integer (meaning that the value i am to use to build my query is safe) ? Is there any need to escape the value using prepared statements if my value has already passed the above test ?
I have not done any testing with the above nor am i really experienced in server-side technologies, so it is up to you PHP/security experts to guide me.
It's still a good idea to use prepared statements. Bind functions at this point are tried and true.
What if you or someone else screws up the filter?
Are you going to remember to use the right filter at every point in your code? This is a very easy thing to mismanage, and sometimes you may not be able to plan for every eventuality. Integers are relatively easy, but strings are far more complex.
In regards to your professional reputation, will other people see this code? If you had open source code (like github or something), and I was a hiring manager looking into your history, I would not hire you for breaking such a standard security practice like this.
Admittedly, point 3 is a little off topic, but I feel that it's worth mentioning.
This answer is an explanation of the shorthand type casting, from comments, as it's easier to read it as an answer than as a set of comments.
Your code:
filter_var($_POST["user_id"], FILTER_VALIDATE_INT) !== false.
This is a long winded way of ensuring that POSTed data is integer. It has issues, because POST data is always cast as a string.
$_POST["user_id"] = (int)$_POST["user_id"];
Is much easier to read and shorter to type, and this forces the data to be the integer type. This will competely solve your security risk if putting non-integer data into an integer placement in your SQL.
This utalises PHP Type Juggling which it is well worth reading up on.
While the above code will solve your security aspect, it will raise other overlap issues because any string can be casted to an integer, but the cast will return 0 if the string doesn't start with an integer value.
Example:
$string = "hello";
print (int)$string; // outputs 0;
$string = "27hello";
print (int)$string; // outputs 27;
$string = true;
print (int)$string; // outputs 1;
$string = "";
print (int)$string; // outputs 0;
So, overall I would suggest the following line to ensure your given POST value is a correct integer:
if (strcmp((int)$_POST['value'], $_POST['value']) == 0){
/// it's ok!
}
Please see this answer for further details as well as this PHP manual page.
maybe you need to see php bugs page this page before use FILTER_VALIDATE_INT
First of all, I am fully aware of SQL injection vulnerabilities and I am using PDO for newer applications that I am developing in PHP.
Long story short, the organization that I'm working for cannot afford to delegate any human resources at the moment to switch everything over to PDO for the rather large application that I'm currently working on, so I'm stuck with using mysql_* functions in the meantime.
Anyways, I am wondering if it is safe to use data validation functions to "sanitize" numeric parameters used in the interpolated queries. We do use mysql_real_escape_string() for strings (and yes I am aware of the limitations there too). Here is an example:
public function foo($id) {
$sql = "SELECT * FROM items WHERE item_id = $id";
$this->query($sql); // call mysql_query and does things with result
}
$id id a user-supplied value via HTTP GET so obviously this code is vulnerable. Would be OK if I did this?
public function foo($id) {
if (!ctype_digit($id)) {
throw new \InvalidArgumentException("ID must be numeric");
}
$sql = "SELECT * FROM items WHERE item_id = $id";
$this->query($sql); // call mysql_query and does things with result
}
As I'm aware, ctype_digit is the same as checking against a regular expression of \d+.
(There's also filter_var($id, FILTER_VALIDATE_INT), but that can potentially return int(0) which evaluates to FALSE under loosely-typed comparisons, so I'd have to do === FALSE there.)
Are there any problems with this temporary solution?
Update:
Variables do not only include primary keys, but any field with type boolean, tinyint, int, bigint, etc., which means that zero is a perfectly acceptable value to be searching for.
We are using PHP 5.3.2
Yes, if you indeed religiously use the correct function to validate the data and correctly prevent the query from running if the data is not as expected, there's no vulnerability I can see. ctype_digit has a very limited and clear purpose:
Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
There's basically nothing that can go wrong with this function, so it's safe to use. It will even return false on an empty string (since PHP 5.1). Note that is_numeric would not be so trustworthy. I would possibly still add a range check to make sure the number is within an expected range, I'm not sure what could happen with overflowing integers. If you additionally cast to (int) after this check, there's no chance of injection.
Caveat emptor: as with all non-native parameterised queries, there's still a chance of injection if you're getting into any shenanigans with connection charsets. The range of bytes that may slip through are severely limited by ctype_digit, but you never know what one could come up with.
Yes, it will work. Your code will raise an exception if the value isn't a numeric string, you'll just have to catch that and display an error message to the user.
Beware that ctype_digit($foo):
Will return true if $foo is empty before PHP 5.1 (see the doc) ;
Will return false for all int values of $foo outside of the [48, 57] interval (ASCII numbers).
So you'll also need to check that $foo is a non-empty string if you plan on using ctype_digit($foo)
Long story short, the organization that I'm working for cannot afford to delegate any human resources at the moment to switch everything over to PDO
I don't see where is the problem here. According to the code you posted, you are already using some sort of DB wrapper, and already planning to alter the calling code for the every numeric parameter. Why not to alter that DB wrapper to make it support prepared statements, and alter calling code to employ it?
Old mysql ext is not a problem - one can emulate prepared statements with it all right.
I am fully aware of SQL injection vulnerabilities.
Your "full awareness" is a bit exaggerated. Unfortunately, most people do not understand the real source of injection, as well as the real purpose of the prepared statement.
That thing with separating data from the query is a nice trick, but totally unnecessary one. While the real value of prepared statement is its inevitability, as opposed to essential arbitrariness of the manual formatting.
Another your fault is separated treatment for the strings - it is partly formatted in the query (adding quotes) and partly - outside (escaping special characters) which again is a call disaster.
As you decided to stick to the manual formatting, then enjoy your injections, sooner or later. Your ideas are good for the artificial, fully controlled sandbox example. However, things turn different in the real life application, with many people working on it. Instead of asking a program to format your data, you are asking people to do that. With all the obvious consequences.
It makes me wonder why PHP users unable learn from their mistakes, and still eagerly devising practices, that has been proved unreliable long time ago.
Just spotted another fallacy in your reasoning
a user-supplied value via HTTP GET so obviously this code is vulnerable.
You have to understand that any unformatted value makes this code vulnerable, no matter if its HTTP GET, FTP PUT or file read. It is not only notorious "user input" have to be properly formatted but any input. This is why it is essential to make DB driver the only place where formatting occurs. It should be not a developer who formats the data but a program. Your idea contradicts with such a cornerstone principle.
Use mysql_real_escape_string and wrap your $id in single quote marks. The single quote marks ensures the safety and avoids the probability of SQL-injection.
For example, SELECT * FROM table WHERE id = 'escaped string' can't be hacked to something like: SELECT * FROM table WHERE id = 1; DROP table; as '1; DROP table;' will be considered the input argument for WHERE.
ctype_digit() will return false for most integer values of $id. If you want to use the function, cast it to string first:
public function foo($id) {
$id = (string)$id;
if (!ctype_digit($id)) {
throw new \InvalidArgumentException("ID must be numeric");
}
$sql = "SELECT * FROM items WHERE item_id = $id";
$this->query($sql); // call mysql_query and does things with result
}
This is because integer is interpreted as ASCII value.
I use intval() for simple cases although (int) apparently eats less resources. Example:
$sql =
"SELECT * FROM categories WHERE category_id = " .
intval($_POST['id']) .
" LIMIT 1";
I am writing a controller action that takes two inputs from a form: an existing user id and and a new userid.
Both should be integer values.
To avoid any potential security problems, is it enough to simply check is_int?
ie:
if (is_int($existingUserId)) {
}
This should avoid any problems i think - but am not 100% sure
You should use is_numeric instead of is_int.
According to the is_int documentation: is_int('23') = bool(false). Also, the documentation notes:
To test if a variable is a number or a numeric string (such as form input, which is always a string), you must use is_numeric().
might be a silly question nonetheless:
I'm playing around with the following code:
$a='a';
if ($_GET['a'] == $a)
echo 'true';
else
echo 'false';
Now, is there any way to send data to break the verification? Obviously the way it could've been done in an SQL injection won't go.
Just wondering how secure this way of validation is.
Thanks in advance.
EDIT:
My question was, is there anything that can be passed thorugh $_GET that could 'break' the comparison and always output 'true'.
If you are looking to validate that $_GET['a'] really in face equals to "a" and nothing else, than yes, that's the code.
However, if you're expecting "a" and only "a" it probably shouldn't be a user input.
Validation (or sanitation), means to take whatever string they might throw at you, and make sure it's valid for whatever purpose you want it to. If it's sent to the database, pass it through mysql_escape_string() or use prepared statements. If it's to be displayed as HTML make sure there aren't any harmful tags by using html_entities() or strip_tags().
Your verification isn't very good for anything else other than saying the user has inputted "a". But yes, nothing other than "a" would be able to get through.
Well, if you knew exactly what was coming in, you could compare without type coercion and check for an empty parameter:
$a = 'a';
if( !empty( $_GET['a'] ) && $_GET['a'] === $a )
{
//do more validation using your data model
}
else
{
//output error msg
}
You could use Prepared-Statements from the mysqli extension this already prevents every possible injection.
If you don't want to use such mysql and mysqli also have "real_escape_string"-methods which you can use in your Query when putting in Userinput
Example
$sql = "SELECT `name` FROM `example` WHERE `id` = '".mysql_real_escape_string($YOURVAR)."'";
real_escape_string method from standart mysql extension
mysqli real_escape_string
I am wondering, If I have a value I know should be numeric, is multiplying it by 1 a safe method to clean it?
function x($p1){
$p1*=1;
sql="select * from t where id ={$p1}";
//run query..
}
Although my example uses an ID, this is being used for many types of numeric values I have in my app (can be money, can be pai etc).
I don't see why it wouldn't be. But what's wrong with using prepared statements? That's always going to be safer than using PHP variables directly in SQL statements.
You can use is_numeric()
I'm sure there is a more "appropriate" way, but for the scope of your question, I would say yes. If some sort of string is passed PHP will interpret it as a zero when doing the mathematical operation.
You can also use is_int()
While that'll probably work, intval seems like a better solution. http://php.net/manual/en/function.intval.php. Your intent will likely be more obvious to someone else reading your code.
If you want to check if a value is numeric before converting it to an int, use is_numeric ( http://php.net/manual/en/function.is-numeric.php ). It'll check for strings that are numeric as well as integers. For example, if a number was coming back from a text input form via AJAX, it might be a string. In that case, is_int would return false, but is_numeric would return true.
EDIT
Now that I know you use DECIMAL for the MySQL column type, you can do something like this:
function getItem($pValue)
{
if (!is_numeric($pValue))
{
return false;
}
$Query = sprintf
(
'SELECT * FROM %s WHERE %s = %.2f',
'TableName',
'Price',
$pValue
);
// Do something with $Query
}
It works most of the times as it will cast strings to integers or doubles, but you have to be careful. It's going to work correctly for scalar values. However, if you do this:
x(new stdClass);
You'll get an E_NOTICE. This is not so bad, right? This:
x(array());
And you'll get an E_ERROR, Unsupported operand types, and the script terminates.
Maybe you'd think that it isn't so bad, but a fatal error at an inopportune moment can leave your system in an unstable state, per example, by losing referential integrity or leaving a series of queries unfinished.
Only you knows if a case like the above can happen. But if this data comes from a user in any way, I'd go with Murphy's Law on this one and not trust it.