views:

229

answers:

4

Hi,

I have recently taken on a project in which I need to integrate with PHP/SQL Server. I am looking for the quickest and easiest function to prevent SQL injection on SQL Server as I prefer MySQL and do not anticipate many more SQL Server related projects.

Is this function sufficient?

$someVal = mssql_escape($_POST['someVal']);

$query = "INSERT INTO tblName SET field = $someVal";

mssql_execute($query);

function mssql_escape($str) {
    return str_replace("'", "''", $str);
}

If not, what additional steps should I take?


EDIT: I am running on a Linux server - sqlsrv_query() only works if your hosting environment is windows

+10  A: 

The best option: do not use SQL statements that get concatenated together - use parametrized queries.

E.g. do not create something like

string stmt = "INSERT INTO dbo.MyTable(field1,field2) VALUES(" + value1 + ", " + value2 + ")"

or something like that and then try to "sanitize" it by replacing single quotes or something - you'll never catch everything, someone will always find a way around your "safe guarding".

Instead, use:

string stmt = "INSERT INTO dbo.MyTable(field1,field2) VALUES(@value1, @value2)";

and then set the parameter values before executing this INSERT statement. This is really the only reliable way to avoid SQL injection - use it!

UPDATE: how to use parametrized queries from PHP - I found something here - does that help at all?

$tsql = "INSERT INTO DateTimeTable (myDate, myTime,
                                    myDateTimeOffset, myDatetime2)
         VALUES (?, ?, ?, ?)";

$params = array(
            date("Y-m-d"), // Current date in Y-m-d format.
            "15:30:41.987", // Time as a string.
            date("c"), // Current date in ISO 8601 format.
            date("Y-m-d H:i:s.u") // Current date and time.
          );

$stmt = sqlsrv_query($conn, $tsql, $params);

So it seems you can't use "named" parameters like @value1, @value2, but instead you just use question marks ? for each parameter, and you basically just create a parameter array which you then pass into the query.

This article Accessing SQL Server Databases with PHP might also help - it has a similar sample of how to insert data using the parametrized queries.

UPDATE: after you've revealed that you're on Linux, this approach doesn't work anymore. Instead, you need to use an alternate library in PHP to call a database - something like PDO.

PDO should work both on any *nix type operating system, and against all sorts of databases, including SQL Server, and it supports parametrized queries, too:

