Is casting user input to an integer sufficient to sanitize it? - php

Quoting from this SO answer:
Everything submitted is initially treated like a string, so forcing known-numeric data into being an integer or float makes sanitization fast and painless.
This was the sanitization method I had independently come up with for a quick and dirty query (looking up a name in a table from a numeric ID); the only variable being plugged into the query is the ID, and I know the ID should be greater than zero and less than 255, so my sanitization is as follows (with a little bit of validation thrown in too):
$id = (int)$_REQUEST['id'];
if ($id < 1 || $id > 255) errmsg("Invalid ID specified!");
$name = $DB->query("SELECT name FROM items WHERE id=${id}")->fetch_all()[0][0];
Is this sufficient to prevent SQL injection attacks or any other malicious attacks based on the user-specified value of $id, or can it still be exploited?
NOTE: the ID/name are not "sensitive", so if some input inadvertently casts to "1" or another valid ID value, I don't care. I just want to avoid antics along the lines of Little Bobby Tables.

The TL;DR answer is yes. When you cast to (int), you cannot get anything except an integer back.
The catch is you might have a use case where this can produce undesirable behavior. Let's take your code
$id = (int)$_REQUEST['id'];
Now, if we call this with
page.php?id=lolsqlinjection
Then the value of $id is 0 (because the string starts with a character, it will cast to 0 by default, see the PHP manual for various oddities with casting strings to integer). As such, any SQL injection is removed, making it safe from that attack vector. But you might have a use case where 0 is a special case, or another record. This is the reason prepared statements tend to be considered superior (showing MySQLi but you can do this with PDO as well)
$prep = $DB->prepare("SELECT name FROM items WHERE id=?");
$prep->bind_param('i', $_REQUEST['id']);
$prep->execute();
What this does is it tells your DB that you want the record that matches the input. As such, with my SQL injection, MySQL is now looking for an item with an integer id of "lolsqlinjection". No such record exists. Thus we avoid any potential edge cases where 0 would be a valid input.

