28

I need to protect an application from SQL injection. Application is connecting to Oracle, using ADO, and search for the username and password to make the authentication.

From what I've read until now, the best approach is by using parameters, not assigning the entire SQL as string. Something like this:

query.SQL.Text := 'select * from table_name where name=:Name and id=:ID'; 
query.Prepare; 
query.ParamByName( 'Name' ).AsString := name; 
query.ParamByName( 'ID' ).AsInteger := id; 
query.Open;

Also, I'm thinking to verify the input from user, and to delete SQL keywords like delete,insert,select,etc...Any input character different than normal ASCII letters and numbers will be deleted.

This will assure me a minimum of security level?

I do not want to use any other components than Delphi 7 standard and Jedi.

Johan
  • 74,508
  • 24
  • 191
  • 319
RBA
  • 12,337
  • 16
  • 79
  • 126

3 Answers3

45

Safe

query.SQL.Text := 'select * from table_name where name=:Name';

This code is safe because you are using parameters.
Parameters are always safe from SQL-injection.

Unsafe

var Username: string;
...
query.SQL.Text := 'select * from table_name where name='+ UserName;

Is unsafe because Username could be name; Drop table_name; Resulting in the following query being executed.

select * from table_name where name=name; Drop table_name;

Also Unsafe

var Username: string;
...
query.SQL.Text := 'select * from table_name where name='''+ UserName+'''';

Because it if username is ' or (1=1); Drop Table_name; -- It will result in the following query:

select * from table_name where name='' or (1=1); Drop Table_name; -- '

But this code is safe

var id: integer;
...
query.SQL.Text := 'select * from table_name where id='+IntToStr(id);

Because IntToStr() will only accept integers so no SQL code can be injected into the query string this way, only numbers (which is exactly what you want and thus allowed)

But I want to do stuff that can't be done with parameters

Parameters can only be used for values. They cannot replace field names or table names. So if you want to execute this query

query:= 'SELECT * FROM :dynamic_table '; {doesn't work}
query:= 'SELECT * FROM '+tableName;      {works, but is unsafe}

The first query fails because you cannot use parameters for table or field names.
The second query is unsafe but is the only way this this can be done.
How to you stay safe?

You have to check the string tablename against a list of approved names.

Const
  ApprovedTables: array[0..1] of string = ('table1','table2');

procedure DoQuery(tablename: string);
var
  i: integer;
  Approved: boolean;
  query: string;
begin
  Approved:= false;
  for i:= lo(ApprovedTables) to hi(ApprovedTables) do begin
    Approved:= Approved or (lowercase(tablename) = ApprovedTables[i]);
  end; {for i}
  if not Approved then exit;
  query:= 'SELECT * FROM '+tablename;
  ...

That's the only way to do this, that I know of.

BTW Your original code has an error:

query.SQL.Text := 'select * from table_name where name=:Name where id=:ID'; 

Should be

query.SQL.Text := 'select * from table_name where name=:Name and id=:ID'; 

You cannot have two where's in one (sub)query

Johan
  • 74,508
  • 24
  • 191
  • 319
  • But still you can inject SQL parameter with `name; Drop table_name;` if it's entered by user in such kind of input box –  May 16 '11 at 10:39
  • @daemon_x: In the first scenario, entering `name; Drop table_name;` in a text box would result in the query `select * from table_name where name='name; Drop table_name;';`, which would be perfectly safe. – Allan May 16 '11 at 16:55
  • @Allan - and what if I use `name'; Drop table_name; Select '0` Will the result be `select * from table_name where name='name'; Drop table_name; Select '0'` ? I'm not a parameter user so I don't know if the single quotes will be doubled (but I hope they are). –  May 16 '11 at 17:18
  • 2
    @daemon_x, parameters never make it into actual SQL, the always get thrown right into the field. Just like the contents of a field are never part of the SQL statement. `select * from table1 where a = 'hello'. If field `a` contains `name'; Drop table1;` it doesn't do anything bad, that's the beauty of parameters. – Johan May 16 '11 at 21:03
  • 1
    @Johan - now I see that; I thought the client side prepares the final query by replacing parameter values, but they are passed as real SQL parameters (`@`) to the DB engine. So +1 (when I'll get votes for tomorrow :)) –  May 16 '11 at 22:14
  • So on a login form, how do you set the parameters to equal the user input for `username` and `password`? If I have `SELECT * FROM [users] WHERE username = :username AND password = :password` where do I tell the page `$username = $_POST['username']` and `$password = $_POST['password']` or is that still the way the variables are formed? – Jamie Jul 23 '12 at 16:06
  • @jlg, you'd use a salted hash to store the password obviously. In delphi, you say: `query1.parambyname('username').AsString:= UsernameFromForm;` – Johan Jul 25 '12 at 08:33
  • Btw jedi tjvquery has macros and you can use those to change things such as table name, column names.The sql is preprocessed within jedi with the macro values as text. Taje care thiugh this could redult in an sql injection attack. Be sure to edit the values against a white list or otherwise ensure only vsl I d values – DavidG Nov 08 '13 at 23:14
12

If you allow the user to influence only the value of parameters that will be bound into an sql command text with placeholders, then you don't really need to inspect what the user enters: the simplest way to avoid SQL injection, as you mention, is to avoid concatenated SQL, and using bound variables (or calling procedures) does this (it also has the advantage - mileage/relevance depends on the database - of allowing the engine to re-use query plans).

If you are using Oracle, then you need to have a really good reason for not using bound variables: Tom Kyte has a ton of good information about this on his site http://asktom.oracle.com. Just enter "bound variables" in the search box.

davek
  • 22,499
  • 9
  • 75
  • 95
6

This will assure me a minimum of security level?

Yes parametrized queries should protect you from SQL injection which would be easy to test. Simply input some dangerous string in the name variable and see what happens. Normally you should get 0 rows returned and not an error.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928