How to sanitise MySQL table names - php

Would this simple table name sanitisation be enough to prevent SQL injections?
$table = str_replace('`', '', $table);
$table = '`'.$table.'`';
Note: I use PDO.

If you are allowing the user to select which table they query from, the only way to "sanitize" and verify no hacking would be to have a whitelist of allowed tables. Your method would fail to allow for other database schemas also. This could also be as simple as querying for a list of tables from a specific database.
I don't have a mysql instance to play with to try to find bad table names. I can say though that while I can't think (or really test and verify) a way to bypass this, I can say that I can think of several ways that the query would straight up fail. Trying to access other databases otherDB.tableName or selecting from multiple tables with joins. While this would likely cause an error, you still don't want to take the chance of someone finding something that will go through.

If you want to be super safe, and avoid errors you could query the INFORMATION_SCHEMA to check if the table exists. Then you will know the tables exists, and can catch an invalid table earlier. You can also limit to a particular schema.
split the input into DB and Table (if necessary)
Select table_name, table_schema from information_schema.tables where table_schema = 'db' and table_name = 'tableName' (note: you can use parametrized query here to prevent injection)
If you get a return, might as well use the returned values (table_schema.table_name), else not a valid table
Then perform your query, knowing that the table exists, and is valid
This extra query of checking information_schema, could cause some overhead, but maybe you could optimize for your specific need.

Related

Table names as variables

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.

SELECT FROM a database named from a value in a JOIN

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'"

Problem with PHP/MySQL Insert

How to insert data into two or more tables?
Currently I am using this trick. (Let just assume that the syntax is correct)
$Sql1 = "insert into user";
$Sql2 = "insert into files";
$Sql3 = "insert into messages";
query $Sql1
query $$ql2
query $Sql3
So I am using this and I need to optimize.
Also, I think I need to use Foreign key? But I don't know how to do it on phpmyadmin.
and lastly:
This is all I want.
To insert data on the "user" table and then automatically the table "messages" and "files"
will have also an ID which is the same with the user.. That is why I use to 3 queries in my code.
Please help me.
Thanks in ADVANCE!
myb
It is perfectly fine to use multiple queries.
However, you should wrap the 3 queries into one transaction. That is, if one query fails for some reason, the effects of all previous queries (if any) can be rolled back so that your database stays in a consistent state.
Note: Transactions are not possible with MySQL's MyISAM tables. Additionally you have to use PHP's Mysqli (or PDO) to be able to use transactions. (I assume that you're not using some high-level data base abstraction layer.)
As to your question regarding foreign keys: As far as I know, it makes no difference when you're using the MyISAM engine for the tables in your database (which I presume), because MyISAM doesn't even support the explicit modeling of foreign keys. Other table engines like InnoDB, however, can use foreign keys to check certain constraints like "a message must always be associated with an existing person".
Here are some nice examples regarding foreign keys: http://www.postgresql.org/files/documentation/books/aw_pgsql/node131.html
This is the answer, I think : http://dev.mysql.com/doc/refman/5.1/en/example-foreign-keys.html
With mysql u can use LAST_INSERT_ID() or in php mysql_insert_id() function. Checkout this manual entries:
http://dev.mysql.com/doc/refman/5.0/en/getting-unique-id.html
http://php.net/manual/en/function.mysql-insert-id.php

binding variable for drop table php

Im writing PHP scripts for using with my mySQL database. The only problem i have is binding variables for drop table/ create table and so on.
$stmt = $link->prepare("DROP TABLE ?");
$stmt->bind_param('s','testing');
$stmt->execute();
is not working. I tried also:
SELECT * FROM (SELECT MAX(name) from profiles where name='testing') <- is working
DROP TABLE (SELECT MAX(name) from profiles where name='testing') <- dont work
Binding a parameter is not the same as just replacing a portion of the string : you cannot just bind anything you want.
In this case : you cannot use a bound parameter for a table name -- you'll have to use string concatenations to build your query, instead of using a prepared statement.
As a reference, quoting PREPARE Syntax :
Parameter markers can be used only
where data values should appear, not
for SQL keywords, identifiers, and so
forth.
As far as I know, you can only bind to a parameter, not to any part of a query you want. You're essentially telling the database "hey, I'm going to pass you a value here, and I want you to do your magic to make sure it doesn't overstep its bounds". Things like table names or field names aren't values, they're part of the table structure itself.
In this case, you'll have to just use a use a simple $query = "DROP TABLE " . $table;. It should be easy enough to check against a list of known tables to ensure you're not injecting anything harmful. Anything that makes DDL changes shouldn't be taking input from the user anyway, as far as I'm concerned. These sorts of changes can be based on user input, but the actual construction of the query should be really well known and shouldn't need outside data to construct.
Also, I'm not really sure what you're trying to do with this query:
DROP TABLE (SELECT MAX(name) from profiles where name='testing');
It looks like you might be trying to delete a record, but that's entirely the wrong syntax for that. If you're trying to drop a table whose name comes from the result of another query, I really don't think you can do that either. I'm 99% sure that DROP TABLE expects only a literal table name value.
Are you sure you want to drop tables dynamically?
It is extremely unusual.
It seems you have wrong database design.
And now you faced a consequence.
It seems you should have one table users and delete rows from it, not tables.

Select more tables after FROM statement?

this is part of a security audition, so there is no way to "change" the query.
Basically, what I found is a flaw that allows statement manipulation, so basically it goes like:
$query = "DELETE FROM `products` WHERE `products`.`whatever` = $variable";
This is PHP, so as far as I know there is no way to execute multiple queries. Using this SQL Injection, I was able to "clear" this table by running "0 OR 1=1#".
This works just fine, but it doesn't allow me to choose more tables to delete from.
This is, in pseudocode what I want to do:
DELETE FROM `products` WHERE `products`.`whatever` = **0 OR 1=1, FROM `othertable` WHERE `othertable`.`othercolumn` = 0 OR 1=1**
Is this plausible anyhow?
If this isn't reliable, is there any other way I could use this?
You can't have multiple FROM clauses for the same DELETE statement, so you can't go about it exactly how you'd want to. If the MySQL db had 'allow multiple queries per statement' turned on, you could try to terminate the one DELETE query and then tack on another to the end, so that it'd look like this:
DELETE FROM `products` WHERE `products`.`whatever` = **0 OR 1=1; DELETE FROM `othertable` WHERE `othertable`.`othercolumn` = 0 OR 1=1**
But that's about it.
Perhaps I don't fully understand the question, but what I take away is that you're building a SQL command as a string and running that string directly against a MySQL database.
You can separate multiple commands using the command separator (usually ';'), so you could run pretty much any command you want as this comic aptly illustrates.
If your database configuration supports multiple commands (or might in the future if someone changes today's setting), you want to ensure you don't have a command separator as part of the input. See this article for advice on sanitizing your input to prevent this type of attack.
As you stated, multiple queries are not supported by the normal MySQL driver module. From the manual for mysql_query:
mysql_query() sends a unique query
(multiple queries are not supported)
to the currently active database on
the server that's associated with the
specified link_identifier .
Unfortunately for your injection efforts, DELETE syntax only supports multiple table deletes by specifying them in the FROM clause. Your injected variable is part of the WHERE, so the most damage you can do is to the single specified table.
Contrary to popular belief, you can actually run multiple MySQL statements from PHP, you just have to be using a different database driver module such as MySQLi. See MySQLi::multi_query().

Categories