$db = new PDO('your-connection-string-here');
$stmt = $db->prepare("SELECT priv FROM testUsers WHERE username=:username AND password=:password");
$stmt->bindParam(':username', $user);
$stmt->bindParam(':password', $pass);
$stmt->execute();
marc_s
Plus it often has other benefits, too, like query plan caching, readability, etc.
Michael Haren
Could you perhaps give an example of what setting those parameter's might look like?
Derek Adair
awesome, I've been trying to track something like that down all morning!!!
Derek Adair
I think you can use named parameters, like :value, but it might just be for MySQLi or PDO, not totally sure.
iconiK
I guess sqlsrv_query() only works on a windows serving environment =( I'm running Linux
Derek Adair
I apologize, I didn't mean to offend or anything. I was unaware that sqlsrv_query() was specific to a windows environment.
Derek Adair
was "un-accepting" improper SO etiquette?
Derek Adair
+2  A: 

No, it's not sufficient. To my knowledge, string replacement can never really be sufficient in general (on any platform).

To prevent SQL injection, all queries need to be parameterized - either as parameterized queries or as stored procedures with parameters.

In these cases, the database calling library (i.e. ADO.NET and SQL Command) sends the parameters separately from the query and the server applies them, which eliminates the ability for the actual SQL to be altered in any way. This has numerous benefits besides injection, which include code page issues and date conversion issues - for that matter any conversions to string can be problematic if the server does not expect them done the way the client does them.

Cade Roux
Escaping quotes is *sufficient*, it's just not a best practice, for the reasons you mentioned, as well as query plan caching, and the peace of mind that if you always parameterize, you never need to escape.
richardtallent
@richardtallent - there are numerous loopholes with escaping - see http://stackoverflow.com/questions/139199/can-i-protect-against-sql-injection-by-escaping-single-quote-and-surrounding-user/139810#139810
Cade Roux
@Cade, the loopholes w/r/t string escaping relate to MySQL's alternate escaping syntax, and potential DBMS translation of Unicode 0x02BC, neither of which apply to MSSQL (yes, I tried the Unicode thing when I heard about it initially, no cigar). So unless he's using a weird QUOTED_IDENTIFIER setting (who does that?), or doing something dumb like truncating escaped strings, what he was doing was *sufficient*, just not optimal. Your advice is good ("use parameters"), but your statement is unproven ("to prevent sql injection, all queries need to be parameterized").
richardtallent
+2  A: 

String replacement to escape quotes IS sufficient to prevent SQL injection attack vectors.

This only applies to SQL Server when QUOTED_IDENTIFIER is ON, and when you don't do something stoopid to your escaped string, such as truncating it or translating your Unicode string to an 8-bit string after escaping. In particular, you need to make sure QUOTED_IDENTIFIER is set to ON. Usually that's the default, but it may depend on the library you are using in PHP to access MSSQL.

Parameterization is a best practice, but there is nothing inherently insecure about escaping quotes to prevent SQL injection, with due care.

The rel issue with escaping strings is not the efficacy of the replacement, it is the potential for forgetting to do the replacement every time.

That said, your code escapes the value, but does not wrap the value in quotes. You need something like this instead:

function mssql_escape($str) {
  return "N'" + str_replace("'", "''", $str) + "'";
}

The N above allows you to pass higher Unicode characters. If that's not a concern (i.e., your text fields are varchar rather than nvarchar), you can remove the N.

Now, if you do this, there are some caveats:

  1. You need to make DAMNED SURE you call mssql_escape for every string value. And therein lies the rub.
  2. Dates and GUID values also need escaping in the same manner.
  3. You should validate numeric values, or at least escape them as well using the same function (MSSQL will cast the string to the appropriate numeric type).

Again, like others have said, parameterized queries are safer--not because escaping quotes doesn't work (it does except as noted above), but because it's easier to visually make sure you didn't forget to escape something.

richardtallent
+1  A: 

I partially disagree with other posters. If you run all your parameters through a function that double the quotes, this should prevent any possible injection attack. Actually in practice the more frequent problem is not deliberate sabotague but queries that break because a value legitimately includes a single quote, like a customer named "O'Hara" or a comment field of "Don't call Sally before 9:00". Anyway, I do escapes like this all the time and have never had a problem.

One caveat: On some database engines, there could be other dangerous characters besides a single quote. The only example I know is Postgres, where the backslash is magic. In this case your escape function must also double backslashes. Check the documentation.

I have nothing against using prepared statements, and for simple cases, where the only thing that changes is the value of the parameter, they are an excellent solution. But I routinely find that I have to build queries in pieces based on conditions in the program, like if parameter X is not null then not only do I need to add it to the where clause but I also need an additional join to get to the value I really need to test. Prepared statements can't handle this. You could, of course, build the SQL in pieces, turn it into a prepared statement, and then supply the parameters. But this is just a pain for no clear gain.

These days I mostly code in Java that allows functions to be overloaded, that is, have multiple implementations depending on the type of the passed in parameter. So I routine write a set of functions that I normally name simply "q" for "quote", that return the given type, suitably quoted. For strings, it doubles any quote marks, then slaps quote marks around the whole thing. For integers it just returns the string representation of the integer. For dates it converts to the JDBC (Java SQL) standard date format, which the driver is then supposed to convert to whatever is needed for the specific database being used. Etc. (On my current project I even included array as a passed in type, which I convert to a format suitable for use in an IN clause.) Then every time I want to include a field in a SQL statement, I just write "q(x)". As this is slapping quotes on when necessary, I don't need the extra string manipulation to put on quotes, so it's probably just as easy as not doing the escape.

For example, vulnerable way:

String myquery="select name from customer where customercode='"+custcode+"'";

Safe way:

String myquery="select name from customer where customercode="+q(custcode);

The right way is not particularly more to type than the wrong way, so it's easy to get in a good habit.

Jay
As I'm sure you know, there is more to sanitizing that just replacing single quotes with double quotes. The other important aspect is ensuring that the values are of type claimed. I.e., verifying that dates are really dates, that integers are really integers, that the snozzberries really taste like snozzberries. :)
Thomas
@Thomas: I'm not sure what you're driving at. The question was about SQL injection attacks. If someone enters "foobar" for a numeric value, and we create a query that says, say, "select plugh from zork where plughid='foobar'", in mySQL and Postgres he just gets a not-found. Maybe some engines give a SQL error. But there's no danger that a hacker could use this to gain access to a database, or that it could lead to database corruption.
Jay
@Jay - My point is that there are more reasons to sanitize your input than just to prevent SQL injection. You should sanitize inputs to catch problems as early as possible before you make your call to the database. It is a bad practice to *solely* rely on your database to verify the data types (and general validity). Thus any routine you build to sanitize inputs should *also* check, at the very least, for validity of the data types.
Thomas
@Thomas: I certainly agree that a program should check the validity of its inputs. But this is a whole different issue from preventing SQL injection. If a user enters "Lots" for a dollar amount, this is an input error and should be rejected with a message to the user. But there is nothing inherently illegal about having quotes embedded in string variables. "O'Hara" is a perfectly valid name; "Don't give up the ship" could be a perfectly valid comment field, etc. Such inputs shouldn't be rejected as invalid: they should just be properly escaped when inserted in a SQL statement.
Jay
Continued: I suppose you could write an input validation function that checks if the input meets various criteria to be legal and returns an error message if it does not, and if it does as a side effect it doubles the quotes for SQL, but I think that would be a bad idea. It would be mixing two un-related tasks in one function.
Jay
@Jay: I think we are discuss two different types of validation. I'm referring to simple data type and injection prevent validation. If the program tries to pass 'foo' to a numeric parameter or column it should be rejected before it gets to the database.
Thomas