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.
Related
so I have a mysql query that looks like this.
$copy = mysql_query("SELECT `id` FROM `brain` WHERE `id` = '$user_screen_name' && '$posts['title']'");
I want the query to search for the id that is in the table where screen name and post title strings are matched and put the found id in a variable. How would I do this?
First, some warnings:
Please stop using mysql_* functions. These extensions have been removed in PHP 7. Learn about prepared statements for PDO and MySQLi and consider using PDO, it's really pretty easy.
Little Bobby says your script is at risk for SQL Injection Attacks.. Even escaping the string is not safe!
To fix your query you need a separate AND condition for each item you want to filter against:
WHERE `id` = '$user_screen_name'
AND `title` = '$posts["title"]'
Without seeing your table layout it would be hard to go much further but if you want the id in a variable you would do this after the query:
$row = mysql_fetch_array($copy);
Once done, $row['id'] will be the variable containing the id.
Add PDO::PARAM_INT or PDO::PARAM_STR have any meaning in Mysql query?
$sql = 'SELECT TagId FROM tagthread WHERE ThreadId = :ThreadId';
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':ThreadId', $threadid, PDO::PARAM_INT);
$stmt->execute();
Yes, use it.
I did a few tests (with PDO::ATTR_EMULATE_PREPARES false) and I found out that the quotes around the values will be different.
When you bind an integer value with PARAM_INT there will be no quotes in the query (A string value with PARAM_INT has quotes). If you bind an integer value with PDO::PARAM_STR there will be quotes and mysql has to cast to integer.
Examples:
$stmt->bindParam(':ThreadId', $threadid, PDO::PARAM_INT);
$threadid = 123;
// SELECT TagId FROM tagthread WHERE ThreadId = 123
$threadid = '123test';
// SELECT TagId FROM tagthread WHERE ThreadId = '123test'
// mysql will cast 123test to 123
EDIT:
I further tested and read on that topic. Conclusion: Implicit casting is dangerous and can lead to unexpected results. Read more on that here. Another disadvantage to always use PDO::PARAM_STR is the performance. Read more on performance Disadvantages of quoting integers in a Mysql query?
So if your column is of type [TINY|SMALL|MEDIUM|BIG]INT than use PARAM_INT. And in case it is a LIMIT clause than cast to integer if the variable type in PHP is not integer.
Edit: Depends! See Your Common Sense comment below.
If the value is a integer it should be treated as an integer.
Apply this with as many datatypes as possible.
If you don't set the Attribute of PDO::ATTR_EMULATE_PREPARES to false, you will get a nasty error.
Solid example:
$stmt = $dbh->prepare("SELECT * FROM table123 WHERE raw_field = :field LIMIT 1 OFFSET :offset;");
$stmt->bindParam(':field', $field);
$stmt->bindParam(':offset', $offset);
if ($map_stmt->execute())
{
$data = stmt->fetch(PDO::FETCH_ASSOC);
}
else
{
echo 'Error :';
echo '<pre>';
print_r($map_stmt->errorInfo());
print_r($map_stmt->debugDumpParams());
echo '</pre>';
}
Will return back a nasty error containing:
Error Code: 1064 You have an error in your SQL syntax; check the
manual that corresponds to your MySQL server version for the right
syntax to use near ''0'' at line 1
Query: SELECT * FROM table123 WHERE raw_field = 'home' LIMIT 1 OFFSET '0'
Useless you treat it as an integer, and it will remove the string (e.g.: ' ').
$stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
In a nutshell:
You choose! Strict data or not..
I cannot tell for all the drivers supported by PDO, but for mysql it's ok not to use PDO::PARAM_INT most of time.
Therefore, it makes no sense to bloat your code with numerous bindParam calls. As a rule, just send your variables directly into execute():
$sql = 'SELECT TagId FROM tagthread WHERE ThreadId = ?';
$stmt = $this->db->prepare($sql);
$stmt->execute([$threadid]);
Here your $threadid variable will be silently bound as a string, but it will make not a single problem for mysql to compare it with integer value stored in database. In reality, everyone does it this way and never has any problem.
The problem with string type bindnig in LIMIT clause can be easily solved by switfhing the emulation mode OFF.
Note that PDO::PARAM_INT doesn't cast your value. Means if you're trying to bind a string type value using this mode, it will be bound as a string nevertheless, even if you explicitly set type to PDO::PARAM_INT. This mode will be actually applied only for integer values.
There are few edge cases where you may want to bind an integer explicitly though:
peculiar column types, like BIGINT or BOOLEAN that require an operand of exact type to be bound (note that in order to bind a BIGINT value with PDO::PARAM_INT you need a mysqlnd-based installation).
some DBAs claim that complex queries with non-trivial query plan can be affected by a wrong operand type. though noone provided a verifiable example yet
All other issues are common for the loose typing and neither mysql nor PDO binding has any special effect in them.
Also, to avoid possible problems you should choose right column types for your data. Say, for big integers you should use BIGINT, while for any price-like data it have to be DECIMAL. And there will be not a single issue with comparison.
I've been urging to know what is the difference between using bindValue and execute(array()) thing-y.
Well let's say I have this example of code
$query = $db->prepare("SELECT embedded_page.uid FROM embedded_page WHERE fbp_id = :fbp_id");
$query->bindValue(":fbp_id", $fbp_id, PDO::PARAM_INT);
$query->execute();
What is the difference between this one?
$query = $db->prepare('SELECT `embedded_page`.`uid`, `embedded_page`.`ticket_id`, `embedded_page`.`event_table` FROM `embedded_page` WHERE `fbp_id` = ?');
$query->execute(array($fbp_id));
Regardless of saving a line of code.
A help would be appreciated.
It is already self explained with your example, That bindValue validate input data type in form of specially defined PDO::PARAM_INT or similar. While in second example no such condition in place as prepare using ?. (Some internal validation may be done by engine assuming for string, int and float types.). Also in case of more variables as place holders in query first form is easier to understand.
I have posted about this before but never in this regard so please take a look:
I was told one way to do sql injections was to use 1=1 where someone can see all entries that don't belong to them.
But lets say i structure my query so that it also selects the user_id of the current user, would that work:
$userid = Current users stored id in database;
$postid = mysql_real_escape_string($_GET['id']);
And now lets assume that i enter: domain.com/page.php?id='' OR '1'='1'
Select article_name from table where user_id=$userid and post_id=$postid
Will the query still return everything or will it not since i have added the User_id barrier?
If you use PDO you don't have to worry about escaping data (in this situation):
$stmt = $pdo->prepare('SELECT article_name FROM table WHERE user_id = :userid AND post_id = :postid');
$stmt->execute(array(
':userid' => $userid,
':postid' => intval($_GET['id']) //Just to be safe
));
// You could also do this instead (thanks #Digital Precision)
//$stmt->bindValue(':postid', $_GET['id'], PDO::PARAM_INT);
//$stmt->execute(array(':userid' => $userid));
while($row = $stmt->fetch()) {
//Work with data
}
For more on PDO see the PHP docs.
The problem with using mysql_real_escape_string() is that as its name suggests it only escapes strings. It escapes the characters that can be used to terminate a string so that an attacker can't close a string and enter malicious SQL.
If you are stubborn and refuse to use PDO, you could use a function like intval() on any unsanitized integers to ensure they contain only numbers.
$post_id = intval($_GET['id']); //Now $post_id can only be a number
mysql_real_escape_string() is for sanitizing strings only. It does NOT protect from SQL injection in integers that are not wrapped in quotes, so your observation is correct: What is shown above is indeed not safe despite mysql_real_escape_string().
You need to either wrap your values in quotes:
Select article_name from table where user_id='$userid' and post_id='$postid'
or make sure that $userid and $postid are integers before running the query.
Not sure what you mean by "I was told one way to do sql injections was to use 1=1 where someone can see all entries that don't belong to them".
1=1 always evaluates to true. I've only ever seen this done when the query being generated by the application has only conditional where clauses with no root where clause. Not sure what it has to do with protecting you from sql injections.
Your query would look like:
Select article_name from table where user_id=$userid and post_id=\'\' OR \'1\'=\'1\'
As other mention while i typing this, it is better to quote your values. So you will have:
Select article_name from table where user_id=$userid and post_id='\'\' OR \'1\'=\'1\''
This returns nothing, if there is not a post with such id.
So your query will not return every post from the current user. But keep in mind to quote your values.
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.