I understand that you cannot use database or table names as parameters in prepared statements. However, our app allows a user to specify the database name during the install. Is there an abstract or PDO provided way to quote these names (backticks for MySQL, brackets for MSSql, etc)?
You cannot directly escape column names and table names in PDO. You can see the answer here:
escaping column name with PDO
What you can do in this situation is to make a query to get all the tables from the given database, like this:
SHOW TABLES;
Or query to get all Databases like this one:
SHOW DATABASES;
Then use this as a white-list for the user input.
When you're using databases, it's wise to exclude some system databases like mysql itself and information_schema.
Other option is to filter the user input with a given regex, for example if your table/database names are only strings with underscore you can use:
preg_match('/^[a-z_]+$/i', $userGivenTableName)
This should remove any potential strings containing SQL Injections.
Related
I have been using prepared statements with PDO and I have found that table names cannot be bound. While I can use something like $sql = "select * from $table_name where something = :something"
If the $table_name variable is assigned internally by a method by running a query and getting the value from the database, is this method safe? Or is it bad practice to have variable table names? If it is opinion based, I'd like to know your opinions.
There are situations when you must use dynamic table names, so this is an acceptable practice. Personally, I would double check if this requirement is a must and if not, then hard code the table names in the queries.
If the table name comes from a user input, then I would use white listing as a security measure: get list of table names from mysql's information_schema and compare them to the input in the application code to avoid any aql injection.
If the table name comes from mysql's information_schema, then that's it, you can use it without any further checks because it is a valid table name. Just put backticks around the name.
If the table name comes from a table created by the application, then I would again simply retrieve the list of tables via the information_schema and check if it existed in the application code. This ensures that the table name is valid.
Currently I'm working on a concept for a simplistic tool to perform some »maintenance« database operations (drop/truncate/copy tables etc.) written in PHP.
That necessarily requires identifiers in SQL statements to be dynamic user input. While prepared statements are perfect for separating SQL statements from user input for any comparative values, they are not meant to be used for identifiers like table or column names. (That means, I can't use prepared statements to prepare identifiers.)
A common way to secure dynamic identifiers is whitelisting but this requires a static and known database structure. For example I want to implement a command like Copy table A and name it B. The interesting part here is the B.
Supposing the user is authenticated and allowed to perform this action, how can I protect this from SQL injection? Is that even possible?
I found an approach that suggests to simply quote grave accents (`) in any identifier like this:
$table_name = 'origin_table'; // can be checked against existing tables
$copy_table_name = 'user_input';
$quoted_table_name = '`' . str_replace( '`', '``', $copy_table_name ) . '`';
$sql_statement = "CREATE TABLE {$quoted_table_name} LIKE {$table_name}";
Is that a sufficient protection against possible SQL injections?
Update
PDO::quote() (mentioned in an answer) is not an option. It does not escape grave accents (`):
$user_input = 'table`; TRUNCATE TABLE users --';
var_dump( $pdo->quote( $user_input ) );
//string(33) "'table`; TRUNCATE TABLE users --'"
Update 2
The PostgreSQL extension has a function for exactly that purpose: https://secure.php.net/manual/en/function.pg-escape-identifier.php
You always want to escape ` identifiers, even table names and field names, as maybe tomorrow they will be reserved words and break your query. As long as you are using prepared statements (PDO, mysqli, or whatever) you will be fine. Note that PDO won't allow to escape table or field names.
You just need to fine grain your filtering mechanism, as to what table name, etc is allowed and not.
BTW: don't do it like you are tring to do: "CREATE TABLE {$quoted_table_name} LIKE {$table_name}";.. user prepared statements with placeholders (?, or :name, etc.)
EDIT:
Since you need to secure identifiers, I see 2 ways:
surround them with (`)
Filtering & escaping incoming values (mysql_real_escape_string or better). Filtering will include striping away any unnecessary characters (you might want to allow only letters [a-z]+ or letters with digits.
As you mention the quote() function for PDO can be used to escape both the column names as well as data. Alternatively using prepared statements such as CREATE TABLE ? LIKE ? and then just execute the query.
I have a SQL statement that has to pull information from two databases, one is a constant and known database, and the other is dynamically found in the first database. The databases structure looks like this:
(database) lookup
(table) test
(fields) key, database_name
(row sample) "foo", "database_foo"
(database) database_foo
(table) bar
(fields) important1, important2
(row sample) "silly", "test"
So my SQL statement looks like this:
SELECT
test.key as key,
test.database_name as database_name,
bar.important1 as important1,
bar.importnat2 as important2,
FROM
lookup.test as test,
(database_name).bar as bar, # this, obviously, doesn't work
WHERE
key = 'foo'
LIMIT 1;
Is there a way I can make this work, or is it better for me to just do two separate SQL statements, one for the lookup, and one for the database?
If you must do it this way then you need to use dynamic sql and a two statements.
You have your query built as a string and then you run an EXEC on the query once it's constructed.
In this case you would have a string variable for the db name, then you would create a query from this variable and your literal query, then you would simply execute it.
Be aware, though, this makes you vulnerable to SQL Injection if you don't control the input parameters.
Erland Sommarskog has a great primer on using dynamic SQL:
http://www.sommarskog.se/dynamic_sql.html
EDIT: From #BryanMoyle comment below, you will likely need to do both a separate query and dynamic sql. You need to extract the value in order to determine the other DB name... Since you cannot use the DB name as a variable otherwise, you'll need to SELECT this information first, then stick it into the subsequent query.
I personally go for 2 separate statements; it would make it easier to control for errors such as the the lookup provides a row, that the row provides a valid database, etc.
As Matthew pointed out, beware of SQLIA and sanitize all user input. I like to MD5 hash inputs and compare to the hash of the value looked up.
I'd go for two separate queries, mediated by php: It's simpler, more robust, and will keep you sane and productive. Write one query to find out which database you should be talking to. Use php (not SQL) to direct your query to that database:
$row = $lookup->prepare("SELECT database_name WHERE key = 'foo'")->execute()->fetch();
$db = $row[0];
Then you contact $db and ask for the row with key foo. Use PHP code to select the right open connection, or switch databases inside the connection with USE:
$query2 = "USE " . $db . "; SELECT * FROM bar where key == 'foo'"
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.
Is it posible to bindParam in the order by portion of a sql statement.
For example, is the following possible?
select whatever from table where age > :age order by :user_specified_order_by_field_name_here
and if not, what's the recommended way to make sure that the user_specified_order_by_field_name_here does not contain SQL injection code?
No, PDO doesn't support dynamic table or column names as prepared values. Any column names you insert into the query will not be escaped, and will lead to a SQL injection vulnerability.
PDO::Quote() won't help either - it can escape strings only, but in mySQL, column names aren't strings.
The only 100% safe way to prevent problems is to compare user_specified_order_by_field_name_here against a list of valid columns in the table. You could even use numbers (that you resolve into column names internally) to add an additional layer of obscurity.