it seems excessive to me to have to initialize, prepare, bind, execute, and fetch, all to get the value of a single column out of a single result.
Here's an example using PDO:
$stmt = $DB->prepare("SELECT name FROM items WHERE id=?");
$stmt->execute([$_REQUEST['id']);
$name = $stmt->fetchColumn();
Not so excessive, right? No need to initialize. No need to bind.
It's true that casting to an int is safe to prevent SQL injection. But I prefer to use query parameters all the time, so my code is consistent. I don't need to think about whether the parameter I'm searching for is actually an integer. I don't have to think about whether casting to (int) is safe for SQL injection.
Just use parameters.
It's safe for any data type.
It's easy to write the code.
It's easy for people to read your code.

In this case, this cannot be exploited since you are casting the the value of the request id to int, you are always going to get an integer, even if somephrase is sent, casting it to int will result 0 so this cannot be exploited.
However, using prepared statements is better, (not safer - both methods are safe), and the reason is to get used to it, so you don't need to cast or sanitize any given variable, by running a prepared statement you are sure that the values are being sanitized by the database driver and everything is safe. Again, this method of casting variables to int cannot be exploited.
Here's an example of how to validate the input in your case :
<?php
$id = (int) $_GET['id'];
if($id === 0 || !in_array($id,range(1,255))
{
if($id === 0 && (string) $_GET['id'] !== '0') {
// sql injection attempt ! ( or not ? )
} else {
// maybe an error
}
} else {
$result = $DB->query(...);
echo $result;
}

Yes, casting to an integer will sanitize your user input but it's an overkill and can lead to errors if you consider PHP's type juggling (all strings are translated to 0, but you can have also unexpected results if the input is a decimal number, e.g. (int) ((0.1+0.7) * 10); yields 7).
The Hitchhiker's Guide to SQL Injection prevention recommends to sanitize numbers by
using prepared statements
or
formatting them to contain only numbers, a decimal delimiter and a sign

Yes, casting the input to integer is sufficient to prevent against SQL injection attacks.
You should still validate the range of the result, as you did.

Your use case seems to be a step further form a simple "make sure this is an integer" test. I suggest you use the PHP tool for the job, filter_input():
filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT, [
"options" => [
"min_range" => 1,
"max_range" => 255
]
]);
This will save you an if (and an isset check) but at the cost of if it fails you don't know if it failed because it wasn't set or it wasn't in range.
As far as SQL injection is concerned then this should be sufficient.

You could do it like that but PHP already offers a facility for input param validation. Take a look at http://php.net/manual/en/filter.filters.php .
Here is an example:
$options = [
'options' => [
'default' => null,
'min_range' => 1,
'max_range' => 255,
]
];
if (isset($_REQUEST['id'])) {
$id = filter_var($_REQUEST['id'], FILTER_VALIDATE_INT, $options);
if ($id) {
// TODO:
}
}
Even with all this filtering you MUST use SQL parameter binding for whatever library you use to access database.

Technically - yes.
It doesn't mean, though, that you should have any idea to use it anywhere in production code.
Instead of the non-optimal code you posted here, try to improve it. For example, even a primitive database wrapper could make your code this
$name = $DB->run("SELECT name FROM items WHERE id=?", [$_REQUEST['id']])->fetchColumn();
which would be industry-standard safe, quick and dirty enough to fit your taste, and also flexible enough to be used with any other query or return format.

Related

Is SQL injection possible with int data type?

Currently I'm not using prepare or bind statement in my php code but I'm taking only int data for my search query so I just want to know where SQL injection is even possible with this query?
$enroll = mysqli_real_escape_string($conn, intval($_POST['enroll']));
$result = mysqli_query($conn, "SELECT * FROM Student_Data WHERE enroll=$enroll LIMIT 1");
I use intval() to take Int only input.
Thank you for the information.
In general, No It is not possible. But if your intension is to protect from SQL injection, why you should not use the better way which is proven? Overall, nothing can be guaranteed tomorrow. So better choice is to use the best practice what is available today.
Firstly, escaping is always context-dependent. The context mysqli_real_escape_string is designed for is inside a single-quoted string in an SQL query. If you are using the string in any other context, do not use that function. For instance, this is not secure:
$input = "a + ' + b";
$sql = "Select * From whatever Where something=" . mysqli_real_escape_string($conn, $input);
The result is this:
Select * From whatever Where something=a + '' + b
So you've gone from a syntax error to a valid, and user-controlled, query.
For that reason, applying both intval and mysqli_real_escape_string never makes sense: if you're using the value in an integer context, it's simply the wrong function, and a symptom of the dangerous mentality that "more escaping is better".
Secondly, understand what the functions you're using actually do: intval converts any PHP value, if it can, to an integer; then the . operator will always convert its operands to strings. So $anything . intval($input) is always converting $input first to an integer, and then to a string.
So, certainly we can predict exactly what characters are possible to end up in the query - digits 0 to 9, and the - sign. It's just about possible that some obscure bug in the database or driver could misinterpret those in such a way as to have unintended consequences, but it's pretty unlikely.
Thirdly, if you have the choice, just use parameters and the whole thing becomes irrelevant.
Remember that, since you no longer have the context it works with, you should not also use mysqli_real_escape_string.

PHP intval in sql query [duplicate]

I have an application that takes data via a POST request. I am using this data to insert a new row into the database. I know that using mysql_real_escape_string() (plus removing % and _) is the way to go for strings, but what about integer values? Right now, I am using the PHP function intval() on them.
However, I wanted to make sure that intval() is perfectly safe. I can't see a way of an attacker preforming a SQL injection attack when the variables are run through intval() first (since it always returns an integer), but I wanted to make sure this is the case from people that have more experience than I.
Thanks.
Yes, intval() is safe. There is absolutely no way to perform an SQL injection when the parameter is converted to integer, because (obviously) the format of an integer does not allow putting SQL keywords (or quotes, or whatever) in it.
The easiest way to prevent SQL injection is to always use prepared statments. Use the mysqli libraries or better yet an ORM such as doctrine etc.
Your queries then become something like:
$stmt = $db->prep_stmt("select * from .... where userid = ? and username = ?");
/* Binding 2 parameters. */
$stmt->bind_param("is", $userid, $username);
$userid = 15;
$username = "don";
/* Executing the statement */
$stmt->execute( ) or die ("Could not execute statement");
I always do
$var = (int)$_POST['var'];
to make sure $var is handled as an integer in all circumstances, and never look at $_POST['var'] again. Looking at the manual, intval() does exactly the same.
Whichever way you go, in subsequence $var will be an actual integer, which is quite safe to handle.
Prepared statment is best way to deal sql injection.
or use PDO
otherwise, intval is better than is_numeric

SQL injection: Is this line safe?

I have this line:
$sql = "UPDATE votes SET up=up+1 WHERE id='{$p}'";
Now from what I've read one way of sql injection is caused by not "closing" sql queries properly which allows hackers to add additional info.
So my question is, is that line safe as to me the up=up+1 has not been "closed" but if I set it like this up='up+1' which to me makes it "closed" it does not work.
row up type is int(11) if that makes any difference.
Update:
$p is sanitized with a function
function sanitize($foo) {
if(get_magic_quotes_gpc() == true) {
$foo = stripslashes($foo);
}
return mysql_real_escape_string(htmlspecialchars($foo));
}
The up=up+1 is not vulnerable because it does not accept user input via a variable. It is merely incrementing an INT value already in your database.
The WHERE id='{$p}' may be vulnerable, however, if the variable $p is not properly filtered and escaped, but as you added above, it has been sanitized by a function. Hopefully the sanitize function goes as far as checking the appropriate type of the variable (be it an int or string or whatever) and checks the appropriate bounds for it.
As always, the safest method is to make use of prepared statements and parameterized queries rather than pass any variables directly into a SQL string. Most of the database APIs available in PHP for the various RDBMS' support prepared statements. (Notably not the mysql_*() functions, however).
It is not safe, unless $p is properly escaped. Otherwise imagine...
$p = "foo'; DROP TABLE votes; SELECT '1"
Then you'd end up executing something like:
UPDATE votes SET up=up+1 WHERE id='foo'; DROP TABLE votes; SELECT '1';
That wouldn't be very pretty...

Replacing mysql_* functions with PDO and prepared statements

I've always done the simple connection of mysql_connect, mysql_pconnect:
$db = mysql_pconnect('*host*', '*user*', '*pass*');
if (!$db) {
echo("<strong>Error:</strong> Could not connect to the database!");
exit;
}
mysql_select_db('*database*');
While using this I've always used the simple method to escape any data before making a query, whether that be INSERT, SELECT, UPDATE or DELETE by using mysql_real_escape_string
$name = $_POST['name'];
$name = mysql_real_escape_string($name);
$sql = mysql_query("SELECT * FROM `users` WHERE (`name` = '$name')") or die(mysql_error());
Now I understand this is safe, to an extent!
It escapes dangerous characters; however, it is still vulnerable to other attacks which can contain safe characters but may be harmful to either displaying data or in some cases, modifying or deleting data maliciously.
So, I searched a little bit and found out about PDO, MySQLi and prepared statements. Yes, I may be late to the game but I've read many, many tutorials (tizag, W3C, blogs, Google searches) out there and not a single one has mentioned these. It seems very strange as to why, as just escaping user input really isn't secure and not good practice to say the least. Yes, I'm aware you could use Regex to tackle it, but still, I'm pretty sure that's not enough?
It is to my understanding that using PDO/prepared statements is a much safer way to store and retrieve data from a database when the variables are given by user input. The only trouble is, the switch over (especially after being very stuck in my ways/habits of previous coding) is a little difficult.
Right now I understand that to connect to my database using PDO I would use
$hostname = '*host*';
$username = '*user*';
$password = '*pass*';
$database = '*database*'
$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password);
if ($dbh) {
echo 'Connected to database';
} else {
echo 'Could not connect to database';
}
Now, function names are different so no longer will my mysql_query, mysql_fetch_array, mysql_num_rows etc work. So I'm having to read/remember a load of new ones, but this is where I'm getting confused.
If I wanted to insert data from say a sign up/registration form, how would I go about doing this, but mainly how would I go about it securely? I assume this is where prepared statements come in, but by using them does this eliminate the need to use something like mysql_real_escape_string? I know that mysql_real_escape_string requires you to be connected to a database via mysql_connect/mysql_pconnect so now we aren't using either won't this function just produce an error?
I've seen different ways to approach the PDO method too, for example, I've seen :variable and ? as what I think are known as place holders (sorry if that is wrong).
But I think this is roughly the idea of what should be done to fetch a user from a database
$user_id = $_GET['id']; // For example from a URL query string
$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
But then I'm stuck on a couple things, if the variable wasn't a number and was a string of text, you have to given a length after PDO:PARAM_STR if I'm not mistaken. But how can you give a set length if you're not sure on the value given from user in-putted data, it can vary each time? Either way, as far as I know to display the data you then do
$stmt->execute();
$result = $stmt->fetchAll();
// Either
foreach($result as $row) {
echo $row['user_id'].'<br />';
echo $row['user_name'].'<br />';
echo $row['user_email'];
}
// Or
foreach($result as $row) {
$user_id = $row['user_id'];
$user_name = $row['user_name'];
$user_email = $row['user_email'];
}
echo("".$user_id."<br />".$user_name."<br />".$user_email."");
Now, is this all safe?
If I am right, would inserting data be the same for example:
$username = $_POST['username'];
$email = $_POST['email'];
$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
VALUES (:username, :email)");
$stmt->bindParam(':username, $username, PDO::PARAM_STR, ?_LENGTH_?);
$stmt->bindParam(':email, $email, PDO::PARAM_STR, ?_LENGTH_?);
$stmt->execute();
Would that work, and is that safe too? If it is right what value would I put in for the ?_LENGTH_?? Have I got this all completely wrong?
UPDATE
The replies I've had so far have been extremely helpful, can't thank you guys enough! Everyone has got a +1 for opening my eyes up to something a little different. It's difficult to choose the top answer, but I think Col. Shrapnel deserves it as everything is pretty much covered, even going into other arrays with custom libraries which I wasn't aware of!
But thanks to all of you:)
Thanks for the interesting question. Here you go:
It escapes dangerous characters,
Your concept is utterly wrong.
In fact "dangerous characters" is a myth, there are none.
And mysql_real_escape_string escaping but merely a string delimiters. From this definition you can conclude it's limitations - it works only for strings.
however, it is still vulnerable to other attacks which can contain safe characters but may be harmful to either displaying data or in some cases, modifying or deleting data maliciously.
You're mixing here everything.
Speaking of database,
for the strings it is NOT vulnerable. As long as your strings being quoted and escaped, they cannot "modify or delete data maliciously".*
for the other data typedata - yes, it's useless. But not because it is somewhat "unsafe" but just because of improper use.
As for the displaying data, I suppose it is offtopic in the PDO related question, as PDO has nothing to do with displaying data either.
escaping user input
^^^ Another delusion to be noted!
a user input has absolutely nothing to do with escaping. As you can learn from the former definition, you have to escape strings, not whatever "user input". So, again:
you have escape strings, no matter of their source
it is useless to escape other types of data, no matter of the source.
Got the point?
Now, I hope you understand the limitations of escaping as well as the "dangerous characters" misconception.
It is to my understanding that using PDO/prepared statements is a much safer
Not really.
In fact, there are four different query parts which we can add to it dynamically:
a string
a number
an identifier
a syntax keyword.
so, you can see that escaping covers only one issue. (but of course, if you treat numbers as strings (putting them in quotes), when applicable, you can make them safe as well)
while prepared statements cover - ugh - whole 2 isues! A big deal ;-)
For the other 2 issues 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?
Now, function names are different so no longer will my mysql_query, mysql_fetch_array, mysql_num_rows etc work.
That is another, grave delusion of PHP users, a natural disaster, a catastrophe:
Even when utilizing old mysql driver, one should never use bare API functions in their code! One have to put them in some library function for the everyday usage! (Not as a some magic rite but just to make the code shorter, less repetitive, error-proof, more consistent and readable).
The same goes for the PDO as well!
Now on with your question again.
but by using them does this eliminate the need to use something like mysql_real_escape_string?
YES.
But I think this is roughly the idea of what should be done to fetch a user from a database
Not to fetch, but to add a whatever data to the query!
you have to given a length after PDO:PARAM_STR if I'm not mistaken
You can, but you don't have to.
Now, is this all safe?
In terms of database safety there are just no weak spots in this code. Nothing to secure here.
for the displaying security - just search this site for the XSS keyword.
Hope I shed some light on the matter.
BTW, for the long inserts you can make some use of the function I wrote someday, Insert/update helper function using PDO
However, I am not using prepared statements at the moment, as I prefer my home-brewed placeholders over them, utilizing a library I mentioned above. So, to counter the code posted by the riha below, it would be as short as these 2 lines:
$sql = 'SELECT * FROM `users` WHERE `name`=?s AND `type`=?s AND `active`=?i';
$data = $db->getRow($sql,$_GET['name'],'admin',1);
But of course you can have the same code using prepared statements as well.
* (yes I am aware of the Schiflett's scaring tales)
I never bother with bindParam() or param types or lengths.
I just pass an array of parameter values to execute(), like this:
$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->execute( array(':user_id' => $user_id) );
$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
VALUES (:username, :email)");
$stmt->execute( array(':username'=>$username, ':email'=>$email) );
This is just as effective, and easier to code.
You may also be interested in my presentation SQL Injection Myths and Fallacies, or my book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.
Yes, :something is a named placeholder in PDO, ? is an anonymous placeholder. They allow you to either bind values one by one or all at once.
So, basically that makes four options to provide your query with values.
One by one with bindValue()
This binds a concrete value to your placeholder as soon as you call it. You may even bind hard coded strings like bindValue(':something', 'foo') if desired.
Providing a parameter type is optional (but suggested). However, since the default is PDO::PARAM_STR, you only need to specify it when it is not a string. Also, PDO will take care of the length here - there is no length parameter.
$sql = '
SELECT *
FROM `users`
WHERE
`name` LIKE :name
AND `type` = :type
AND `active` = :active
';
$stm = $db->prepare($sql);
$stm->bindValue(':name', $_GET['name']); // PDO::PARAM_STR is the default and can be omitted.
$stm->bindValue(':type', 'admin'); // This is not possible with bindParam().
$stm->bindValue(':active', 1, PDO::PARAM_INT);
$stm->execute();
...
I usually prefer this approach. I find it the cleanest and most flexible.
One by one with bindParam()
A variable is bound to your placeholder that will be read when the query is executed, NOT when bindParam() is called. That may or may not be what you want. It comes in handy when you want to repeatedly execute your query with different values.
$sql = 'SELECT * FROM `users` WHERE `id` = :id';
$stm = $db->prepare($sql);
$id = 0;
$stm->bindParam(':id', $id, PDO::PARAM_INT);
$userids = array(2, 7, 8, 9, 10);
foreach ($userids as $userid) {
$id = $userid;
$stm->execute();
...
}
You only prepare and bind once which safes CPU cycles. :)
All at once with named placeholders
You just drop in an array to execute(). Each key is a named placeholder in your query (see Bill Karwins answer). The order of the array is not important.
On a side note: With this approach you cannot provide PDO with data type hints (PDO::PARAM_INT etc.). AFAIK, PDO tries to guess.
All at once with anonymous placeholders
You also drop in an array to execute(), but it is numerically indexed (has no string keys). The values will replace your anonymous placeholders one by one in the order they appear in your query/array - first array value replaces first placeholder and so forth. See erm410's answer.
As with the array and named placeholders, you cannot provide data type hints.
What they have in common
All of those require you to bind/provide as much values as you have
placeholders. If you bind too many/few, PDO will eat your children.
You don't have to take care about escaping, PDO handles that. Prepared PDO statements are SQL injection safe by design. However, that's not true for exec() and query() - you should generally only use those two for hardcoded queries.
Also be aware that PDO throws exceptions. Those could reveal potentially sensitive information to the user. You should at least put your initial PDO setup in a try/catch block!
If you don't want it to throw Exceptions later on, you can set the error mode to warning.
try {
$db = new PDO(...);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING)
} catch (PDOException $e) {
echo 'Oops, something went wrong with the database connection.';
}
To answer the length question, specifying it is optional unless the param you are binding is an OUT parameter from a stored procedure, so in most cases you can safely omit it.
As far as safety goes, escaping is done behind the scenes when you bind the parameters. This is possible because you had to create a database connection when you created the object. You are also protected from SQL injection attacks since by preparing the statement, you are telling your database the format of the statement before user input can get anywhere near to it. An example:
$id = '1; MALICIOUS second STATEMENT';
mysql_query("SELECT * FROM `users` WHERE `id` = $id"); /* selects user with id 1
and the executes the
malicious second statement */
$stmt = $pdo->prepare("SELECT * FROM `users` WHERE `id` = ?") /* Tells DB to expect a
single statement with
a single parameter */
$stmt->execute(array($id)); /* selects user with id '1; MALICIOUS second
STATEMENT' i.e. returns empty set. */
Thus, in terms of safety, your examples above seem fine.
Finally, I agree that binding parameters individually is tedious and is just as effectively done with an array passed to PDOStatement->execute() (see http://www.php.net/manual/en/pdostatement.execute.php).

how to cast bigint to prevent sql injection in php?

i am using php and running sql queries on a mysql server.
in order to prevent sql injections, i am using mysql_real_escape_string.
i am also using (int) for numbers casting, in the following manner:
$desired_age = 12;
$query = "select id from users where (age > ".(int)$desired_age.")";
$result = mysql_query($query);
that work.
But, when the variable contains larger numbers, casting them fails since they are larger than int.
$user_id = 5633847511239487;
$query = "select age from users where (id = ".(int)$user_id.")";
$result = mysql_query($query);
// this will not produce the desired result,
// since the user_id is actually being cast to int
Is there another way to cast large number (like BIGINT), except for the use of mysql_real_escape_string, when is comes to sql injection prevention?
If you are generating the user ID yourself there is no need to cast it for MySQL since there is no chance of SQL injection or other string issues.
If it is a user submitted value then use filter_var() (or is_numeric()) to verify it is a number and not a string.
You could use something like:
preg_replace('/[^0-9]/','',$user_id);
to replace all non numeric symbols in your string.
But there actually is no need to do so, simply use mysql_real_escape_string() as your integer value will be converted to a string anyway once $query is built.
Validate input. Don't just simply escape it, validate it, if it's a number. There're couple of PHP functions which do the trick, like is_numeric() - Finds whether a variable is a number or a numeric string
http://www.php.net/is_numeric
Use server-side prepared, parametrized statements (and thus remove the need for xyz_real_escape_string()) and/or treat the id as a string. The MySQL server has built-in rules for string<->number conversions and if you should decide to change to type/structure of the id field you don't have to change the php code as well. Unless you have concrete needs for (micro-)optimization there's usually no need to let the code make this kind of assumptions about the structure and value range of an id field in the database.
$pdo = new PDO('mysql:...');
$pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$stmt = $pdo->prepare('SELECT age FROM users WHERE id=?');
$stmt->execute(array('5633847511239487'));
After some research I've come to such setup
private function escapeInt($value)
{
if (is_float($value))
{
return number_format($value, 0, '.', ''); // may lose precision on big numbers
}
elseif(preg_match('/^-?[0-9]+$/', $value))
{
return (string)$value;
}
else
{
$this->error("Invalid value");
}
}
Separate case for the floats because $i = 184467440737095; become float on a 32-bit system and thus will crumble to scientific notation when cast to string.
And simple regexp for the rest
You can even multiply the variable by *1, you can check it min and max values you can accept (for age bigint is not an option at all... so why even allow numbers more than values you are prepared for?
And there is also PDO with its query preparing.

Categories