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.
Related
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.
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.
I use PDO to access my MySQL database, and want to use IN. But sadly it don't seam to work with prepare, so I wrote this function
function is_numeric_array($array){
if(!is_array($array))
return is_numeric($array);
if(is_array($array))
foreach($array as $int)
if(!is_numeric($int))
return false;
return true;
}
Then used it like this
if(!is_numeric_array($_SESSION['story'])){
die("Error, array contains non-integers");
}
$query = "(";
for($i = 0; $i<count($_SESSION['story']); $i++)
$query .= $_SESSION['story'][$i].(count($_SESSION['story'])-1 != $i ? "," : "");
$query .= ")";
//Collect all data needed
$stories = openConnection() -> query("SELECT * FROM `stories` WHERE `id` IN {$query}") -> fetchAll();
I know it, looks ugly. But I don't want any SQL injects.
You don't really have to test for the input being numeric, because in MySQL, any string e.g. '123abc' in a numeric context (like being compared to an integer column id) implicitly takes only the digits and ignores the rest. A non-numeric string like 'abc' simply has the integer value 0 because there are no leading digits.
The point is, values are safe from SQL injection if you use query parameters. Whether the inputs come from $_SESSION or another source is irrelevant. $_SESSION is neither safe or unsafe with respect to SQL injection, it's how you pass the data to your query that matters.
I would also simplify the code to format the list of parameter placeholders:
$placeholders = implode(',', array_fill(1, count((array)$_SESSION['story']), '?'));
And forget about bindParam(), just pass the array to execute().
//Collect all data needed
$storyQuery = openConnection() -> prepare("SELECT * FROM `stories`
WHERE `id` IN ({$placeholders})");
$storyQuery -> execute((array)$_SESSION['story']);
$story = $storyQuery -> fetchAll();
Re your comment:
In PDO, you can use either named parameters like :id, or you can use positional parameters, which are always ? (but don't mix these two types in a given query, use one or the other).
Passing an array to execute() automatically binds the array elements to the parameters. A simple array (i.e. indexed by integers) is easy to bind to positional parameters.
If you use named parameters, you must pass an associative array where the keys of the array match the parameter names. The array keys may optionally be prefixed with : but it's not required.
If you're new to PDO, it really pays to read the documentation. There are code examples and everything!
$_SESSION is just a way to store data on server over a session. It's not direct related with SQL injection.
Even if it's a cookie or a session , the hash that I store is alphanumeric only, for security purposes. When I'm checking the cookie/session against any type of inject / modification , I use ctype_alnum:
if (ctype_alnum($_SESSION['value']))
// exec code
else
// trigger_error
This way, no matter who is setting the SESSION value (you or the client, if you give him the possibility to), there wont be any case in which non-alphanumeric chars like comma, quotes, double quotes or whatever will be inserted.
Hi im trying to fix a bug with my script. The problem is my get query still works with extra letters.
So, edit.php?id=1 works and so does edit.php?id=1hello (obviously it shouldnt). Whats happening?
$idtoedit = mysql_real_escape_string($_GET["id"]);
//Check if ID exists
$doesidexist = mysql_query("SELECT `id` FROM Data WHERE `id` = \"$idtoedit\"");
if (mysql_num_rows($doesidexist) == 0) {
die("<div class=\"alert alert-error\"><h4 class=\"alert-heading\">Error</h4><p>ID does not exist.</p><p><a class=\"btn btn-danger\" href=\"javascript:history.go(-1)\">Go Back</a></p></div></div></body></html>");
}
This is because of MySQL's "anything goes" handling of input, when converting strings to numbers.
The SQL query you build pass the input in as a string (the "s around it) so mysql will try to convert it to a number, if the column on the left hand side of the = has a number type (the id column in your example).
Check this section of the manual for further information.
mysql_real_escape_string only "cleans" the variable so it's safe to use in the query, however it doesn't cast it into an integer and it's not supposed to. You can use PDO to and parameterized queries to do it, otherwise you have to manually cast it yourself i.e.
$idtoedit = (int) mysql_real_escape_string($_GET["id"]);
PDO example:
$sth = $pdoObject->prepare('SELECT `id` FROM Data WHERE `id` = ?');
$sth->bindParam(1, $_GET["id"], PDO::PARAM_INT);
$sth->execute();
Edit:
Haven't really answered your question so to clarify:
Just like complex857 said, MySQL tries to convert it based on the column type, therefore casting 1hello results in an integer value of 1.
I am trying to execute this query using PDO:
select * from users where uuid = 0x1e8ef774581c102cbcfef1ab81872213;
I pass this SQL query to the prepare method of PDO:
select * from users where uuid = :uuid
Then I pass this hashmap to execute:
Array ( [:uuid] => 0x1e8ef774581c102cbcfef1ab81872213 )
It looks like this query is being executed on the mysql server, when I call fetchAll:
select * from users where uuid = '0x1e8ef774581c102cbcfef1ab81872213';
How can I execute the query without having PDO add the quotes around my hex?
Thanks,
Steve
Your value HAS to be inserted as a string, as it's far beyond (128bit) what can be represented as a normal number in PHP in both 64bit and 32bit editions.
e.g. skip the placeholders and embed it into the query string directly:
$uuid = '0x....';
$sql = "SELECT ... WHERE uuid = $uuid";
which means you lose the benefits of placeholders, and will have to deal with SQL injection mitigation directly.
You don't mention which DBMS you're using, but you might be able to get around it by exploiting your DBMS's casting functions, eg.
SELECT... WHERE uuid = CAST(:uuid AS uuid_type)
with this, even though it goes into the DB as a string, it'll be treated as a native uuid when push comes to shove.