Escape string on server side - mysql

I have a stored procedure in a database that accepts string arguments that are inserted directly into a query. I have client-side code to escape the inputs, but that doesn't stop anyone with permission to execute that procedure with bad arguments and inject SQL.
Current implementation is something like this:
CREATE PROCEDURE grantPermissionSuffix (perm VARCHAR(30), target VARCHAR(30), id VARCHAR(8), host VARCHAR(45), suffix VARCHAR(45))
BEGIN
SET #setPermissionCmd = CONCAT('GRANT ', perm, ' ON ', target, ' TO ''', id, '''#''', host, ''' ', suffix, ';');
PREPARE setPermissionStmt FROM #setPermissionCmd;
EXECUTE setPermissionStmt;
DEALLOCATE PREPARE setPermissionStmt;
FLUSH PRIVILEGES;
END
Clearly this is a recipe for disaster. How can I prevent an injection opportunity? Is there a standard SQL function to escape input? Is there one for MySQL? Is there another way to get the same result without extra client-side code?
My fear is that the only solution will be client-side prepared statements, which is not an option at this time. I need all the logic to be handled on a server, requiring clients to only call this procedure (I don't want to have to grant users permission to modify tables/permissions directly, only to handle it with procedures they're allowed to execute).

You can prepare statements with ? placeholders for variables and later EXECUTE USING the variables, just like in client-side prepared statements, at least according to the manual. I'm not sure how well this would work when substituting table names, though, but this is limited by prepare rules, so if it doesn't work in server-side, it wouldn't work on client-side either.
UPDATE:
Apparently, mysql doesn't recognize ? placeholders in prepared GRANT query. In that case, you'll have to take care of it manually. Some tips are in that answer - namely, using ` (backtick) to escape identifiers and using a whitelist for keywords - that way, you also gain fine-grain control on what you do allow in your procedure.
I would add that for your specific purposes it might be better to select from information_schema and mysql tables to control that, for example, db and table passed to you actually exist. You can use prepared statements with placeholders for that, so it's safe. Something like this will check db and table:
PREPARE mystat FROM 'SELECT count(*) into #res FROM INFORMATION_SCHEMA.TABLES WHERE upper(TABLE_SCHEMA)=UPPER(?) and UPPER(TABLE_NAME)=UPPER(?)';
set #db = 'mydb'; --these two are params to your procedure
set #table = 'mytable';
set #res = 0;
execute mystat using #db, #table;
select #res; --if it's still 0, then no db/table exists, possibly an attack is happening.
Checking user and host can be done like that, too, using mysql.users table instead. For the rest of params, you'll have to build a whitelist.
Yet another way I see is to check for allowed characters using a regular expression - there's REGEXP command for that. For example, you can control that your procedure parameter has only alphabetic uppercase with if #var REGEXP '^[A-z]+$'. To my knowledge, it's impossible to perform an SQL injection using only A-z.

Related

Using 'where..in' inside store procedure with prepared statements (Safe Way)

Im trying to secure my store procedure to avoid SQL Injection attacks using prepared
statements. with the guide that mentioned here :
"https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html"
mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
mysql> SET #a = 3;
mysql> SET #b = 4;
mysql> EXECUTE stmt1 USING #a, #b;
+------------+
| hypotenuse |
+------------+
| 5 |
+------------+
mysql> DEALLOCATE PREPARE stmt1;
I have no problem with passing parameter one by one.
Now if i have to pass array of item to SP from java and use 'where..in' , what is the best
approach ?
I can use something like this :
SET #somestring = '1,3,18,25';
SET #s=CONCAT("
SELECT * FROM city
WHERE id IN (",#somestring,");");
PREPARE stmt FROM #s;
EXECUTE stmt;
Dont know if is it secure enough for injection , since i guess its not checking parameter
one by one while it not use "USING #a, #b".
You cannot pass an array to your stored procedure, because MySQL doesn't support arrays. Your string '1,3,18,25' is a string that happens to contain commas. This is not an array.
Interpolating an unknown string into a dynamic SQL statement is SQL injection, full stop. You can't be sure it does not contain special characters that would change the syntax of the dynamic SQL query, so it's not safe.
The safest way to use variables in dynamic SQL statements is by using query parameters. But there's a couple of problems: I assume your string with comma-separated numbers may have a variable number of numbers, and you must support that.
Query parameters can only be used for individual scalar values. One parameter per value:
WHERE id IN (?, ?, ?, ?)
The syntax for EXECUTE stmt USING ... supports a variable number of arguments, but not a dynamic number of arguments. You must code the arguments as fixed in your code, and the arguments must be individual user-defined variables (the type with the # sigil). There's no good way to convert a string of comma-separated values into a like number of individual variables. It's possible to extract substrings in a loop, but that's a lot of code.
And it still wouldn't help because you'd have to find a way to pass a dynamic number of arguments to EXECUTE ... USING.
A common workaround for MySQL users is to use FIND_IN_SET(). This allows you to match a column to a comma-separated string of values.
WHERE FIND_IN_SET(id, '1,3,18,25') > 0
So you could pass your string as a single parameter to a prepared statement:
SET #somestring = '1,3,18,25';
SET #s='SELECT * FROM city WHERE FIND_IN_SET(id, ?)';
PREPARE stmt FROM #s;
EXECUTE stmt USING #somestring;
In fact, you don't even need to use PREPARE & EXECUTE for this. You can use MySQL variables in a query directly.
SELECT * FROM city WHERE FIND_IN_SET(id, #somestring);
This is safe, because the variable does not cause SQL injection. The query has already been parsed at the time you create the stored procedure, so there's no way the content of the variable can affect the syntax of the query, which is what we're trying to avoid.
This is safe ... but it's not optimized. By using FIND_IN_SET(), the query cannot use an index to search for the values in your string. It will be forced to do a table-scan. Probably not what you want.
So what are the options for solutions?
You could check the input string to make sure it has only digits and commas, and abort if not.
IF #somestring NOT REGEXP '^([[:digit:]]+,)*[[:digit:]]+$' THEN
SIGNAL SQLSTATE VALUE '45000'
SET MESSAGE_TEXT = 'Invalid input, please use only comma-separated integers';
FI
Once you confirm that the string is safe, then you can safely interpolate it into the query string, as in your example with CONCAT().
My preferred solution is to stop using MySQL stored procedures. I hardly ever use them, because virtually every other programming interface for MySQL is easier to code.

which method to follow to prevent SQL injection in MySql Stored Procedure

hi friends i googled for this and find different methods use by others to prevent sql injection. i wrote in below stored procedure before finalising to follow specific method i want suggestion from you guys. which method should i follow.
below is the example of my stored procedure, in which i wrote different methods
CREATE DEFINER=`root`#`localhost` PROCEDURE `spTestSQLInjection`(pSelfId VARCHAR(100),bIntSelfId BIGINT(20))
BEGIN
SET #sSelfId = pSelfId;
-- Method:1
-- below code is for injection
SET #selectQuery = CONCAT('select * from userProfile where userId = ',#sSelfId);
PREPARE stmt FROM #selectQuery;
EXECUTE stmt ;
DEALLOCATE PREPARE stmt;
-- Method:2
-- injection doesent affect below code
select * from userProfile where userId = #sSelfId;
-- Method:3
select * from userProfile where userId = bIntSelfId;
-- Method:4
SET #sSelectQuery=
'select * from userProfile where userId = ? ';
PREPARE stmtQuery FROM #sSelectQuery;
EXECUTE stmtQuery USING #sSelfId;
DEALLOCATE PREPARE stmtQuery;
END
executed below stored procedure in workbench :
1)call spTestSQLInjection('231', 231);
result : when i pass proper data then result set gives single user data for all then 4 method.
2)call spTestSQLInjection('231 OR 1=1', 231);
result : when i pass '231 OR 1=1' data then result set gives all user data for method 1 and single record for method,2,3,4.
so concluded that method1 is prone to sql injection so not to follow this method, as its dynamic query
& its advisable not to write dynamic query in stored procedure.
method2, method3 worked & gave single user record, which means this query are not prone to sql injection.
method4 is adviced by most of the developer to follow this to prevent sql injection in stored procedure. but my
live project contains 20 to 30 queries(insert/update/delete) inside a stored procedure, so writing prepared statement
for all is time consuming.
so guide me to follow which method, method2, method3, or method4
Thanking you in advance, any help will be appreciated.
Methods 2, 3, and 4 are safe from SQL injection, but method 3 is the simplest solution.
CREATE DEFINER=`root`#`localhost` PROCEDURE `spTestSQLInjection`(pSelfId VARCHAR(100), bIntSelfId BIGINT(20))
BEGIN
-- Method:3
select * from userProfile where userId = bIntSelfId;
END
There's no need to create a user-defined variable, because the procedure parameter bIntSelfId is already a variable.
There's no need to use a parameter or a prepared statement in this case, because the variable is treated only as a scalar value. It doesn't need to modify any SQL syntax, nor is it used as an identifier, so it can simply be used in the query as shown above.
This assumes your table does not have its own column with the same name of bIntSelfId. If it did, the use of that identifier would be ambiguous. It's recommended to name your parameters distinctly from any of the columns of tables you will query using that variable. Using a user-defined variable or a query parameter would also avoid the ambiguity.

How to use prepare statement for Use database in Mysql

set #switch_schema= concat('use ', v6_schema, ';');
select #switch_schema;
PREPARE s3 from #switch_schema;
EXECUTE s3;
Prepare statement does not support 'Use'; is there a solution to this?
The workaround is the following but I am looking for a more robust solution
set #db := v6_schema;
drop temporary table if exists tempdb.activeUnits;
set #query = concat ('create temporary table tempdb.activeUnits
select *
from ',#db,'.activemodelunits_blue
where active_datetime = (Select max(active_datetime) from ',#db,'.ActiveModelUnits_blue) ';
PREPARE s3 from #query;
EXECUTE s3;
I'm afraid since you can't run USE as a prepared statement, your options are limited.
USE before you call this routine
You could call USE from your application before calling the procedure where you reference the table.
USE v6_schema;
CALL MyProcedure();
USE inside a CASE
If you are writing this code in a stored procedure, you can use the CASE statement.
BEGIN
CASE v6_schema
WHEN 'myschema1' THEN USE myschema1;
WHEN 'myschema2' THEN USE myschema2;
WHEN 'myschema3' THEN USE myschema3;
ELSE USE mydefaultschema;
END CASE;
END;
This means you're limited to the finite list of schemas for which you have coded. You can't make this adapt to any future schema name you think of in the future, without updating the code.
Use qualified table names
This is the workaround you mentioned in your question. Concatenate the schema name with table names, every time you reference those tables in prepared queries.

Is possible to interpret and operation on string using a mathematical function in MySQL?

I want to select the final value of a string (for example '3+4') using a SELECT. I want to know if exists a function or a way in MySQL.
Example:
SELECT ('4+1*3') as VALUE;
The result is:
VALUE
7
You can do this in three steps:
Combine query into a single string
Create a prepared statement from this string
Execute said statement
Something like this:
SET #query = CONCAT('SELECT (', '4+1*3', ') AS VALUE');
PREPARE stmt FROM #query;
EXECUTE stmt;
Note that this does not merely evaluate mathematical expressions. A malicious user could insert pretty much anything into the query here, so you are open to the worst of sql injection attacks unless you carefully sanitize the string you paste into the query to only allow mathematical expressions.
Note that I can think of no application where this kind of operation would be useful. I'd say if you have to evaluate arbitrary strings, you're probably better of doing so in application code, not in the database.

Using a variable in a table name without dynmaic SQL

How can I use a variable in a SQL query without using dynamic SQL and the concat method?
I’d love to be able to declare variables at the start of a script to then use them throughout the script (e.g. table names)
Here’s an example of what I’d like to do:
declare variables
Set #Table1 = 'WhatsOn_20141208'
-- drop and re-create table
drop table if exists #Table1;
CREATE TABLE #Table1 (
rac_account_no varchar(100),
addressLine2 varchar(100)
);
-- load data into table
LOAD DATA LOCAL INFILE 'C:/Example.txt'
INTO TABLE #Table1
FIELDS TERMINATED BY '|'
ENCLOSED BY '"' LINES TERMINATED BY '\n'
IGNORE 1 LINES;
-- update addressLine2 column
Update #Table1
set addressLine2 = REPLACE(addressLine2,"*UNKNOWN*","");
If the table name changes, I want to be able to change it in the variables once rather than doing a find and replace of all occurrences.
The only solution I’ve found so far is using dynamic SQL and concatenating the string like this example:
SET #s = CONCAT('select * from ', #Cat, ' where ID = ', #ID_1);
PREPARE stmt1 FROM #s;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
Is there an easier way?
Cheers,
Lucas
You asked, How can I use a variable in a SQL query without using dynamic SQL and the concat method?
Sorry, you can't. Dynamic SQL is the way that's done in MySQL.
This applies to the names of schemas, tables, and columns -- that is, data dictionary names -- and not to values.
Most application developers who need variable names for data dictionary items construct the queries in their host language. That is, they use php or Java or something like that to turn strings like
SELECT xxx FROM yyy WHERE zzz
into strings like
SELECT id,name,value FROM transaction WHERE id=?
They then proceed to use bind variables for the data values.
MySQL prepared statements are simply a way of doing that kind of work inside the MySQL server rather than in the host language. But (in my opinion) prepared statements are hard to unit-test and troubleshoot, so it's more efficient software engineering to use your host language for that.
It's a little hazardous when the application's data source isn't trusted, so it's important to check every input for validity.