Example:
$user_input = $_POST['input'];
'SELECT '.$user_input.' FROM table_name'
So it's selecting a column in a database based on a secure (this example isn't secure obviously) value. Is this practical/allowable code?
In SQL you simply send a string to the DBMS (like MySQL). Because of this you can build any string you want and submit it to the DBMS.
You just have to make sure the resulting SQL query is valid. That means that e.g. the columns exist and that no invalid symbol appears.
On it's face, this code is valid, assuming that $user_input must be a valid column name, which means that it must exist and must not contain any special characters, reserved words, etc. (unless they're escaped).
As you said, however, this code isn't secure, but as long as you plan to build the query securely and use PDO or MySQLi (no deprecated mysql_* functions...), you should be fine. If you need an example that doesn't use deprecated functions (including mysql_real_escape_string, which is also being deprecated) I'll provide one.
I know you stated that you know this code isn't secure, but here's another example if you're curious. As was discussed in the comments and this question, this input:
$user_input = '; DELETE FROM table_name ; *';
'SELECT '.$user_input.' FROM table_name'
will delete the entire contents of the table table_name. Even though this code raises a syntax error, MySQL will continue to execute it, thus effectively truncating table_name.
Related
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 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;"
}
...
I'm currently writing a php framework with focus on security. I use a query builder to generate SQL-statements, so that it is not bound to MySQL. (Or SQL in general) I found certain posibilities that user could inject row names, so it has to escape them somehow. Because of how the query builder works, i sadly cannot use prepared statements. How can I fix this?
EDIT:
The system works for example like this: db::select()-from('Tablename')->that('rowname')->run(). And I'm afraid one user could do something like that($_GET['foo']) or something. I could live with that, but I thought there has to be a way to sanatize this
To escape backtick you have to double it. Here is a function from my class
private function escapeIdent($value)
{
if ($value)
{
return "`".str_replace("`","``",$value)."`";
} else {
$this->error("Empty value for identifier (?n) placeholder");
}
}
//example:
$db->query("UPDATE users SET ?u=?s", $_POST['field'], $_POST['value']);
So, it will create a syntactically correct identifier.
But it is always better to whitelist it, as there can be a field, though with correct name,to which a user have no access rights. (So, schema-based solution is still dangerous from this point of view. Imagine there is a role field with value admin for the query from my example)
I have 2 functions in my class for this purpose, both accepts an array of allowed values.
Because of how the query builder works, i sadly cannot use prepared statements. How can I fix this?
If you can't use query parameters, then change the query builder to apply escaping to its arguments before interpolating them into SQL expressions.
Lots of people correctly advocate for query parameters, but escaping is also safe IF you do it correctly and consistently.
Cf. mysqli::real_escape_string()
Re your comment, okay I see where you're going. I was confused because you said "row name" and that's not the correct terminology. You must mean column name.
Yes, you're right, there are no functions in any of the MySQL APIs to escape table or column identifiers correctly. The escaping functions are for string literals and date literals only.
The best way to protect SQL queries when untrusted input names a table or column is to use allowlisting. That is, test the argument against a list of known table names or column names, which you either code manually, or else discover it from DESCRIBE table.
See examples of allowlisting at my past answers:
escaping column name with PDO
PHP PDO + Prepare Statement
My presentation SQL Injection Myths and Fallacies
My book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.
I have the following MySQL query that I execute from a .php page
SELECT * FROM servers WHERE name LIKE '%$value%'
which, when executed, selects 0 rows (However, the query runs successfully, so I can't use mysql_error() to debug). When I run the query in PHPMyAdmin it selects the appropriate rows. Other queries such as
SELECT * FROM servers
work fine. I can put my code up here if it will help.
Edit: Here's something offering an improvement based on Marek's answer below. Please see the comments regarding the practice of putting variables directly into queries and consider using prepared statements. Anyway, here it goes.
PHP substitutes variables inside doubly-quoted strings, but not inside singly-quoted strings.
One quote character is just treated as an ordinary character within a string delimited by the other.
Putting that together, you can write:
$q = "SELECT * FROM servers WHERE name LIKE '%$value%'"; //Fine
You cannot write:
$p = 'SELECT * FROM servers WHERE name LIKE "%$value%"'; //Broken!
$q works because it's a doubly-quoted string, and the apostrophes are just ordinary characters. $p does not work because it's a singly-quoted string.
As pointed out by GoodFather below, you can also say ${value} to avoid ambiguities with the ambient string, e.g. $r = "ABC${value}DEF";.
You really need to look at doing this query more safely. This will help with your issue as well. As it stands, you are vulnerable to SQL injection. Look at the examples from the PHP manual for how to do it right:
http://php.net/manual/en/function.mysql-query.php
EDIT: From your comments you mentioned that you are already taking care of the string properly, which is great. The code below should fix your problem.
For example, you could rewrite your query statement (in PHP) like so:
$query = sprintf("SELECT * FROM servers WHERE name LIKE '%". mysql_real_escape_string($value) . "%'");
That will clean up your code and it will also handle the issue with your LIKE statement not working properly.
Here is another good article on the subject:
http://joshhighland.com/blog/2008/07/06/php-sprintf-sql-like/
Are you expecting a case-sensitive or case-insensitive query? I'm betting case-insensitive since you're expecting results but not seeing them. Take a look at your database's default collation or the table's specific collation and make sure it ends in _ci, whatever it is.
I have a php file which at the start, assigns some variables from what was sent using $_GET.
It then does some mysql queries, processes the output, then echos out some text and variables.
The only protection I have set in the code is mysql_real_escape_string on the GETs.
Is that enough to prevent attacks?
What else can be done?
Well, you take mysql_real_escape_string awfully wrong.
It's not your fault though - its one of wickedest delusions among PHP society. Even official man page put it all wrong.
This function has nothing to do with securing anything in general and GET variables in particular
This function is merely escaping string delimiters, to make string delimiters unable to break a string. Thus, 2 most important consequences:
not only GET variables but ALL variables being put into query in quotes should be processed with mysql_real_escape_string(), no matter of their source or origin or possible dangerousness
it will have effect on the quoted strings only. It's totally useless to use this function for any other part of query, LIMIT clause variables for example.
Thus, to secure your SQL query, you have to follow whole set of rules, not just deceiving "sanitize your data with mysql_real_escape_string".
You can learn how to protect your SQL from my earlier answer on the similar topic: In PHP when submitting strings to the database should I take care of illegal characters using htmlspecialchars() or use a regular expression?
update
a scenario to show why mysql_real_escape_string is not a silver bullet
being given with url
http://www.example.com/news.php?offset=99999+UNION+SELECT+password+FROM+users+--
a code
$offset = mysql_real_escape_string($_GET['offset']);
$sql = "SELECT title FROM news LIMIT $offset,20";
Will result if not in not so pompous as little bobby tables' one but in somewhat no less disastrous.
No, there are plenty of attacks that you might not have protection for. One example is CSRF. It's a big field, so I recommend reading up on this stuff on the Owasp site:
http://www.owasp.org/
Using this is definitely not sufficient. It is not even sufficient when you only consider sql injection. It is sufficient when you consider sql injection on strings only, but as soon as you have an integer (say an id) it goes wrong:
http://example.com/foo.php?id=10
Goes through:
$q = "SELECT * FROM foo where id = " + mysql_real_escape_string($_GET['id'])
Which results in de SQL query:
SELECT * FROM foo where id = 10
This is easily exploitable, for instance:
http://example.com/foo.php?id=10%3B%20DROP%20TABLE%20foo
Goes through:
$q = "SELECT * FROM foo where id = " + mysql_real_escape_string($_GET['id'])
Which results in de SQL query:
SELECT * FROM foo where id = 10;DROP TABLE foo
I hope this clarifies why it isn't enough.
How you should solve this? Define what input is allowed, and check that the input is indeed of that form, for instance:
if(preg.match("^[0-9]+$",$_GET['id']){
// your code here
}else{
// invalid id, throw error
}
But the best way to be on the safe side (regarding SQL Injection) is using prepared statements:
http://php.net/manual/en/pdo.prepared-statements.php
mysql_real_escape_string will only protect you agains SQL Injections when you use the return value in a MySQL string declaration like:
'SELECT foo FROM bar WHERE quux="'.mysql_real_escape_string($val).'"'
It won’t protect you if you use it in any other context (specifying the sorting order with ASC/DESC, row limits, table/row names, etc.). In that case you’ll need to validate/filter/sanitize the value separately.
And if the user data can also be part of the query result that you are about to output, use htmlspecialchars to replace any HTML special character.
you have if the get variables have values using the isset() and empty() functions