Why mysql_real_escape_string() did not prevent hack? - php

I've a website that hacked today. Server logs returned something like this as hacker's tries:
www.site.com/notifications.php?PID=7&id=999999.9%20union%20all%20select%20%28select%20distinct%20concat%280x7e%2C0x27%2Cunhex%28Hex%28cast%28schema_name%20as%20char%29%29%29%2C0x27%2C0x7e%29%20from%20%60information_schema%60.schemata%20limit%201%2C1%29%2C0x31303235343830303536%2C0x31303235343830303536%2C0x31303235343830303536--
But I've used mysql_real_escape_string() in my code:
if (isset($_GET['id']) && $_GET['id'] != '') {
$id = mysql_real_escape_string($_GET['id']);
} else {
$id = '';
}
if ($id == '') {
$stmt = "SELECT * FROM tbln13 ORDER BY id DESC";
} else {
$stmt = "SELECT * FROM tbln13 WHERE id = $id";
}
$NewsResult = mysql_query($stmt) or die (mysql_error());
Why my website could not prevent this attack?

Because escape_string add slashes and such to quotes. You didn't have any quotes in your query, or the string they submitted.
Your query doesn't have a STRING in it, it appears to expect an int. If you expected an integer, you should have verified it was an int, or forced it to an int, before using it in a query. Escaping a value as a string, then using it as an int, won't work.
Switch to prepared statements in MySQLi or PDO.

The sql injected query looks like this
SELECT * FROM tbln13 WHERE id = 999999.9
union all select
(select distinct concat(0x7e,0x27,unhex(Hex(cast(schema_name as char))),0x27,0x7e)
from `information_schema`.schemata
limit 1,1),
0x31303235343830303536, 0x31303235343830303536, 0x31303235343830303536--
as you see, you were injected because you have just allowed this!
You expected a number but you didn't check for it! So you got the number and something more.
You should have checked the $id variable for what you expected, which is the number. This is what I would use:
if (!preg_match('/^\d+$/', $id))
die("ERROR: invalid id"); // error, don't continue

Use prepared statements, that will, in most cases, prevent SQL injections.
A simple and comprehensible guide to prepared statements can be found in this website:
Bobby Tables
More over you should stop using MYSQL, it's outdated and will be removed in future implementations. Use MySQLi or PDO instead.

Because your escaped variable is not a string therefore it is not inside quotes in your query. If you want a quick fix you can change your query to:
$stmt = "SELECT * FROM tbln13 WHERE id = '$id'";
It is not standard use for numeric comparison but should work.

As mentioned by others, you should ditch deprecated mysql_* functions and instead used prepared statements via mysqli or PDO.
Even then you should also be validating your input, because just using prepared statements will not help you identify whether you have input values that are valid. You would ideally make sure all your input is valid before even attempting a prepared statement. In this case, this validation could be as simple as this:
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if (false === $id) {
// you do not have a valid integer value passed. Do something.
} else {
// continue with your prepared statement
}

Related

My MySQL prepared statement won't work

I have a MySQL statement that won't work for me. I've checked several parts of the code but it keeps returning null as the result. I've also tried replacing the WHERE enc_mail = AND enc_public_id=" to "WHERE 1" to check if it was a problem with the variables, but it is not. I did not get errors either.
$connect_db = mysqli_connect("myhost","my username","my password","my db");
$mail_id = crypto(mysqli_real_escape_string($connect_db,htmlspecialchars($_GET['em'])),'e');
$public_id = mysqli_real_escape_string($connect_db,htmlspecialchars($_GET['public']));
$active_true = true;
$check = $connect_db->prepare("SELECT active FROM enc_data WHERE enc_mail=? AND enc_pub_id=?");
$check->bind_param("ss", $mail_id, $public_id);
$active = $check->execute();
if($active[0]=="" ){
//It goes here once the code is run
}
You need to apply bind_result and then fetch
Also there is absolutely no reason to escape_string when using prepared statements as #GrumpyCrouton said
i would recommend you switch to PDO as it is more straightforward
I agree with #Akintunde that you should NOT use escaping and htmlspecialchars on query parameters. Escaping is redundant when you use query parameters. htmlspecialchars is just when you output content to HTML, not for input to SQL.
You don't necessarily have to use bind_result() for a mysqli query. You can get a result object from the prepared statement, and then use fetch methods on the result object to get successive rows.
Here's how I would write your code:
// makes mysqli throw exceptions if errors occur
mysqli_report(MYSQLI_REPORT_STRICT);
$connect_db = new mysqli("myhost", "my username", "my password", "my db");
$mail_id = $_GET['em'];
$public_id = $_GET['public'];
$active_true = true;
$sql = "SELECT active FROM enc_data WHERE enc_mail=? AND enc_pub_id=?";
$stmt = $connect_db->prepare($sql);
$stmt->bind_param("ss", $mail_id, $public_id);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
if($row["active"]=="" ){
//It goes here once the code is run
}
}
But in fact I would prefer to use PDO instead of mysqli, so I guess that's not really how I would write the OP's code. :-)

