In our application, users can create custom export functions in form of SQL statements. Something like this:
SELECT name, age, date_birth FROM users WHERE group_id = 2
I don't want them to clear the whole database by inserting a DELETE statement. My ideas would be:
We are using PHP PDO.
The SQL LIMIT clause constrains the number of rows returned by a SELECT statement. For Microsoft databases like SQL Server or MSAccess, you can use the SELECT TOP statement to limit your results, which is Microsoft's proprietary equivalent to the SELECT LIMIT statement.
When you use select * you're make it impossible to profile, therefore you're not writing clear & straightforward code and you are going against the spirit of the quote. select * is an anti-pattern. So selecting columns is not a premature optimization.
Use an INNER JOIN to join with your max ID's. Assuming the ID column is indexed, this is likely as fast as its going to get. MySQL will create many temporary tables, while in my example there will be only one.
The SELECT statements can force locking via hints and they can get blocked for other reasons.
Tune the SELECT query so it uses fewer I/O resources. Run the query at a quiet time. Run the query on a separate copy of the database (e.g. a readable secondary).
SELECT can block updates. A properly designed data model and query will only cause minimal blocking and not be an issue. The 'usual' WITH NOLOCK hint is almost always the wrong answer. The proper answer is to tune your query so it does not scan huge tables.
Is it Possible to Protect a Power Query in excel? The answer is yes, you can protect your query in Excel. It’s actually a no brainer. It not only protects but also prevents your users from modifying your queries.
Run the query at a quiet time. Run the query on a separate copy of the database (e.g. a readable secondary). Run the query in an I/O-limited resource pool as described here *.
As I see it, there are three options to choose from:
Create a tool that will create the query for the user on the background. Simply by clicking buttons and entering table names. This way you can catch all weird behavior in the background bringing you out of danger for queries you don't want executed.
Create a MySQL user that is only allowed to do SELECT
queries. I believe you can even decide what tables that user is allowed to select from. Use that user to execute the queries the user enters. Create a seperate user that has the permissions you want it to to do your UPDATE
, INSERT
and DELETE
queries.
Before the query is executed, make sure there is nothing harmfull in it. Scan the query for bad syntax.
Example:
// Check if SELECT is in the query
if (preg_match('/SELECT/', strtoupper($query)) != 0) {
// Array with forbidden query parts
$disAllow = array(
'INSERT',
'UPDATE',
'DELETE',
'RENAME',
'DROP',
'CREATE',
'TRUNCATE',
'ALTER',
'COMMIT',
'ROLLBACK',
'MERGE',
'CALL',
'EXPLAIN',
'LOCK',
'GRANT',
'REVOKE',
'SAVEPOINT',
'TRANSACTION',
'SET',
);
// Convert array to pipe-seperated string
// strings are appended and prepended with \b
$disAllow = implode('|',
array_map(function ($value) {
return '\b' . $value . '\b';
}
), $disAllow);
// Check if no other harmfull statements exist
if (preg_match('/('.$disAllow.')/gai', $query) == 0) {
// Execute query
}
}
Note: You could add some PHP code to filter out comments before doing this check
What you are looking to do is quite possible however you'll never have a 100 percent guarantee that it's safe. Instead of letting the users make the queries it's better to use an API to provide data to your users.
Don't do this, there will always be creative ways to make a dangerous query. Create an API that will manually construct your queries.
Since you said you would prefer not to use read only SQL accounts if there are alternatives. If you're running PHP 5.5.21+ or 5.6.5+: I'd suggest checking if the first statement of the query is a SELECT statement and disabling multiple queries in your PDO connection.
First disable multi statements on your PDO object...
$pdo = new PDO('mysql:host=hostname;dbname=database', 'user', 'password', [PDO::MYSQL_ATTR_MULTI_STATEMENTS => false]);
Then check that the first statement in the query uses SELECT and that there are no subqueries. The first regex ignores leading whitespace which is optional and the second detects parenthesis which would be used to create subqueries -- this does have the side effect of preventing users from using SQL functions but based on your example I don't think that's an issue.
if (preg_match('/^(\s+)?SELECT/i', $query) && preg_match('/[()]+/', $query) === 0) {
// run query
}
If you're running an older version, you can disable emulated prepares to prevent multiple statements being executed but this relies on PDO::prepare() being used.
FWIW: It would be a lot better to use prepared statements/generate safe queries for your users or to use a read-only SQL account. If you're using MySQL and have admin rights/remote access, I'd suggest using the community edition of SQLyog (https://github.com/webyog/sqlyog-community/wiki/Downloads) to create read-only user accounts. It's extremely user friendly so you won't have to learn the GRANT syntax.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With