I have some links structured as follows...
http://domain.com?problem_id=23&course_id=4
The expected values from the GET "fields" (problem_id and course_id) are to be integers. Can I validate this data by simply saying...
if (is_numeric($_GET['problem_id'])){
//It's safe, so do stuff.
} else {
echo 'It appears you submitted a problem incorrectly. Please contact us for assistance';
exit;
}
Or is this still open to nastiness like sql injection, etc.?
PROPOSED SOLUTION
$int_problem_id = (int) $_GET['problem_id'];
if (ctype_digit($int_problem_id)){
//It's safe, so do stuff.
} else {
echo 'It appears you submitted a problem incorrectly. Please contact us for assistance';
exit;
}
Yes, it is a solution. Also, you can additionally cast to int.
$integer = (int) $_GET['problem_id'] ;
You should secure all the input for your database even though numeric values will do no harm as they do not contain special symbols.
You would have ensured that ?problem_id= is numeric. All of your other fields may still be at risk though, so this isn't the proper way of securing against SQL injection. You should look into PDO and MySQLi, and their bindParam/bind_params functions.
Related
I have code that looks like:
$id = (int) $_REQUEST['edit_id'];
$result = mysql_query("SELECT * FROM dis WHERE dis_id = $id ");
Can anyone do SQL injection in this code like I am using (int) to filter the raw data? If so, how can it be hacked and how can the hack be prevented?
This on itself won't hurt and this specific case will not lead to SQL injection.
However, teach yourself to always use prepared statements, to train your muscle memory and not take "shortcuts".
There’s nothing wrong with it, assuming there’s no way way to break the int filter. However, things break all the time (in this case there’s little to worry about). But I agree with the previous answer: use prepared statements so sanitization/validation is built in.
I usually don’t trust a single layer of security when I can make sure my data is sanitized/validated with a line or 2. I made a trait that checks for expected input, and reuse it from project to project if I’m patching something without prepared statements.
I usually create a bunch of custom php sanitisation scripts for my applications to sanitise incoming data and run these against the data just for peace of mind. For example...
function SanitiseIntegerData($data) {
if ( preg_match("/[^\d]/", $data ) == TRUE ) { //If data contains characters apart from integers
echo("ERROR: Invalid GET data");
$_SESSION = array();
session_destroy();
session_unset();
echo "<script>window.location='index.php'</script>";
}else{
return $data;
}
}
I have a table with a primary key called ID. ID will be passed to the page like:
testpage.php?ID=123.
If I use:
$sanitized_id = filter_input(INPUT_GET, 'ID', FILTER_SANITIZE_NUMBER_INT)
can $sanitized_id be safely used in a query?
It's close to being ready. Although SQL injection should be prevented by means of prepared statements, one still may want to have URLS strict and clean. and for this purpose your code is not enough. For example, FILTER_SANITIZE_NUMBER_INT doesn't remove minus signs.
Here's some code that I use. I pretty much just copy and pasted it from an existing project of mine. I just changed the ID and put in the echos.
if (empty($_GET['ID'])) {
echo('ID is empty');
exit;
}
$sanitized_id = filter_input(INPUT_GET, 'ID', FILTER_SANITIZE_NUMBER_INT);
$sanitized_id = str_replace('-', '', $sanitized_id); //Get rid of any - signs
if (($sanitized_id != $_GET['ID']) || (strlen($sanitized_id) != strlen($_GET['ID']))) {
//strlen catches if + signs are included, I'm really strict at what I allow on the URL
echo('Invalid url');
exit;
}
echo('The sanitized ID is: ' . $sanitized_id);
There are two very simple ways to make sure an entry will be an integer :
Casting :
$myInt = (int) $_GET['ID'];
Using intval() :
$myInt = intval($_GET['ID']);
The filter_input() function has great filters, particulary for sanitizing emails, urls, or IP addresses, but really, using it to sanitize an integer is overkill.
Instead of sanitisation, I would recommend going further and perform validation:
$id = filter_input(INPUT_GET, 'ID', FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
if ($id === null) {
die("Die, evil request, die!");
}
// post: $id is a valid integer
On top of this, I would still go for prepared statements anyway; in this code it might be obvious that ID must be a valid integer, but once the database code gets separated further from the request handling you will want some reassurance.
can $sanitized_id be safely used in a query?
NO.
Whatever input validation should have nothing to do with "using data in the query".
In a sanely designed application these layers (input validation and database interaction) should be separated, and lay too far from each other. And database layer should know absolutely nothing of the data origin, source, nature or any validations.
Thus, no matter which validations you performed at the entry point, at database level you have to use prepared statements anyway.
I've been working on a code that escapes your posts if they are strings before you enter them in DB, is it an good idea? Here is the code: (Updated to numeric)
static function securePosts(){
$posts = array();
foreach($_POST as $key => $val){
if(!is_numeric($val)){
if(is_string($val)){
if(get_magic_quotes_gpc())
$val = stripslashes($val);
$posts[$key] = mysql_real_escape_string($val);
}
}else
$posts[$key] = $val;
}
return $posts;
}
Then in an other file:
if(isset($_POST)){
$post = ChangeHandler::securePosts();
if(isset($post['user'])){
AddUserToDbOrWhatEver($post['user']);
}
}
Is this good or will it have bad effects when escaping before even entering it in the function (addtodborwhater)
When working with user-input, one should distinguish between validation and escaping.
Validation
There you test the content of the user-input. If you expect a number, you check if this is really a numerical input. Validation can be done as early as possible. If the validation fails, you can reject it immediately and return with an error message.
Escaping
Here you bring the user-input into a form, that can not damage a given target system. Escaping should be done as late as possible and only for the given system. If you want to store the user-input into a database, you would use a function like mysqli_real_escape_string() or a parameterized PDO query. Later if you want to output it on an HTML page you would use htmlspecialchars().
It's not a good idea to preventive escape the user-input, or to escape it for several target systems. Each escaping can corrupt the original value for other target systems, you can loose information this way.
P.S.
As YourCommonSense correctly pointed out, it is not always enough to use escape functions to be safe, but that does not mean that you should not use them. Often the character encoding is a pitfall for security efforts, and it is a good habit to declare the character encoding explicitely. In the case of mysqli this can be done with $db->set_charset('utf8'); and for HTML pages it helps to declare the charset with a meta tag.
It is ALWAYS a good idea to escape user input BEFORE inserting anything in database. However, you should also try to convert values, that you expect to be a number to integers (signed or unsigned). Or better - you should use prepared SQL statements. There is a lot of info of the latter here and on PHP docs.
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
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.