I am trying to implement SQL insertion protection to SQL select statements using RMySQL. I have been trying to prepend an escape character (i.e. a backslash) in front of each risk character - i.e. a quote (" or ') or backslash (\). I am using the RMySQL function, dbEscapeStrings which appears to be similar to PHP's mysql_real_escape_string function.
I suspect I am missing something very obvious but, as MySQL requires character strings in the WHERE statement to be enclosed by quotes, using dbEscapeStrings to apply escape characters to quotes in the select statement is throwing an error blocking all string queries, not just the injection attacks. For example,
user <- "'peter'"
tmp <- sprintf("select * from users where username = %s", user)
sql <- dbEscapeStrings(con, tmp)
dbGetQuery(con, sql)
dbEscapeStrings inserts a double backslash in front of each quote (i.e. the sql variable produced is "select * from users where username = \\'peter\\'") which throws a syntax error on the MySQL server when dbGetQuery is run.
Any suggestions appreciated on how to get the above to work or implement an alternative SQL insertion protection using RMySQL? Does RMySQL provide for using prepared statements that could prevent insertion attacks?
The safest (and easiest) way would be to use a parameterised query (this isn't available on the CRAN release, but is in the dev version)
dbGetQuery(myconnection, "SELECT * FROM users WHERE username = ?", list(user))
Related
I've been preaching both to my colleagues and here on SO about the goodness of using parameters in SQL queries, especially in .NET applications. I've even gone so far as to promise them as giving immunity against SQL injection attacks.
But I'm starting to wonder if this really is true. Are there any known SQL injection attacks that will be successfull against a parameterized query? Can you for example send a string that causes a buffer overflow on the server?
There are of course other considerations to make to ensure that a web application is safe (like sanitizing user input and all that stuff) but now I am thinking of SQL injections. I'm especially interested in attacks against MsSQL 2005 and 2008 since they are my primary databases, but all databases are interesting.
Edit: To clarify what I mean by parameters and parameterized queries. By using parameters I mean using "variables" instead of building the sql query in a string.
So instead of doing this:
SELECT * FROM Table WHERE Name = 'a name'
We do this:
SELECT * FROM Table WHERE Name = #Name
and then set the value of the #Name parameter on the query / command object.
Placeholders are enough to prevent injections. You might still be open to buffer overflows, but that is a completely different flavor of attack from an SQL injection (the attack vector would not be SQL syntax but binary). Since the parameters passed will all be escaped properly, there isn't any way for an attacker to pass data that will be treated like "live" SQL.
You can't use functions inside placeholders, and you can't use placeholders as column or table names, because they are escaped and quoted as string literals.
However, if you use parameters as part of a string concatenation inside your dynamic query, you are still vulnerable to injection, because your strings will not be escaped but will be literal. Using other types for parameters (such as integer) is safe.
That said, if you're using use input to set the value of something like security_level, then someone could just make themselves administrators in your system and have a free-for-all. But that's just basic input validation, and has nothing to do with SQL injection.
No, there is still risk of SQL injection any time you interpolate unvalidated data into an SQL query.
Query parameters help to avoid this risk by separating literal values from the SQL syntax.
'SELECT * FROM mytable WHERE colname = ?'
That's fine, but there are other purposes of interpolating data into a dynamic SQL query that cannot use query parameters, because it's not an SQL value but instead a table name, column name, expression, or some other syntax.
'SELECT * FROM ' + #tablename + ' WHERE colname IN (' + #comma_list + ')'
' ORDER BY ' + #colname'
It doesn't matter whether you're using stored procedures or executing dynamic SQL queries directly from application code. The risk is still there.
The remedy in these cases is to employ FIEO as needed:
Filter Input: validate that the data look like legitimate integers, table names, column names, etc. before you interpolate them.
Escape Output: in this case "output" means putting data into a SQL query. We use functions to transform variables used as string literals in an SQL expression, so that quote marks and other special characters inside the string are escaped. We should also use functions to transform variables that would be used as table names, column names, etc. As for other syntax, like writing whole SQL expressions dynamically, that's a more complex problem.
There seems to be some confusion in this thread about the definition of a "parameterised query".
SQL such as a stored proc that accepts parameters.
SQL that is called using the DBMS Parameters collection.
Given the former definition, many of the links show working attacks.
But the "normal" definition is the latter one. Given that definition, I don't know of any SQL injection attack that will work. That doesn't mean that there isn't one, but I have yet to see it.
From the comments, I'm not expressing myself clearly enough, so here's an example that will hopefully be clearer:
This approach is open to SQL injection
exec dbo.MyStoredProc 'DodgyText'
This approach isn't open to SQL injection
using (SqlCommand cmd = new SqlCommand("dbo.MyStoredProc", testConnection))
{
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter newParam = new SqlParameter(paramName, SqlDbType.Varchar);
newParam.Value = "DodgyText";
.....
cmd.Parameters.Add(newParam);
.....
cmd.ExecuteNonQuery();
}
any sql parameter of string type (varchar, nvarchar, etc) that is used to construct a dynamic query is still vulnerable
otherwise the parameter type conversion (e.g. to int, decimal, date, etc.) should eliminate any attempt to inject sql via the parameter
EDIT: an example, where parameter #p1 is intended to be a table name
create procedure dbo.uspBeAfraidBeVeryAfraid ( #p1 varchar(64) )
AS
SET NOCOUNT ON
declare #sql varchar(512)
set #sql = 'select * from ' + #p1
exec(#sql)
GO
If #p1 is selected from a drop-down list it is a potential sql-injection attack vector;
If #p1 is formulated programmatically w/out the ability of the user to intervene then it is not a potential sql-injection attack vector
A buffer overflow is not SQL injection.
Parametrized queries guarantee you are safe against SQL injection. They don't guarantee there aren't possible exploits in the form of bugs in your SQL server, but nothing will guarantee that.
Your data is not safe if you use dynamic sql in any way shape or form because the permissions must be at the table level. Yes you have limited the type and amount of injection attack from that particular query, but not limited the access a user can get if he or she finds a way into the system and you are completely vunerable to internal users accessing what they shouldn't in order to commit fraud or steal personal information to sell. Dynamic SQL of any type is a dangerous practice. If you use non-dynamic stored procs, you can set permissions at the procesdure level and no user can do anything except what is defined by the procs (except system admins of course).
It is possible for a stored proc to be vulnerable to special types of SQL injection via overflow/truncation, see: Injection Enabled by Data Truncation here:
http://msdn.microsoft.com/en-us/library/ms161953.aspx
Just remember that with parameters you can easily store the string, or say username if you don't have any policies, "); drop table users; --"
This in itself won't cause any harm, but you better know where and how that date is used further on in your application (e.g. stored in a cookie, retrieved later on to do other stuff.
You can run dynamic sql as example
DECLARE #SQL NVARCHAR(4000);
DECLARE #ParameterDefinition NVARCHAR(4000);
SELECT #ParameterDefinition = '#date varchar(10)'
SET #SQL='Select CAST(#date AS DATETIME) Date'
EXEC sp_executeSQL #SQL,#ParameterDefinition,#date='04/15/2011'
I'm using hibernate and mysql
when I run the following statement in mysql it works perfectly:
INSERT INTO table1 (name, is_visited) VALUES ('visit our site \'n\' days',true);
However, When I run with hibernate native query I get error:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'days',true)' at line 1.
Here is the java code:
String query = "INSERT INTO table1 (name, is_visited) VALUES ('visit our site \'n\' days',true)";
Query nativeQuery = getEntityManager().createNativeQuery(query);
nativeQuery.executeUpdate();
When I change the query statement to
"INSERT INTO table1 (name, is_visited) VALUES ('visit our site \'n days',true)";
it works.
looks like there is an issue with \'n\'
any idea?
What is happening here is that the following query is correctly escaping the single quotes when run directly on MySQL:
INSERT INTO table1 (name, is_visited) VALUES ('visit our site \'n\' days', true);
This works on MySQL because one valid way to escape literal quotes on MySQL is to escape them with backslash. However, doing this inside a Java string means that the \' are not being passed to MySQL. Instead, Java consumes the single backslash, and just the single quotes make it across to the database. I suggest using the other method of escaping single quotes here, which is to double them up:
String query = "INSERT INTO table1 (name, is_visited) VALUES ('visit our site ''n''x days', true)";
While it might be possible to escape the backslashes from Java, that seems confusing to me, because it requires keeping track of escaping across both your Java and database layer.
As another general comment, if you used the Hibernate ORM layer to do the insert, or used a prepared statement, you wouldn't have to worry about this escaping problem.
I am trying to accomplish a simple licensing system in golang and have tried numerous ways to get it to work. Basically, I have input a couple of random licensing keys into my database and my golang program should check to see if the user-input key exists and if it does then add the user specified username and password into the database to login later.
This is the code that I have that hasn't been working:
"IF EXISTS (SELECT * FROM login WHERE LK = "+reglicenceEntry.Text()+") THEN
INSERT INTO `login` (`Username`, `Password`, `LK`) VALUES
('"+regusernameEntry.Text()+"', '"+regpasswordEntry.Text()+"', ''); "
This is the golang error:
Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'IF EXISTS (SELECT * FROM login WHERE LK = '5qp515YHXEmSDzwqgoJh') THEN INSERT IN' at line 1
Thanks so much!
MySQL syntax doesn't support IF...THEN constructs except within stored routines and triggers and events. See https://dev.mysql.com/doc/refman/8.0/en/sql-syntax-compound-statements.html
I suggest an alternative solution for your code:
INSERT INTO `login` (`Username`, `Password`, `LK`)
SELECT ?, ?, ''
FROM `login`
WHERE `LK` = ?
LIMIT 1
If your login table does not have the LK value, the SELECT above will return 0 rows, therefore it will not insert anything.
If your login table has the LK value, the SELECT above will return at least 1 row (and I limit it to 1), therefore it will insert a row. The row it inserts is comprised of your username and password, and a blank string for the LK.
I showed use of parameter placeholders. You should use parameters in SQL instead of concatenating variables into your query. This is good practice to avoid accidental SQL injection. See http://go-database-sql.org/prepared.html for examples.
The purpose of using parameters is to avoid SQL injection problems. See my answer to What is SQL injection? for an explanation of SQL injection.
Or my presentation SQL Injection Myths and Fallacies (or youtube video).
When using parameters, you do two steps.
The first step to prepare a query with placeholders (?) where you would otherwise concatenate variables into your SQL query.
The second step is to execute the prepared query, and this is the time you pass the variables to fill in the placeholders.
The point is to keep variables separate from your query, so if there's anything in the variable that could unintentionally change your SQL syntax (like imbalanced quotes), it is never combined with the SQL. After you do the prepare, the SQL has already been parsed by the MySQL server, and there's no way to change the syntax after that.
MySQL remembers which parts of the query need to be filled in, and when you pass variables during the execute step, MySQL fills in the missing parts of the query using your values — but this happens within the MySQL server, not in your application.
Thus the dynamic parts of the query — your variables — are kept separate from the SQL syntax and you avoid SQL injection problems.
For your task described in your question, it would look something like this (I have not tested this Go code, but it should put you on the right path).
stmt, err := tx.Prepare("INSERT INTO `login` (`Username`, `Password`, `LK`) SELECT ?, ?, '' FROM `login` WHERE `LK` = ? LIMIT 1")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
_, err = stmt.Exec(regusernameEntry.Text(), regpasswordEntry.Text(), reglicenceEntry.Text())
if err != nil {
log.Fatal(err)
}
The order of parameters is important. The variables you pass to Exec() must be in the same order that the ? placeholders appear in your prepared SQL statement. They are matched up, one for one, in the same order, by the MySQL server.
Do not put quotes around the placeholders in your prepared SQL statement. That will work as a literal string '?' in SQL. Use an unquoted ? character for a placeholder. When it gets combined by MySQL in the server, it will work as if you had put quotes around the value like a string — but with no risk of SQL injection even if that string value containing special characters.
Here's another site that gives more code examples: https://github.com/go-sql-driver/mysql/wiki/Examples
The Exec() function is for executing SQL that has no result set, like INSERT, UPDATE, DELETE. There are other functions in the Go SQL driver like Query() and QueryRow() that also accept parameter arguments. You'd use these if your SQL returns a result set.
Here is my simple query:
my $SQLp = "SELECT MAX([PawnPayments].[CreationTimeDate]) as MaxTransDate
FROM [PawnSafeDBCE].[dbo].[PawnPayments]
INNER JOIN [PawnSafeDBCE].[dbo].[PawnPaymentDetails]
ON [PawnPayments[.[PaymentID] = [PawnPaymentDetails].[PaymentID]
WHERE [PawnPaymentDetails].[TicketID[ = '$TicketID'
AND [PawnPaymentDetails].[StoreID] ='$StoreID'
Note that query is written on Perl engine. I keep receiving an error that says:
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '[PawnPayments].[CreationTimeDate]) as MaxTransDate:"
I believe the error has to do with the bracket notation, but unfortunately, I am having to use this style due to a poorly constructed 3rd party table. Any help? Am I missing something obvious?
Huge EDIT: The table I am querying is actually on a SQL server, not a MySQL server! My database runs on the MySQL server, but this 3rd party database runs on an older version of Microsoft SQL.
I don't know why you have all those square brackets around your table and column names, but they aren't necessary and they aren't standard SQL. That's what is causing your syntax error.
my $SQLp = "SELECT MAX(PawnPayments.CreationTimeDate) as MaxTransDate
FROM PawnSafeDBCE.dbo.PawnPayments
INNER JOIN PawnSafeDBCE.dbo.PawnPaymentDetails
ON PawnPayments.PaymentID = PawnPaymentDetails.PaymentID
WHERE PawnPaymentDetails.TicketID = '$TicketID'
AND PawnPaymentDetails.StoreID ='$StoreID'";
I'll also add that having variables interpolated in your SQL statement like that is potentially leaving you open to SQL injection attacks. Far better to use bind points in your SQL and use extra arguments to execute to fill in the values (assuming you're using DBI).
my $SQLp = "SELECT MAX(PawnPayments.CreationTimeDate) as MaxTransDate
FROM PawnSafeDBCE.dbo.PawnPayments
INNER JOIN PawnSafeDBCE.dbo.PawnPaymentDetails
ON PawnPayments.PaymentID = PawnPaymentDetails.PaymentID
WHERE PawnPaymentDetails.TicketID = ?
AND PawnPaymentDetails.StoreID = ?";
my $sth = $dbh->prepare($SQLp);
$sth->execute($TicketID, $StoreID);
Update: As Bill Karwin points out in a comment, the database.schema.table syntax makes no sense in a MySQL database. So I think you're a little confused. The error message you are getting definitely mentions MySQL, so you're connecting to a MySQL server, using DBD::MySQL - but perhaps you should be connecting to an MSSQL server instead.
It might be useful if you showed us your database connection code - the call that sets up your $dbh (or equivalent) variable.
You say you are querying a MS SQL database, but the error message clearly says you are using a MySQL database or a MySQL database driver.
If you are querying a MS SQL database, fix your connection string.
If you are querying a MySQL database, use a MySQL-compatible query. MySQL uses backticks to quote identifiers (not square brackets like MS SQL).
[PawnPayments].[CreationTimeDate]
should be
`PawnPayments`.`CreationTimeDate`
Note that your code suffers from injection bugs due to incorrect quoting of value inserted into the SQL query. (It's not good enough just to put quotes around the values!) These can cause your code to fail, and they could make you vulnerable to injection attacks. Fix the quoting, or use replaceable parameters.
I'm trying to write sql to insert a SQL code into one of the table's columns.
The table has these three columns: email, verification code, sql.
I try this code, and variations of it, playing around with the quotes and backslashing/escaping them, etc... but something's still wrong:
INSERT INTO pre_registration(email, verification_code, sql) VALUES('myemail#gmail.com', '8efb100a295c0c690931222ff4467bb8', '"INSERT INTO customer(title) VALUES(\'Mr.\')"')
It tells me there's an error in the SQL syntax:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'sql) VALUES('myemail#gmail.com', '89f0fd5c927d466d6ec9a21b9ac34ffa', "INSER' at line 1
How to do it? I'm using PHP/MySQL.
MySQL considers sql as a keyword. You have to quote it:
INSERT INTO pre_registration(email, verification_code, `sql`) VALUES('myemail#gmail.com', '8efb100a295c0c690931222ff4467bb8', '"INSERT INTO customer(title) VALUES(\'Mr.\')"')
By the way double the quotes to escape them instead of using bakslashes. This is more SQL friendly.
INSERT INTO pre_registration(email, verification_code, `sql`) VALUES('myemail#gmail.com', '8efb100a295c0c690931222ff4467bb8', '"INSERT INTO customer(title) VALUES(''Mr.'')"')
Some insight into the exact SQL error would help. At first glance I'd say you need to apply spaces between the table name and the open parentheses and between values and the open parentheses.
Also, the Single quotes around the double quotes for the SQL portion may be creating an error though I am not certain. Whatever is between the single quotes is interpreted literally which should make the escape characters actually be slashes inside the stored data.
Also, sql is a reserved word that must be quoted for use.
Finally, depending on your situation there may be a more secure method of data entry using prepare and bound parameters:
try
{
$conn = new PDO ( "sqlsrv:server = $serverstringname; Database = $logindatabase", "$loginusername", "$loginpassword");
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch ( PDOException $e )
{
print( "Error connecting to SQL Server." );
die(print_r($e));
}
$email = 'myemail#gmail.com' //or some other way of setting the variable like $_POST
$verification_code = '#####' //or $_Post method
$sql = 'Put Query Here' //probably have to declare this explicitly
$sql_insert = "INSERT INTO pre_registration_info (email, verification_code, 'sql') VALUES (?,?,?)";
$stmt = $conn->prepare($sql_insert);
$stmt->bindValue(1, $email);
$stmt->bindValue(2, $verification_code);
$stmt->bindValue(3, $sql);
$stmt->execute();