A line of code like
my $sql_query = "SELECT * FROM Users WHERE user='$user';";
might introduce an SQL injection vulnerability into your program. To avoid this one could use something like
my $sth = $dbh->prepare("SELECT * FROM Users WHERE user='?';");
$dbh->execute($user);
However, in the code I am currently working on the following is used
$sql_query = "SELECT * FROM Users WHERE user='" . $user . "';";
$dbh->prepare($sql_query);
$dbh->execute();
Does this actually work? If yes, are there any differences to what I would have done? What are the advantages and disadvantages?
my $sth = $dbh->prepare("SELECT * FROM Users WHERE user='?'");
This won't work because it's searching for a literal '?' character — not a parameter. If you try to send a value for the parameter, MySQL will be like, "what do you want me to do with this?" because the query has no parameter placeholder.
If you want to use a parameter, you must NOT put the parameter placeholder inside string delimiters in the SQL query, even if the parameter will take string or datetime value:
my $sth = $dbh->prepare("SELECT * FROM Users WHERE user=?");
The next example:
$sql_query = "SELECT * FROM Users WHERE user='" . $user . "'";
$dbh->prepare($sql_query);
$dbh->execute();
That will run the query, but it's NOT safe. You can prepare any query even if it has no parameters.
Using prepare() is not what makes queries safe from SQL injection. What makes it safer is using parameters to combine dynamic values instead of doing string-concatenation like you're doing in this example.
But using parameters does require the use of prepare().
PS: You don't need to put ; at the end of your SQL queries when you run them one at a time programmatically. The separator is only needed if you run multiple queries, like in an SQL script, or in a stored procedure. In your examples, the ; is harmless but MySQL doesn't require it, and it will just ignore it.
Related
MySQLdb: cursor.query( 'select * from %s;', ('thistable',) )
should end up as: 'select * from thistable'
actually ends up as: "select * from 'thistable' "
the db natually throws syntax error: ...near ' 'thistable' ' at ...
It behaves as though the data converter is including the string's quotes as part of the string, ie the string is " 'thistable' " instead of 'thistable'. Any and all help with this is deeply appreciated. One thing I did notice in my questing is that the script's charset is utf8, while the db server and db are latin1. Could this be my problem?
OS: os X Sierra
python: 3.6
MySQL: 5.3.6
mysql-connector-c: 6.1.11
There's a difference between building dynamic SQL and building parameterized queries.
In general, parameterized queries let you plug in values for comparison/input, but not database objects. So, your application code assumes that %s is a quoted literal, not an database object.
If you need to dynamically add in database objects (tables, columns, procedure names, etc.), you will probably need to build the query string first, with placeholders (like %s) for actual parameters you need to pass in.
In this case, ypercubeᵀᴹ's suggestion to simply use the string 'select * from thistable' directly. If you needed to run the same query against multiple tables, loop through the list of table, building the string like:
queryString = 'SELECT * FROM ' + currTable
I am doing a project for my school and I am task to debug all of the issues found using the application call HPE Fortify. The report generated by the application only indicates the code below prone to SQL injection:
String sql = " select Distinct p1.desc1,p2.desc2 from parameter p1"
+" inner join parameter p2"
+" on p1.ParaCode1='CR_DERIVE' and p1.ParaCode2=p2.Desc2"
+" inner join parameter p3"
+ " on p2.ParaCode3=p3.ParaCode1 and p3.ParaCode3=p2.Desc2"
+" where p2.paracode1='ATTRIBUTE'"
+ " and p2.ParaCode2='" + ddl_attribute.SelectedValue + "'";
But not the codes below:
strSQL = "SELECT Paracode2 FROM Parameter WHERE Paracode1 = 'PROGMGR' AND Desc1 = '" + login + "' AND Status = 'A' ";
I would like to know the reason why as I am unclear regarding SQL injection and I am new to this. Thanks for the response
You're concatenating some application variables into your SQL query string, so the safety depends on how those variables' values were set. Did they come from some untrusted input? Or were they set from some safe application data?
If HPE Fortify has analyzed your code and knows how your login variable was assigned its value, it may be able to tell that it's safe to use in an SQL expression. Whereas it may not be able to make that conclusion about the SelectedValue variable, so it assumes it's unsafe and therefore could cause an SQL vulnerability.
The Perl language does something similar, without the use of a tool like HPE Fortify. Every Perl variable is either "tainted" or "untainted" depending on where it got its value. So you can tell whether a variable is safe to use in SQL, or in eval() or other possible code-injection situations. It's a pity more languages don't support something similar.
But I agree with other commenters that you should learn to use query parameters. It's easy and it's safe. And you can stop getting eyestrain figuring out if you've balanced your quotes-within-quotes correctly.
Your code sample looks like it might be Java. Here's an example in Java:
strSQL = "SELECT Paracode2 FROM Parameter"
+ " WHERE Paracode1 = 'PROGMGR' AND Desc1 = ? AND Status = 'A' ";
Notice the ? placeholder for the parameter has no single-quotes around it within the SQL string. You must not put SQL quotes around the placeholder.
PreparedStatement stmt = con.PreparedStatement(sql);
stmt.setString(1, login);
ResultSet rs = stmt.executeQuery();
For more information to help you understand SQL injection, you might like my presentation SQL Injection Myths and Fallacies, or the video of me delivering that talk: https://www.youtube.com/watch?v=VldxqTejybk
I'm having problems with my query returning errors. What I have at this moment is this:
comi C X'7D'
SqlQuery S 500A
VarSel S 10A
VarSel = *blanks;
SqlQuery = 'select USU into VarSel from FXXUSELN ' +
'where USU = upper(' + comi + %trim(pusuario) +
comi + ') and PWDUSU = upper(' + comi +
%trim(ppassword) + comi + ')';
Exec SQL execute immediate :SqlQuery;
psqlcod = sqlcod;
But when I try to debug that code, it returns -084: UNACCEPTABLE SQL STATEMENT as sqlcod. Printing the SqlQuery variable in the debugger call I get this string, which seems to be correct:
select USU into VarSel from FXXUSELN where USU = upper('usua
rio') and PWDUSU = upper('password')
Anybody knows how to solve this problem so I can make a login stored procedure? Thanks in advance.
The problem is that SELECT INTO can not be dynamically prepared.
If you look at the Invocation section of SELECT INTO in the manual you'll see:
This statement can only be embedded in an application program. It is an executable statement that cannot be dynamically prepared. It must not be specified in REXX.
Also note the following Actions Allowed on SQL Statements section of appendix B in the manual which summarizes where/how each statement may be used.
Finally, if what you tried had worked, you'd have opened yourself up to SQL Injection attacks. Generally speaking, for any DB you should avoid dynamic SQL. If you must use dynamic SQL, then you should use parameter makers instead of directly concatenating user input into the string.
In your case, there's no need for dynamic SQL if the first place. Static SQL is easier, safer, and allows the use of SELECT INTO
exec SQL
select USU into :VarSel from FXXUSELN
where USU = upper(:pusuario)
and PWDUSU = upper(:ppassword);
Is there a way to reuse the ?'s used on a DBI prepare statement. Consider the following code:
$sth=$dbh->prepare("INSERT INTO mytable(a,b,c) SELECT ?,B(?),C(?)");
$sth->execute($a,$a,$a);
It would be very nice to instead use something like this:
#I'm making this up as something I hope exists
$sth=$dbh->prepare("INSERT INTO mytable(a,b,c) SELECT ?,B(?:1),C(?:1)");
$sth->execute($a);
Notice that only one $a is passed to the execute instead of three. Is there a way to do this in real life?
It depends on your DBD. For example, using DBD::Pg with the $1 style of placeholders, or DBD::Oracle with named placeholders and bind_param, you can do exactly what you like. But using the general purpose ? style of placeholders that works DBI-wide, it's not possible.
If you use a library to generate your SQL statements for you, e.g. SQL::Abstract or a full-on ORM like DBIx::Class, you won't have to worry about things like that.
Alternatively you can do something similar with just a few lines of code:
my $sql = 'INSERT INTO ...blah blah... VALUES (' . (join(', ', ('?') x scalar(#insert_elements))) . ')';
#hobbs' answer is right -- default DBI placeholders can't do it. #Ether's answer is right -- a SQL abstraction can make this a non-issue.
However, typically one need only bind each distinct parameterized value once. In your example, using a scalar derived table makes the user-supplied value available by name to the rest of the query:
my $sth = $dbh->prepare(<<'__eosql');
INSERT INTO mytable(a,b,c)
SELECT x, B(x), C(x) FROM (SELECT ? AS x) subq
-- Subquery may vary from DB to DB:
-- "FROM (SELECT ? AS x FROM DUAL) subq"
-- "FROM (SELECT ? FROM rdb$database) subq(x)"
-- "FROM (VALUES (?)) subq(x)"
-- etc.
__eosql
for $v (#values) {
$sth->execute($v);
}
Usually this is incrementally more "wire efficient" than the alternative, since the user-supplied parameter is typically transmitted just once instead of N times.
You can set SQL variables in one SQL statement and then use that variable multiple times in the next query.
$dbh->do('set #reusable = ?', undef, $perl_var);
$dbh->select_arrayref('select * from table where cola = #reusable or colb = #reusable');
No duplicated variables and you still get the safety of parameterized queries.
How do I using a variable for the name of a table in a DBI query? I know how to use placeholders as part of the where clause, but how do I do this for the table name?
I would like to do something like this:
my $table_name='table1';
my $query = $dbh_cgi->prepare("select * from ?");
$query->execute($table_name);
So far, I end up getting a MySQL syntax error because DBI adds quotes around the name, table1.
One of the limitations of placeholders is that they can't be used for table names. Instead of using a placeholder, you can use a variable. To make sure the variable's contents are safe, use quote_identifier(), for example:
my $table_name='table1'; #-- Or get this from somewhere else
my $safe_table_name = $dbh_cgi->quote_identifier($table_name);
my $query = $dbh_cgi->prepare("select * from $safe_table_name");
$query->execute();