Is prepared statement necessary when dealing with trusted data fetched from another query?
For example.
When a user is navigating throughout the site, they click named links like this: /?category=health where health is the value that is sent to the database.
In this scenario I of course use prepared statement like this:
$qry = $dbh->prepare('SELECT category_id, and, other, columns FROM categories WHERE query_value = ?');
$qry->execute([$_GET['category']]);
$get = $qry->fetch();
$qry = null;
But further down the script, I would display content associated with the selected category based on the categories.category_id fetched from the last query.
$Banners = $dbh->query('SELECT image FROM Banners WHERE category_id = '.$get['category_id'])->fetchAll();
I would like to think that this is a secure query.
That the value could be no other than a trusted value since it has to be a result from the previous query?
And this query won't be executed if the previous query doesn't return true.
Here's how I've done it so far:
It's a 3-liner. But it would speed up the coding part a bit if I was certain that the 1-liner above is fine too.
$qry = $dbh->prepare('SELECT image FROM Banners WHERE category_id = ?');
$qry->execute([$get['category_id']]);
$Banners = $qry->fetchAll();
What this all is about largely is about ensuring your SQL syntax is sane and what you think it is. Preventing malicious attacks is just a corollary of that. What if your category ids in the future spawn apostrophes or whatnot as part of the regular value? You need escaping for correct syntax then anyway, or, better, prepared statements.
Secondarily, there is second order injection, in which a value which was previously treated as unsafe is now suddenly treated as safe, even though it still has the potential for attacks. E.g. if your values legitimately contain apostrophes.
More broadly speaking: how do you ensure a value is "safe"?
// $get['category_id'] is safe and doesn't need escaping
'SELECT image FROM Banners WHERE category_id = '.$get['category_id']
Well, how do you know this? What code ensures this value is safe? Where does this value come from? Are you sure that's where it comes from? You're outsourcing the integrity of this query to some other part of the code here. How can you be sure that other part does its job correctly? Now, or in the future, after a lot of refactoring? Simply ensure yourself that you are producing correct queries by writing queries in a secure way, don't outsource your security.
You need to ask yourself: is this query prone to injection the way it is written? And the answer to the above example is a plain yes, it is prone to injection, and the safety merely depends on your trust that $get['category_id'] is what you think it is.
Related
I've watched Computerphile's video many times on this subject(for any of you who want, this is the link: https://www.youtube.com/watch?v=_jKylhJtPmI). He provides some really good advice on how to combat SQL Injection and make your app more effective. These are the key points from his video:
Don't use straight and unprotected SQL commands because this is the way hackers can perform a SQL Injection, stealing, modifying, or even deleting your data.
A good approach is to use the mysql_real_escape_string(String s) function. This basically places on the start of every dangerous character (/,", {, }, etc) a slash (/). So basically this makes the quote or slash inside the string useless.
The best thing to do is to use prepared statements. So, you basically say:
SELECT * FROM USERS WHERE username = ?
Later you go and replace the question mark with the string you want to input as the user name. This has the advantage of not confusing PHP or any other fault-tolerant language, and using this simple and (kind of, hacky) elegant solution to just say replace this with the string and tell the language that what is given is just a string and nothing more than that.
That is good and all, but this video is really outdated. It was made way back in 2013 and since then a lot of new technology has emerged. So, I tried to search the internet to find if there were any new approaches or if this is the one. But the problem was that either I couldn't find it or either I found something that was super confusing.
So, my question is: Is there a better and enhanced way to combat SQL Injection that has been introduced, or if prepared statements are still the norm and if they are vulnerable to any kind of attack?
Parameter binding is still the best solution in most examples of combining dynamic data with an SQL query.
You should understand why. It's NOT just doing a string substitution for you. You could do that yourself.
It works because it separates the dynamic value from the SQL-parsing step. The RDBMS parses the SQL syntax during prepare():
$stmt = $pdo->prepare("SELECT * FROM USERS WHERE username = ?");
After this point, the RDBMS knows that the ? must only be a single scalar value. Not anything else. Not a list of values, not a column name, not an expression, not a subquery, not a UNION to a second SELECT query, etc.
Then you send the value to be bound to that placeholder in the execute step.
$stmt->execute( [ "taraiordanov" ] );
The value is sent to the RDBMS server, and it takes its place in the query but only as a value and then the query can be executed.
This is allows you to execute the query multiple times with different values plugged in. Even though the SQL parser only needed to parse the query once. It remembers how to plug a new value into the original prepared SQL query, so you can execute() as many times as you want:
$stmt->execute( [ "hpotter" ] );
$stmt->execute( [ "hgranger" ] );
$stmt->execute( [ "rweasley" ] );
...
Are prepared statements the best? Yes, they are. It doesn't matter that the advice comes from 2013, it's still true. Actually, this feature about SQL dates back a lot further than that.
So are query parameters the foolproof way of defending against SQL injection? Yes they are, if you need to combine a variable as a value in SQL. That is, you intend for the parameter to substitute in your query where you would otherwise use a quoted string literal, a quoted date literal, or a numeric literal.
But there are other things you might need to do with queries too. Sometimes you need to build an SQL query piece by piece based on conditions in your application. Like what if you want to do a search for username but sometimes also add a term to your search for last_login date? A parameter can't add a whole new term to the search.
This isn't allowed:
$OTHER_TERMS = "and last_login > '2019-04-01'";
$stmt = $pdo->prepare("SELECT * FROM USERS WHERE username = ? ?");
$stmt->execute( [ "taraiordanov", $OTHER_TERMS ] ); // DOES NOT WORK
What if you want to allow the user to request sorting a result, and you want to let the user choose which column to sort by, and whether to sort ascending or descending?
$stmt = $pdo->prepare("SELECT * FROM USERS WHERE username = ? ORDER BY ? ?");
$stmt->execute( [ "taraiordanov", "last_login", "DESC" ] ); // DOES NOT WORK
In these cases, you must put the column names and syntax for query terms into your SQL string before prepare(). You just have to be extra careful not to let untrusted input contaminate the dynamic parts you put in the query. That is, make sure it's based on string values you have complete control over in your code, not anything from outside the app, like user input or a file or the result of calling an API.
Re comments:
The idea Martin is adding is sometimes called whitelisting. I'll write out Martin's example in a more readable manner:
switch ($_GET['order']) {
case "desc":
$sqlOrder = "DESC";
break;
default:
$sqlOrder = "ASC";
break;
}
I replaced Martin's case "asc" with default because then if the user input is anything else -- even something malicious -- the only thing that can happen is that any other input will default to SQL order ASC.
This means there are only two possible outcomes, ASC or DESC. Once your code has complete control over the possible values, and you know both values are safe, then you can interpolate the value into your SQL query.
In short: always keep in your mind an assumption that $_GET and $_POST may contain malicious content. It's easy for a client to put anything they want into the request. They are not limited by the values in your HTML form.
Write your code defensively with that assumption in mind.
Another tip: Many people think that client input in $_GET and $_POST are the only inputs you need to protect against. This is not true! Any source of input can contain problematic content. Reading a file and using that in your SQL query, or calling an API, for example.
Even data that has previously been inserted in your database safely can introduce SQL injection if you use it wrong.
My question of to day is. Do i need to escape PDO in my script?
$columns = implode(", ",$column);
$query = ''.$query.' '.$columns.' FROM '.$table.'';
$dbh_query = $dbh->prepare($query);
$dbh_query->execute();
$dbh_querys = $dbh_query->fetchAll();
return $dbh_querys;
The whole script can be found at.
https://github.com/joshuahiwat/crud/blob/master/control/query_connector.class.php
Can someone explain why do i need a escape at this time or why not.
I like to hear from you, thanks a lot!
The parts of your query that are dynamic are the table name and column names. You can't use bind functions for these parts of the query. Bind functions can be used only for the parts of the query that would otherwise be a simple value in an SQL query. Like a numeric constant, or a quoted string or quoted date literal.
To avoid SQL injection from dynamic table names or column names, you have the following choices:
Use values that are predefined in your class, or otherwise certain to be safe. Don't use external content from users or any other source.
Use escaping. Note that the function PDO::quote() doesn't do the kind of escaping you need for table names or column names.
Create a "allowlist" of known table names and the column names for the respective table, and compare the dynamic input to the allowlist. If it doesn't match the allowlist, raise an error.
First of all you need to understand that the word you are using - "escape" - is meaningless.
What you probably mean is "to make your query safe from SQL injection". But, unfortunately, there is no such magic "escaping" that will make some abstract query safe.
The traditional query building assumes that all the query parts beside data values are hard-coded, while data values are bound via placeholders, like this:
$query = 'SELECT col1, col2 FROM some_table WHERE id = ?';
$stmt = $dbh->prepare($query);
$stmt->execute([$id]);
$row = $stmt->fetch();
This kind of a query considered safe.
In your case of a dynamically constructed query, every part is potentially vulnerable.
And here it is very important to understand that a burden of sanitizing all the query parts is entirely on this function. You cannot dismiss the danger simply claiming that your data is coming from the trusted source. That's a slippery ground because people often have no idea whether their source is trusted or not.
So, if take your question as "Do I have to protect this code from SQL injection", than the answer is - YES, YOU HAVE.
In the meantime you are protecting only a small part of your query - the data values. So you still have to protect (this term is much better than "escape") all other parts.
On a side note, your code is connecting to database every time it runs a query, which is highly inefficient and makes it impossible to use some database features.
I am trying to inject into a dummy website I have made, its a simple form which uses the text input to send data to my php file and then outputs the data gathered. The following is my code for the SQL.
$id = $_GET['id'];
$id = $_GET['id'];
$data = $conn->query('SELECT * FROM users WHERE username = ' . $conn->quote($id));
foreach($data as $row) {
echo $row['id'].' '.$row['username'];
}
When I try to use things such as unions I get no data back and if I put an apostrophe at the end of the URL I don't get a MySQL error. Could someone please explain why the site is secure from SQL injections?
As there are some confusions as to what I asked my final goal is to be able to get into the information schema so I have been trying to use statements like to get into the schema but without success:
' and 1=1 union select table_name,table_schema from information_schema.tables where table_schema='users' #
Other apparently may have missed what you were asking...
You are INTENTIONALLY trying to sql-inject your own site, such as for personal learning on how NOT to, but also see what impacts sql-injection CAN do. If so, take a look at your statement and see "what would I need to add to fake it out".
"SELECT * from users WHERE username = '$id'"
If the user puts a value such as "Bill" for the $id, it would become
"SELECT * from users WHERE username = 'Bill'"
and run no problem. Now, you want to inject and see ALL users, a common way is to close the quote and then add something else that will always return true... such as a user puts a value of
' OR 1=1 ;--
The above would result in
"SELECT * from users WHERE username = '' OR 1=1;-- '"
The semi-colon and dashes are important to "finish" the original query, and then indicate that anything after the dashes are comments so it won't try to execute anything AFTER the otherwise dangling close quote from your original query build construct.
Hopefully that helps answer why you may be failing while TRYING to inject into your own site.
COMMENT FEEDBACK
I don't know why my version would not work, I am not trying to union anything, just force an all records returned.
With respect to your UNION clause, that looks ok, but if your users table has 3 columns and your UNION is only 2 columns, that should fail as the union should be the same number of columns as in the original query. THAT would cause a failure on execution, but not enough specific information to confirm.
Most likely, you have magic quotes enabled, which is saving your otherwise-vulnerable code.
Don't rely on it.
As it can be clearly seen from either question and answers, most people don't understand what injection is. For some strange reason everyone takes injection consequences for injection itself. While injection is just a query creation. No more, no less.
So, the result of injection is not whatever data returned, but mere SQL query string. Thus, what the OP have to check is the resulting SQL query. It is extremely simple a task, as primitive as just echoing the query string out. This will reveal injection possibility immediately, without toilsome guesswork and sophisticated query building.
Simple output like this
SELECT * from users WHERE username = 'Bill\''
will tell you that magic quotes are on and whole question is a thousand-times-duplicate and not-a-real-one at once.
UPDATE
For some strange reason the code in the question mysteriously has been changed to invulnerable PDO based code. Which leads me to believe that whole performance were just a mere trolling.
Your injected SQL String should look like this
-1'/**/UNION/**/SELECT/**/1,##VERSION/**/FROM/**/users/**/WHERE/**/1='1
as you need to close the last ' in the final sql query
Update:
like Your Common Sense pointed out
For some strange reason the code in the question mysteriously has been changed to invulnerable PDO based code. Which leads me to believe that whole performance were just a mere trolling.
I have this code:
<?php
$table = $_GET ["table"];
$query = "SELECT 1 FROM $table";
$st = $pdo->prepare($query);
$st->execute();
This is not the real code, but it is an example to get the idea.
If I make:
hacked.php?table=users;DROP TABLE users;
It will work, cause it is not correctly escaped.
However, if I want to update information like this:
hacked.php?table=users; UPDATE users SET name="abc" WHERE name="def";
It will not work, cause since it is escaped, pdo will convert the query to
SELECT 1 FROM users; UPDATE users SET name=\"abc\" WHERE name=\"def\";
and obviously it fails.
Is there anyway to make this query works?
EDIT 1
We have one guy in our team only devoted to check my code and hacked it. So I want to be ready if this can be in some way accomplished.
EDIT 2
I was already read this: Are PDO prepared statements sufficient to prevent SQL injection? but it really did not answered my question. However it gave me a way to go through. And the solution of #duskwuff was the same I came to. So, for the admins, if this should be removed or marked as a duplicate is ok. But I insist that this can be helpful for someone to know how pdo prepared can be hacked.
It will not work, cause since it is escaped, pdo will convert the query to
SELECT 1 FROM users; UPDATE users SET name=\"abc\" WHERE name=\"def\";
This is incorrect! PDO does not perform escaping on text that is interpolated into queries, as it has no awareness of what text was interpolated. What you're seeing is the result of PHP's deprecated magic_quotes feature adding backslashes to the content of request variables (like $_GET and $_POST). Even if this is enabled, it can be trivially avoided in a query like this one by using non-quoted constructs such as:
SELECT 1 FROM users; UPDATE users SET name = CHAR(97,98,99) WHERE name = CHAR(100,101,102)
(CHAR() is a MySQL function which constructs a string from a list of character code values. If you're using some other database, an equivalent function probably exists.)
Interpolating unescaped content directly into a query is never safe. Don't do it.
I think you are asking the wrong question. If you have code that is even remotely similar to this, then you have a huge problem with the way you're writing code... and probably with the way you're conceptualizing the problem that you need to solve, or you're working from a very bad design.
If, for some reason, you have a need for anything about the design of your database to be passed in on a URL query string or an http post, and if, for some reason, you think executing an unescaped query is the approach you need... then whatever you're doing, you're doing it wrong.
If, by some remote chance, you actually have a need to pass the name of a table to a web page, then the very least you must do is compare the input value to some kind of static structure to see if the input value is in the list... and then use the value from the list, or from something static, never from the input.
Simplistically something as primitive as the following would be a far superior approach, though arguably it is a bad design if table names, column names, or any database internals ever need to go out into browser-land.
$table = $_GET ["table"];
IF ($table == "users")
{
$query = "SELECT 1 FROM users;"
}
ELSEIF ($table == "points")
{
$query = "SELECT 1 FROM points;"
}
...
Hey there, I'm doing some queries to a MySQL database that involves some user-input.. And I was wondering how to prevent injection attacks, like, for example, if someone were to type
"; a-MySQL query here;"
It would be executed, this would allow people access to compromise my system. I'm wondering how i could protect against things like this, possibly create a function that filters out the query looking for bad statements? or perhaps not allowing the ; character?
To sum it all up:
Whats the common practice now adays to prevent injection attacks?
How should I go about it?
How effective will this solution be against people who know what they are doing from changing my database.
The only way is to properly escape user-submitted data. Others have pointed out some ways of doing so.
There's another way: prepared statements and placeholders. Prepared statements are supported by every modern PHP database interface, including mysqli and PDO.
Let's use PDO as a demonstration. Let's say we wanted to update a bit of data in the table foo submitted by a user.
$sql = 'UPDATE foo SET bar = ? WHERE user_id = ?';
$sh = $db->prepare($sql);
$sh->execute(array( $_POST['bar'], $_SESSION['user_id'] ));
The variables in the array passed to execute replace the question mark placeholders in the query. When this happens, they are automatically escaped and quoted. You don't need to manually escape them to make them safe to put in the database!
On the other hand, you will still need to filter them for unexpected content, like HTML, Javascript, letters where you expect numbers, etc. Making data safe to insert into the database is only half of the battle.
An even better way than calling a mysql_escape_string variant is to use bound queries in PDO, so that it's impossible to forget or miss a case since PDO will do the escaping for you when circumstances require it.
See for more:
http://www.electrictoolbox.com/php-pdo-bound-placeholders/
They best solution is to run every piece of input from the wild (user) through this function PHP.net: MySQL Real Escape String This will clean up most - if not all - Injection issues seen today. You would simply need to do something like the following:
$var = mysql_real_escape_string($_GET['var']);
$query = "INSERT INTO foo (var) VALUES (\"$var\");
It's always good practice to force type casting on variables you know should be a type. For instance a numerical identifier:
$id = (INT)$_GET['id'];
or
$id = ( is_numeric($_GET['id']) ? (INT)$_GET['id'] : -1; // replace -1 with FALSE, or NULL, etc
Which will force that variable to be an Integer so you won't end up with $id being "foo" or some other non-numeric.