This question already has answers here:
How can I prevent SQL injection in PHP?
(27 answers)
Closed 9 years ago.
I've below code in one of my php files to fetch data from DB:
$products = $this->db->get_rows('SELECT * from products WHERE shop_id='.$_SESSION['shop_id'].'AND tags,title,text LIKE \'%'.$_POST['search'].'%\'');
Is it problematic? I mean LIKE operator can be injected?
Edited
please provide examples of injecting in this way
Any operator can be injected without binding.
$_POST['search'] = "1%'; DROP TABLE myTable LIKE '%";
Would make
.... AND tags,title,text LIKE '%1%'; DROP TABLE myTable LIKE '%%'
Read on how to bind parameters.
Of course this can be injected, you need to sanitize your input. Right now you are taking raw post data and inserting it into your SQL statement.
You should run your POST data through some sort of data sanitization, something like mysql_real_escape_string or the like
Or at least prepared statements. let server side code do the work for you.
Never, ever, use database queries like that, don't construct a string with variables and use it for database activities.
Construct a string that will later on be prepared and executed, by inserting the variables into the string, making them not act like "commands" but as "values".
You can do it like this:
$query = "SELECT * from products WHERE shop_id = :shopId;"; // An example, you can finish the rest on your own.
Now, you can prepare the statement (I recommend using PDO for this).
$statement = $db->prepare($query); // Prepare the query.
Now you can execute variables into the prepared query:
$statement->execute(array(
':shopId' => $_SESSION['shop_id']
));
If you're inserting or updating, then you would have wanted to do:
$success = $statement->execute(array(
':shopId' => $_SESSION['shop_id']
));
which stores a boolean in $success, or you can fetch the values from a result if you're SELECTing:
$statement->execute(array(
':shopId' => $_SESSION['shop_id']
));
$result = $statement->fetch(PDO::FETCH_ASSOC);
if($result )
{
// You can access $result['userId'] or other columns;
}
Note that you should actually make that be a function, and pass $shopId into the function, but not the session itself, and check if the session actually exists.
I recommend googling on how to use PDO, or take a look on one of my examples: How to write update query using some {$variable} with example
This is really bad. Pulling vars into an SQL statement without cleaning or checking them is a good way to get pwnd. There are several things that people can inject into code. Another injection method to watch out for, 1=1 always returns true.
$products = $this->db->get_rows('SELECT * from products WHERE shop_id='.$_SESSION['shop_id'].'AND tags,title,text LIKE \'%'.$_POST['search'].'%\'');
//This example expects no result from the table initially so we would blind attack the DB to pull the admin record.
$_POST['search'] = "-1\'; union all select * from users limit 1;";
Someone call pull up the top account in the database (like the admin).
$user_id = $this->db->get_rows('SELECT * from users WHERE email="'.$_POST['email'].'" and password="'.$_POST['password'].'"');
//This always returns true so now I'm the admin again
$_POST['password'] = "x\' or 1=1 limit 1";
You also want to be careful what you print on screen.
$user_id = $this->db->get_rows('SELECT * from users WHERE email="'.$_POST['email'].'" and password="'.$_POST['password'].'"');
A message that you echo that says "No user name exists for $_POST['email']" could be replaced with something else.
$_POST['email']=";
$fp = fopen('index.php', 'w');
fwrite($fp, \"header('Location: http://badwebsite.com;');\";
fclose($fp);";
index.php could now people to a different website entirely where an infected page exists or an infected page on the site.
If you're checking IDs do something like:
if(preg_match('!^[0-9]$!',$_POST['id'])){
$id = $_POST['id'];
} else {
//flush
}
or count for the number of possible records... if you're only expecting one and you get all of the records in the DB then it's an injection attempt.
if(is_numeric($_POST['id'])){
$id = $_POST['id'];
$count = mysql_result(mysql_query("select count(*) from users where id='$id''),0);
}
Related
I am using a PDO prepare statement to select the result.
My index.php file is:
include('operations.php');
$userprofileobj = new operations();
if(isset($_SESSION['user_email']))
{
$results = $userprofileobj->verify_user('account', $_SESSION['user_email'])->fetch(PDO::FETCH_ASSOC);
echo $results['username'];
}
My operations.php file is:
<?php
include('userclass.php');
class operations extends userclass
{
public function verify_user($table_name, $user_email)
{
$stmt = $this->con->prepare("select * from " . $table_name . " where username = :user_email");
$stmt->execute([
':user_email' => $user_email,
]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
I am trying to match email and the result should be fetched in index.php. But I got an error:
Fatal error: Call to a member function fetchColumn() on boolean in operations.php on line 84
Your result variable (besides being overwritten) is not what you think it is. It's a PDO statement.
Try this instead:
$stmt = $this->con->prepare("select * from ".$table_name." where username = :user_email");
$stmt->execute([
':user_email' => $user_email,
]);
if (false !== ($row = $stmt->fetchColumn()))
{
return $row;
}
However, this will only return the first column of the first row. Instead you probably want:
return $stmt->fetchAll(PDO::FETCH_ASSOC);
I changed $result to $stmt, because it's not a result. It's a Statement Object.
Original issues
In your original code (see below) you are overwriting it with the return from execute which is a Boolean.
// Old code (don't use this)
$result = $result->execute([
':user_email' => $user_email,
]);
//$result = TRUE|FALSE
if ($result->fetchColumn() !== false)
{
return $result;
}
And then you try to call a method of the Boolean, which, well, won't work. But the problems are deeper than just that. Let’s assume you don’t overwrite it.
// Old code (don't use this)
$result->execute([
':user_email' => $user_email,
]);
//$result = PDOStatment object.
if ($result->fetchColumn() !== false)
{
return $result;
}
Now the result is still your PDOStatement, which is good, but as I said, you're not saving the fetched data. This time you return the PDOStatement object. Which is not what you want.
Then further, as I stated earlier, if you do save it and return it, it's still probably not what you are after. Because fetchColumn() accesses only one row at a time and only one column.
But I have no way to know what you want. Maybe that is what you want? In that case your query is less than ideal. Maybe you just want to see if a user exists with a given email? In that case I would use this query.
$result = $this->con->prepare("SELECT id FROM ".$table_name." WHERE username = :user_email");
$result->execute([
':user_email' => $user_email,
]);
// There isn't any need to check it (see below)
return $result->fetchColumn();
PDOStatement::fetchColumn() returns a single column from the next row of a result set or FALSE if there are no more rows.
I can also tell by your stuff, that your database setup is probably wrong. That is, if you really need a dynamic table $table. The reason I can say this is you should not be duplicating any user data (or any data really, this is called normalization), and having the table dynamic implies that the email may exist separately in two (or more) tables.
If that is not the case then don't make it dynamic. Why is this an issue? Well, think what happens if a user changes their "email" now, because it exists in two tables (potentially). You'll have to update it in both places. But it's worse than that as it overcomplicates anything you do with the emails.
Without seeing the schema for your tables, I can only speculate on that, and how to fix it. But generally you would use a foreign key and associate the user record to that. Then using a JOIN you can access the email without duplication.
That said, there are a few cases where this may be acceptable, but I have no way to know if that is true in your case. Just a quick example would be a separate table for users and administrators (basically two-user systems).
Security
The last thing is be very very careful with this:
"select * from ".$table_name." where username = :user_email"
The issue here is it's open to SQL injection. Anytime you concatenate a variable into SQL, you open the door for injection attacks. Well, you may say I'm passing in a canned string account. Which is OK, but there is no validation at the point of failure. So maybe in five months you reuse this code and forget that you never validated the table name. Maybe not, but the fact remains that if user data could get into that argument, you have no protection whatsoever against injection on the table name. The possibility for it is there.
Something as simple as this:
public function verify_user($table_name,$user_email){
$allowed = ['account','users'];
if(!in_array($table_name, $allowed )) throw new Exception('Invalid table name');
}
See now it's virtually impossible to inject something into the table name. Further because it's in the same method (at the point of failure), you will never lose that protection. It's very easy to be in a rush latter and copy a piece of code change a few things and .... well you know.
Just my two cents.
UPDATE
So even if the chance is small that user input could get into $table you can not guarantee it 100%, because from within verify_user you have no way to know where the data came from, but you are trusting on faith that it's not user input. When it comes to SQL injection, you can't say well this is OK, because I will only call this method a certain way. It has to be 100% injection proof or as close as is humanly possible.
Why is this important, you ask? Imagine this.
$userprofileobj->verify_user('account --',$_SESSION['user_email']);
Those two little --s are like // in PHP, but for SQL, they comment out the rest of the line in SQL so your query becomes this.
"select * from account -- where username = :user_email"
Or (essentially)
"select * from account"
So we just modified what your query does. Now thankfully it's not really possible to run two queries at once in PDO. You can do it (with some work) in MySqli. But because of security reasons, they have mostly done away with this ability. The reason is this (or worse like creating database users).
$userprofileobj->verify_user('account; DROP TABLE account --',$_SESSION['user_email']);
Which, if you could do two queries, would do this:
SELECT * FROM account
DROP TABLE account
In any case this is dangerous stuff and is to be avoided at all costs. Being too lazy (and I am a lazy programmer, so don't take that wrong) to put a table name in is not an answer you want to give after your database has been compromised and you have exposed user data to a third party. It's just not an option.
All this does:
if(!in_array($table_name, ['table1', 'table2', ...])) throw new Exception('Invalid table name');
Is throw an error if "needle" $table_name is not in "haystack" - a canned list of table names. So if I do this (using our example above):
if(!in_array('account --', ['table1', 'table2', ...])) throw new Exception('Invalid table name');
It won't find account -- in our list of table1 and table2 and will blow up, thus preventing the injection attack.
$result = $this->con->prepare("select * from ".$table_name." where username = :user_email");
$result = $result->execute(..)
You're overwriting $result. The $this->con->prepare(..) sets $result to be a PDO Statement (See http://php.net/manual/en/pdo.prepare.php). A PDO Statement object has the method ->execute(...), which returns a boolean (true/false) as well as the ->fetchColumn() method. When you're doing your execute() you're overwriting your PDO Statement Object with the result of the execute() which is only a boolean and has no methods in it at all. As such that's why $result has no ->fetchColumn() method.
new to php and am enrolled on a course, so can ask tutor tomorrow if this is more complicated than i think it might be!
I have an sql query, and it works fine. But I am trying to add and 'and' in the select statement.
This is what I have at the minute
$query = "SELECT * from table1 where table1.age <= " . $_POST['min_age'] ;
I have a 'region' input on my linked html page and want results to be returned only if the min_age and region values match those inputted by the user.
I have tried adding an 'and where' but it doesn't work and I am not sure if it is because of the multiple "'s or if what I am trying to do needs a different method?
Thanks
If you need multiple conditions, just separate them with AND:
... WHERE table1.age <= ? AND table1.region = ?
No need to use WHERE again. Just like you wouldn't need to use if() more than once if you were writing a complex condition in PHP.
PS: This isn't directly related to your question, but you should get into the habit of not putting $_POST or $_GET variables directly into your SQL queries. It's a good way to get hacked! Ask your tutor about "SQL injection," or read my presentation SQL Injection Myths and Fallacies.
I know you're just starting out, but if you were training to be an electrician, you would place a high priority on learning how to avoid being electrocuted or how to avoid causing a fire.
Here's how I would write your query using mysqli. One advantage of using query parameters is you never need to worry about where you start and end your quotes.
$query = "SELECT * from table1 where table1.age <= ? AND table1.region = ?";
$stmt = $mysqli->prepare($query) or trigger_error($mysqli->error, E_USER_ERROR);
$stmt->bind_param("is", $_POST["min_age"], $_POST["region"]);
$stmt->execute() or trigger_error($stmt->error, E_USER_ERROR);
The other good habit I'm showing here is to always report if prepare() or execute() return an error.
If you must interpolate variables into your SQL, first make sure you protect the variables either by coercing the value to an integer, or else by using a proper escaping function like mysqli_real_escape_string(). Don't put $_POST variables directly into the string. Also you don't have to stop and restart the quotes if you use PHP's syntax for embedding variables directly in double-quoted strings:
$age = (int) $_POST["min_age"];
$region = $mysqli->real_escape_string($_POST["region"]);
$query = "SELECT * from table1 where table1.age <= {$age}
AND table1.region = '{$region}'";
This question already has answers here:
How can I prevent SQL injection in PHP?
(27 answers)
Closed 9 years ago.
I know i am not secure when i am using this code so anything i can add in my code?
I have tried my self sql injection they are somewhere working but not much as i dont have much knowledge about sql injection. but as hacker are more smart so they can really hack my website.
Url looks like this :
http://example.com/profile.php?userID=1
php
$userID = $_GET['userID'];
$userID = mysql_real_escape_string($userID);
$CheckQuery = mysql_query("SELECT * FROM tbl_user WHERE id='$userID'");
$CheckNumber = mysql_num_rows($CheckQuery);
if ($CheckNumber !== 1)
{
header("Location: tos.php");
}
I tried:
http://example.com/profile.php?userID=1'
which hide many things on site.
when i tried
http://example.com/profile.php?userID=1' UNION SELECT * FROM tbl_user; with havij it was hacked
Thanks :|
use mysqli::prepare or at least sprintf
mysql_query(sprintf("SELECT * FROM tbl_user WHERE id='%d'", $userID);
$db = new mysqli(<database connection info here>);
$stmt = $db->prepare("SELECT * FROM tbl_user WHERE id='?'");
$stmt->bind_param("id", $userID);
$stmt->execute();
$stmt->close();
Dont use mysql_* functionality at all.
Use PDO or mysqli.
http://php.net/PDO
http://php.net/mysqli
PDO will escape your data for you.
But for your current code:
$userID = $_GET['userID'];
$userID = mysql_real_escape_string($userID);
if(ctype_digit($userID))
{
$CheckQuery = mysql_query("SELECT * FROM tbl_user WHERE id='$userID'");
$CheckNumber = mysql_num_rows($CheckQuery);
if ($CheckNumber !== 1)
{
header("Location: tos.php");
}
} else {
// THE USER ID IS NOT ALL NUMBERS, CREATE AN ERROR
}
I know i am not secure when i am using this code
This statement is wrong.
As a matter of fact, this very code is pretty secure.
And none of the codes you provided below would do any harm. Why do you think it is not secure?
This way is not recommended, yes. And the way you are using to format your queries may lead to injection for some other query. But the present code is perfectly secure.
As long as you are enclosing every variable in quotes and escape special chars in it - it is safe to be put into query.
Only if you omit one these two rules (i.e. escape but don't quote or quote but don't escape) - you are in sure danger. But as long as you're following both, you're safe.
The only reason for "hacking" I can guess of is a single quote used in HTML context. In some circumstances it can "hide many things on the page". But for the SQL, with the code you posted here, it's harmless
Look, out of this link
http://example.com/profile.php?userID=1'
your code will produce such a query
SELECT * FROM tbl_user WHERE id='1\''
which is quite legit for mysql and will even return a record for id=1, as it will cast 1' to 1 and find the record. This is why there is no redirect to tos.php.
So, the problem is somewhere else.
either there is a code that does not follow the rules I posted above
or this problem is unrelated to SQL at all - so, you are barking wrong tree and thus still keep whatever vulnerability open
Most likely you have to echo your values out
u can try type casting the value
<?php
$CheckQuery = mysql_query("SELECT * FROM tbl_user WHERE id='".(int)$userID."'");
?>
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Best way to prevent SQL Injection in PHP
I just found that my website is vunerable.
Since it's connected to a DB and have functions like: Register, Change Password, Notices, etc... and SUPOSING it's fully vulnerable.
What should I look for into the code in order to start making it safe?
I mean, I did some researches and everywhere, everyone says different things about security.
"Use PDO."
"Use mysql_real_escape_string."
"Use addslashes."
What exactly should I look for??
"$_POST" and "$_GET" variables??
"$_SESSION" variables?
SQL querys?
$sql = "select * from user";
$sql = "update user set user="new_user_name";
$sql = "insert into user (user) values ('userid')";
What should I do in each case?
Please, help me to know what and where I must go.
Thank you.
Following are the points to be considered for making safe php application.
USE PDO or mysqli
Never trust any inputs. Consider every variable viz $_POST, $_GET, $_COOKIE, $_SESSION, $_SERVER as if they were tainted. Use appropriate filtering measure for these variables.
To avoid XSS attack use php’s builtin functions htmlentities,
strip_tags, etc while inserting the user input data into the
database.
Disable Register Globals in PHP.INI
Disable “allow_url_fopen” in PHP.INI
Don’t allow user to input more data than required. Validate input to
allow max number of characters. Also validate each field for
relevant datatypes.
Disable error reporting after Development period. It might give
information about database that’ll be useful to hackers.
Use one time token while posting a form. If token exist and matches
the form post is valid otherwise invalid.
Use parametrized database queries
Use stored procedures
You can google for each point for more details.
HOpe this helps
What you should look for: Any data send from the client/user. Sanitize/escape this data.
PDO can sanitize queries (using PDO::prepare) and supports multiple SQL systems.
For MySQL, use MySQLi. mysqli_real_escape_string is the function to use for sanitizing data if you are using MySQL.
None of the SQL queries you provided are actually vulnerable to SQL injection.
SQL injection vulnerabilities happen because SQL input is not properly escaped.
For example:
$sql = "select * from users where user_id =" . $_GET['user_id'];
Consider if I passed in the following:
http://some_server.com/some_page.php?user_id=123%20or%201=1
The query when executed would end up being:
select * from users where user_id = 123 or 1=1
To fix this, use parameterized queries:
$query = "select * from users where user_id = ?"
When you bind the user_id value to the query, the data access layer will escape the input string properly and the following would be executed:
select * from users where user_id = '123 or 1=1' which would not return any rows, preventing the injection
If using PHP and the mysql extension:
$sql = "select * from users where user_id = '" . mysql_real_escape_string($_GET['user_id']) . "'";
Keep in mind you need to escape ALL input that is going into a SQL query:
$sql = "select id_column from some_table where id = 1";
$stmt = mysqli_query($conn, $sql);
if($stmt === false) die(mysqli_error($conn) . "\n");
while($row = mysqli_fetch_assoc($conn, $stmt) {
$sql = "update some_other_table set some_value = 'new value' where some_column = '" . mysqli_real_escape_string($conn, $row['id_column']) . "'";
....
}
This is because values you select from the database might include characters that are not safe for execution in a SQL statement, like the name "O'Hara" or example.
}
I've been using PDO.
An example for that in your case:
<?php
$stmt = $dbh->prepare("insert into user (user) values (?)");
$stmt->bindParam(1, $name);
$name = 'ValueHere';
$stmt->execute();
?>
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.