Why is a mysqli QUERY working, but the same PREPARED statement version returning an SQL syntax error?

OK, so I have gone round and round with this now for 2 hours and cannot figure out where the so-called SQL syntax error is. I finally re-wrote the prepared statement as a standard query - and it works fine, literally identical syntax.
Prepared Statement Code: (NOT working)
if ($account_info = $mysqli->prepare("SELECT users.specid, users.username ?
FROM users ? WHERE users.id = ?")) {
//A SWITCH to determine bind_param and bind_result
} else {
//Error output
}
The above results in the following MYSQL error:
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 '? FROM users ? WHERE users.id = ?' at line 1
Now if I literally change the '?' to $variables and make the prepared statement into a normal query like:
if ($account_info = $mysqli->query("SELECT users.specid, users.username $param1
FROM users $param2 WHERE users.id = $param3")) {
//Fetch array and set variables to results
} else {
//Error output
}
The above code WORKS as expected with no errors.
For those curious what the $variables are in the specific switch case I'm testing:
$param1 = ', tenants.paper';
$param2 = ', tenants';
$param3 = $_SESSION['user_id'].' AND tenants.id = users.specid';
So why does one work but not the other when they have the same syntax??? It doesn't even get to the bind_param part!? I'd prefer to use the prepared statement method.
You can't pass object nane (tablename or columnname ) as param .
So users.username ? and users ? as you are trying to use are wrong ..
passing param is not a string substituition ..
This kind of action are disallowed by param binding
and you should avoid this ..but if you really need then try with string concatenation
You only bind values for parameter bindings. Not parts of SQL. ::bind_param
What you are trying to do with $param1 = ', tenants.paper'; is already SQL injection. Prepared statements are build to prevent this.
You should make a method per query instead of a generic query.
You cannot bind complex query parts and columns in a query. I also don't understand why you need to parametrise strings you explicitly set in your code.
Do this instead:
$param = $_SESSION['user_id'];
if ($account_info = $mysqli->prepare("SELECT users.specid, users.username, tenants.paper
FROM users JOIN tenants ON tenants.id=users.specid WHERE users.id = ?")) {
//A SWITCH to determine bind_param and bind_result
} else {
//Error output
}
If you (at any point in the future) need to escape column names from user input (though you shouldn't allow users such power to begin with) do this:
$columnNameFromUserInput = $_GET["column"];
$columnNameFromUserInput = "`".str_replace("`","",$columnNameFromUserInput)."`";
This should be enough.
Do not put query segments that have parts that need escaping in a variable. Put the parts that need escaping in their own separate variables so you can bind them is the whole idea here.
Example:
$param1 = ', tenants.paper'; //Bad has a comma in it, should be `tenants`.`paper` and the comma should go in the query itself
$param2 = ', tenants'; //Bad, though you have to use JOIN in any SQL language after 1992
//The next part is very very bad.
// You have something that needs escaping mixed with things that compose a query. Split them.
$param3 = $_SESSION['user_id'].' AND tenants.id = users.specid';

Having hard time solving sql injection php

The following code I used for a image thumbnail when clicked it gets executed by taking the it "ID " from the database.
echo '<a class="thumbnail" href="view.php?id='.$row['id'] .'"">';
The code below actuality handle the GET variable passed through the above code.
<?php
require '../header.php';
if (isset($_GET['id']))
{
require '../../functions/function_db.php';
$id =mysql_real_escape_string (htmlentities($_GET['id']));
$sql = "SELECT * FROM `site_products` WHERE `id` = $id LIMIT 1";
$result = mysql_query($sql);
while ($row = mysql_fetch_assoc($result))
{
$product_name = $row['product_name'];
$price = $row['final_price'];
$desc = $row['short_description'];
}
}
?>
In spite of using mysql_real_escape_string the URL becomes SQL injection vulnerable in following scenario .
http://localhost/cart/pages/men/view.php?id=1'
http://localhost/cart/pages/men/view.php?id=1 orderby 1
and the webpage gives following mysql error.
Warning: mysql_fetch_assoc() expects parameter 1 to be resource, boolean given
How to solve this ???
Updated because off an comment from Jason McCreary
prepared statements are more safe but always force an type when you bind the values.
But you still need to watch out for second order SQL injections they are still possible then even if you make use off prepared statements
Id should be an integer just cast it to an int and filter out NULL bytes, NULL bytes are also evil things
$id = (int)(str_replace("\0", "", $_GET['id']));
$id =mysql_real_escape_string (htmlentities($_GET['id']));
$sql = "SELECT * FROM `site_products` WHERE `id` = $id LIMIT 1";
This is a common misuse of mysql_real_escape_string(). The function is only for escaping single quoted strings for MySQL queries. Single quotes (apostrophes) should always go around the return value. And why htmlentities() here?
Cast the value to an integer instead (e.g. $id = (int)$_GET['id'];, having the effect of keeping only digits 0-9), or put single quotes around the escaped value, or better yet, switch to mysqli or PDO prepared statements.
See also: How can I prevent SQL injection in PHP?

Is this vulnerable to SQL injection

I realize there are a lot of questions already about this. But my method isn't the same as theirs, so I wanted to know. I think I understand SQL, but I don't want to risk making a mistake in the future, so thanks for any help. (This is just a project I'm doing, not homework or anything important).
function checkLogin($username, $password) {
$username = strtolower($username);
connectToDatabase();
$result = mysql_query("SELECT * FROM `users` WHERE username='$username'");
$dbpassword = "";
while($row = mysql_fetch_array($result))
{
$rowuser = $row['username'];
if($username != $row['username']) continue;
$dbpassword = $row['password'];
}
if($dbpassword == "") {
return false;
}
$genpass = generatePassword($password);
return $genpass == $dbpassword;
}
So hit me with your best shot :)
And I don't think my method is as efficient as it could be. I don't understand php enough to understand what $row = mysql_fetch_array($result) is doing unfortunately.
Because you are taking an arbitrary string and placing it directly into an SQL statement, you are vulnerable to SQL injection.
( EDITED based on a comment below. )
The classic example of SQL injection is making a username such as the following:
Robert'); DROP TABLE users;--
Obligatory XKCD link
Explanation:
Given the "username" above, interpolation into your string results in:
SELECT * FROM `users` WHERE username='Robert'); DROP TABLE users;--'
The comment symbol -- at the end is required to "get rid" of your closing quote, because I just substituted one of mine to end your select statement so that I could inject a DROP TABLE statement.
As #sarnold pointed out, PHP's mysql_query only executes a the first query in the string, so the above example (known as query stacking) does not apply. The function is explained here: http://php.net/manual/en/function.mysql-query.php.
A better example can be found here. Here they use a username of
' OR 1 OR username = '
which interpolated becomes
SELECT * FROM `users` WHERE username='' OR 1 OR username = ''
and which would cause your application to retrieve all users.
The short answer is yes.
A perhaps more helpful answer is that you should never trust user input; prepared statements are the easiest way to protect against this, if you have PDO available. See PDO Prepared Statements
<?php
$stmt = $dbh->prepare("SELECT * FROM `users` WHERE username=?");
if ($stmt->execute($username)) {
while ($row = $stmt->fetch()) {
print_r($row);
}
}
?>
The other answers are an excellent description of your problem, however, I think they both overlook the best solution: use PHP's PDO Prepared Statements for your queries.
$stmt = $dbh->prepare("SELECT * FROM users where username = ?");
if ($stmt->execute(array($username))) {
while ($row = $stmt->fetch()) {
print_r($row);
}
}
This is a small, simple example. There are more sophisticated ways of using PDO that might fit your application better.
When you use PDO prepared statements you never need to manually escape anything and so long as you use this slightly different style, you will never write an SQL injection vulnerability and you don't have to maintain two variables per underlying "data" -- one sanitized, one as the user supplied it -- because only one is ever required.
I would say yes it is open to SQL injection.
This is because you are taking user input in the form of $username and putting it into your SQL statement without making sure it is clean.
This is a function that I like to use in my applications for the purpose of cleaning strings:
function escape($data) {
$magicQuotes = get_magic_quotes_gpc();
if(function_exists('mysql_real_escape_string')) {
if($magicQuotes) {
$data = stripslashes($data);
}
$data = mysql_real_escape_string($data);
}
else {
if(!$magicQuotes) {
$data = addslashes($data);
}
}
return $data;
}
Then you can use it like this:
$username = escape(strtolower($username));
connectToDatabase();
$result = mysql_query("SELECT * FROM `users` WHERE username='$username'");

How to use mysql_real_escape_string function in PHP

So in this program I'm writing, I actually grab a SQL query from the user using a form. I then go on to run that query on my database.
I know not to "trust" user input, so I want to do sanitization on the input. I'm trying to use mysql_real_escape_string but have been unsuccessful in getting it to work.
Here's what I'm trying, given the input:
select * from Actor;
//"query" is the input string:
$clean_string = mysql_real_escape_string($query, $db_connection);
$rs = mysql_query($clean_string, $db_connection);
if (!$rs)
{
echo "Invalid input!";
}
This is ALWAYS giving me the
"Invalid input!"
error.
When I take out the clean_string part and just run mysql_query on query, the
"invalid
input"
message is not output. Rather, when I do this:
$rs = mysql_query($query, $db_connection);
if (!$rs)
{
echo "Invalid input!";
}
It does NOT output
"invalid input".
However, I need to use the mysql_real_escape_string function. What am I doing wrong?
Update:
Given
select * from Actor; as an input, I've found the following.
Using echo statements I've
found that before sanitizing, the string holds the value:
select * from Actor;
which is correct. However, after sanitizing it holds the incorrect
value of select *\r\nfrom Actor;, hence the error message. Why is
mysql_real_escape_string doing this?
use it on the actual values in your query, not the whole query string itself.
example:
$username = mysql_real_escape_string($_POST['username']);
$query = "update table set username='$username' ...";
$rs = mysql_query($query);
Rather than using the outdated mysql extension, switch to PDO. Prepared statement parameters aren't vulnerable to injection because they keep values separate from statements. Prepared statements and PDO have other advantages, including performance, ease of use and additional features. If you need a tutorial, try "Writing MySQL Scripts with PHP and PDO".
mysql_real_escape_string() is the string escaping function. It does not make any input safe, just string values, not for use with LIKE clauses, and integers need to be handled differently still.
An easier and more universal example might be:
$post = array_map("mysql_real_escape_string", $_POST);
// cleans all input variables at once
mysql_query("SELECT * FROM tbl WHERE id='$post[id]'
OR name='$post[name]' OR mtime<'$post[mtime]' ");
// uses escaped $post rather than the raw $_POST variables
Note how each variable must still be enclosed by ' single quotes for SQL strings. (Otherwise the escaping would be pointless.)
You should use mysql_real_escape_string to escape the parameters to the query, not the entire query itself.
For example, let's say you have two variables you received from a form. Then, your code would look like this:
$Query = sprintf(
'INSERT INTO SomeTable VALUES("%s", "%s")',
mysql_real_escape_string($_POST['a'], $DBConnection),
mysql_real_escape_string($_POST['b'], $DBConnection)
);
$Result = mysql_query($Query, $DBConnection);
manual mysql_real_escape_string()
Escapes special characters in a string
for use in an SQL statement
So you can't escape entire query, just data... because it will escape all unsafe characters like quotes (valid parts of query).
If you try something like that (to escape entire query)
echo mysql_real_escape_string("INSERT INTO some_table VALUES ('xyz', 'abc', '123');");
Output is
INSERT INTO some_table VALUES (\'xyz\',
\'abc\', \'123\');
and that is not valid query any more.
This worked for me. dwolf (wtec.co)
<?php
// add data to db
require_once('../admin/connect.php');
$mysqli = new mysqli($servername, $username, $password, $dbname);
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
$post = $mysqli->real_escape_string($_POST['name']);
$title = $mysqli->real_escape_string($_POST['message']);
/* this query with escaped $post,$title will work */
if ($mysqli->query("INSERT into press (title, post) VALUES ('$post', '$title')")) {
printf("%d Row inserted.\n", $mysqli->affected_rows);
}
$mysqli->close();
//header("location:../admin");
?>

Categories