Mysqli_real_escape_string vulnerable - php

So I was busy with PHP, inserting data into mysql when I wanted to know: I have come accross some posts that say it's bad practice to use single quotes to insert data into a database. one of the examples: Why are VALUES written between quotes when sent to a database?
The post is about why they're written between quotes, but one thing was clear: It's bad practice to insert it like:
$sql = INSERT INTO example (example1, example2, example3) VALUES
('$example1', '$example2', '$example3');
Why is this be bad practice? Apparently it is vurnerable to injection as stated in the above link given. the OP his question was related to the comment was: We use mysqli_real_escape_string for this. The respons given was:
#XX To a large extent, yes, it is an alternative solution to the problem. It doesn't disable anything, but it escapes things so that for instance ' becomes '' or \' in the SQL string, keeping the attacker from ending the string. There are awkward cases where such escaping is hard, and it's easy to miss one escape call among many, which is why parametrised queries are considered the most reliable approach.
First of all: How does a script want to fool mysqli_real_escape_string into NOT escaping certain stuff? I found something that said the following and correct me if i'm wrong: mysqli_real_escape_string - example for 100% safety. As you can see he refers to another page, that has an answer. However He then makes a claim that should make his data 100% safe and someone else responds with:
Yes, that is generally safe. Mysql and mysqli are perfectly safe when used right (specific bugs in very specific encodings notwithstanding). The advantage of prepared statements is that it's more difficult to do things the wrong way.
I have the following example to make it clear for myself: I have 2 doors, 1 door is open, but behind a closed door. How would you attack an open door with a closed door in front of it?
There is an answer here: SQL injection that gets around mysql_real_escape_string(), but he says as an safe example:
mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");`
Isn't mysqli_real_escape_string already doing the same? he's just specifying what characters should be mysqli_real_escaped_string. So how can this all of the sudden become safe? Since it is doing the exact same thing as when you would say:
$example = mysqli_real_escape_string ($conn, $_POST['exampleVariable']);
So how does this:
mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
become safe and this:
$example = mysqli_real_escape_string ($conn, $_POST['exampleVariable']);
not? Isn't he just narrowing down what mysqli_real_escape_string would escape, thus making it more vulnerable?

The thing is, mysqli_real_escape_string() or other proper escaping is actually technically secure, in the end, that's about what parameterized queries do too. However, there is always a however. For example it is only secure if you have quotes around variables. Without quotes it is not. When it's a 1000 line project with one developer, it's probably ok in the first 3 months. Then eventually even that one developer will forget the quotes, because they are not always needed syntactically. Imagine what will happen in a 2 million LOC project with 200 devs, after 5 years.
Similarly, you must not ever forget using your manual escape function. It may be easy first. Then there is a variable that is validated and can only hold a number, so you are sure it's ok to just put it in without escaping. Then you change your mind and change it into a string, because the query is ok anyway. Or somebody else does it after 2 years. And so on. It's not a technical issue. It's a management-ish issue, in the long run, your code will be vulnerable somehow. Hence it is bad practice.
Another point, it's much harder to automatically scan for SQL injection vulnerabilities if manual escaping is in place. Static code scanners can easily find all instances of concatenated queries, but it's very hard for them to correlate previous escaping if there is any. If you use something like parameterized queries, it is straightforward to find sql injections, because all concatenations are potential candidates.

Setting the character set for the connection does not change what mysqli_real_escape_string() escapes. But it avoids the multi-byte character bug because it controls how the string is interpreted after the backslash escape characters have been inserted.
Of course, you can avoid any uncertainty by using query parameters instead.

Related

Shortcomings of mysql_real_escape_string?

I have seen a few people on here state that concatenating queries using mysql_real_escape_string will not protect you (entirely) from SQL injection attacks.
However, I am yet to see an example of input that illustrates an attack that mysql_real_escape_string would not protect you from. The majority of examples forget that mysql_query is limited to one query and use mysql_real_escape_string incorrectly.
The only example I can think of is the following:
mysql_query('DELETE FROM users WHERE user_id = '.mysql_real_escape_string($input));
This would not protect you from the following input:
5 OR 1=1
I would see this as incorrect usage of mysql_real_escape_string rather than a shortcoming, it is designed for strings not numeric values. You should either cast to a numeric type or if you are going to treat the input as a string when sanitising you should do the same in your query and wrap quotation marks around it.
Can anyone provide an example of input that can get around mysql_real_escape_string that does not rely on incorrect handling of numeric values or forget that mysql_query can only execute one query?
Edit: I am interested in the limitations of mysql_real_escape_string and not comparing it to alternatives, I realise there are better options for new projects and am not disputing that.
The main shortcoming of mysql_real_escape_string, or of the mysql_ extension in general, is that it is harder to apply correctly than other, more modern APIs, especially prepared statements. mysql_real_escape_string is supposed to be used in exactly one case: escaping text content that is used as a value in an SQL statement between quotes. E.g.:
$value = mysql_real_escape_string($value, $link);
$sql = "... `foo` = '$value' ...";
^^^^^^
mysql_real_escape_string makes sure that the $value in the above context does not mess up the SQL syntax. It does not work as you may think here:
$sql = "... `foo` = $value ...";
or here:
$sql = "... `$value` ...";
or here:
$sql = mysql_real_escape_string("... `foo` = '$value' ...");
If applied to values which are used in any context other than a quoted string in an SQL statement, it is misapplied and may or may not mess up the resulting syntax and/or allow somebody to submit values which may enable SQL injection attacks. The use case of mysql_real_escape_string is very narrow, but is seldom correctly understood.
Another way to get yourself into hot water using mysql_real_escape_string is when you set the database connection encoding using the wrong method. You should do this:
mysql_set_charset('utf8', $link);
You can also do this though:
mysql_query("SET NAMES 'utf8'", $link);
The problem is that the latter bypasses the mysql_ API, which still thinks you're talking to the database using latin1 (or something else). When using mysql_real_escape_string now, it will assume the wrong character encoding and escape strings differently than the database will interpret them later. By running the SET NAMES query, you have created a rift between how the mysql_ client API is treating strings and how the database will interpret these strings. This can be used for injection attacks in certain multibyte string situations.
There are no fundamental injection vulnerabilities in mysql_real_escape_string that I am aware of if it is applied correctly. Again though, the main problem is that it is terrifyingly easy to apply it incorrectly, which opens up vulnerabilities.
Ok, so apart from mysql_* being deprecated, I understand your wanting to know about any possible workaround that might exist. perhaps this blog post and the slides might reveal some of them.But as this older question here shows, casting and quoting isn't full proof. There's just So many things that can wrong, and Murphy's law, twined with that ever valid mantra "Never trust the network", will go horribly wrong.
Perhaps this article, but most importantly, the follow-up to that article can reveal even more security issues. To be honest, I know mysql_real_escape_string isn't fullproof, even in combination with type casting and string formats:
printf('WHERE id = \'%d\'',(int)mysql_real_escape_string($_REQUEST['id']));
doesn't cover every possible attack. I'm no expert on this matter, but what I can tell you is sanitizing every input, will, if anything, give you a FALSE sense of security. Most of the time, you'll know (initially) what and why and how you protect against the attacks, but your colleagues might not. They might forget something, and your entire system is compromized.
In summary: Yes, you might be able to prevent any form of malicious input from getting to your DB, but every additional action it requires is an added risk. In that scenario, the greatest liability (as always) is the developer that hasn't had is fourth cup of coffee on a monday morning. No code, no matter how defensive and well thought out, can protect itself from the monster that is a tired developer with a bad temper, going cold turkey on caffeine and nicotine.

Is this use of str_replace sufficient to prevent SQL injection attacks?

Edit if you plan on answering this question please at least read it. Don't simply read the title, then Google 'sql injection php', and paste the results as an answer
First, I'm well aware that there are lots of resources available on how best to prevent SQL injection, but my question is specifically about if very little effort can be enough.
An organization I contract for was recently told that partners' (PHP) websites developed by their previous contractor have been found to have major security issues (my personal favourite is by using the string 'Emergency' in a URL you can gain unauthenticated access to any page in the site...)
I have been asked to review the security of a PHP site and highlight any major issues. I know from previous experience with this site that the standard of coding is truly awful (e.g. huge swathes of code duplicated across pages with around 5% variation, hundreds of unused variables, $var = "yes" in place of booleans, SQL statements written into every single script, unsecure by default (some pages forget to authenticate users), etc). Reviewing the site is a painful reminder that there are some real morons in the world that call themselves developers.
Because of these code quality issues I want to only highlight the serious and semi-serious security problems. If I note every problem with the site my review will take weeks.
I am not an expert on SQL injection, but it's my understanding that you must be able to close an existing query's quoted string before injecting any other kind of statement. So is the following line of code sufficient?
"'".str_replace("'","''",$_POST['var_name'])."'"
I'm not interested in suggestions about improving or changing the code, just whether it's possible in this scenario for SQL injection via $_POST['var_name']. If you're not familiar with PHP str_replace does replace all instances of the first argument with the second, not just the first instance.
Any input much appreciated.
No. In all honesty, if you are not preparing your statements, you are ASKING for a world of hurt.
Just because you escape your quotes with quotes, you are not protecting yourself. Think about this:
A user send you: username'; drop database foo;
You will escape it as username''; drop database foo;
But! if the user does: username\'; drop database foo;
You will be in trouble. You will resolve this for username\''; drop database foo;
Meaning the quote the user placed is escaped, and your quote ended the field username. The drop will then be execute. This is very unsecure.
You need to make sure you Prepare your statements or apply a quote command such as PDO::quote or mysqli_real_escape_string as these escape special characters.
You've got two options - Escaping the special characters in your unsafe_variable, or using a parameterized query.
$safe_variable = mysql_real_escape_string($_POST["user-input"]);
$mysqli = new mysqli("server", "username", "password", "database_name");
$unsafe_variable = $_POST["user-input"];
$stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
Both would protect you from SQL Injection. The parameterized query is considered better practice, but escaping characters in your variable will require fewer changes.
To answer your question: no, simply removing quotes is not enough to prevent SQL injection.
For example,
"SELECT * FROM user_table WHERE userid=$userid AND password=$password"
Could go badly wrong if $password contained "garbage OR 1", or something like that. So if they've got badly formatted queries, removing quotes won't help.
It depends on your database server, and its configuration.
Doubling ' to '' is the ANSI standard way of escaping string literal content, and should be enough.
But, if you're using MySQL or versions of PostgreSQL before 9.1, string literal syntax is not ANSI-conformant by default (it can be, if reconfigured), and the backslash character has a special meaning. This permits attacks like the \' in 680's answer.
Additionally, if you're passing byte strings to your data access function, and your database happens to be using an East Asian character encoding, you'll have the problem that str_replace does a byte replace on a ' that may be part of a multi-byte sequence, ending up with a high byte followed by '': the first quote gets eaten as part of the multi-byte sequence, then the second closes the string. (This doesn't happen in UTF-8 which is designed to avoid trailing low bytes.)
Using the appropriate escaping call for the DB you are using (eg mysql_real_escape_string), or, better, using parameterised queries, means you don't have to know about these obscure and annoying edge cases.

filter_input and mysqli_real_escape_string for integers

I'm developing a simple PHP database application for internal use but would like to code it to best practices. Some of my pages are receiving integer values from GET requests and I'm just wondering how much validation and sanitation is really required.
Currently I'm using $num = filter_input(INPUT_GET, 'num', FILTER_VALIDATE_INT, $num_options); with specified min and max values. From here I'm exiting with an error message if $num == false
Is it necessary to also use $mysqli->real_escape_string($num);
Currently I am not bothering because I think it's quite hard to do SQL injection using an integer...
Thanks,
Kevin
UPDATE: To clarify the query I'm doing looks like this
$sql = "SELECT employeeID, concat(FirstName, ' ', LastName) as Name FROM employee WHERE employeeID='$num'";
I see your using mysqli, your best option for security is to look into Prepared Statements.
PHP mysqli Prepared Statements
It's a bit involved for an example, but the above link has indepth examples.
Once you get the hang of it though, and build your class. It's really only a normal sql query but instead of including your values you use ?
"SELECT * FROM account WHERE username = ? AND password = ?"
and you bind your values to the statement:
array("bradley", "Passw0rd");
The security comes from, as a short answer, is the fact you don't concat the values into the query string yourself. Making it less prone to sql injection.
Like many other PHP users you are taking escaping wrong. You taking it as a some sort of magic wand which makes some "evil characters" "safe".
This is wrong idea.
though prepared statements can be taken as a sort of such a magic wand, escaping is not a synonym for "SQL injection protection". It is a merely string syntax rule - no more, no less.
Is it necessary to also use $mysqli->real_escape_string($num);
It is irrelevant question.
To escape or not to escape decision have to be bound to SQL, not to the data source or any validations:
real_escape_string() have to be used for the sql strings, i.e. parts of the query enclosed in quotes. Have to be used unconditionally, despite of whatever previous manipulations.
For the any other part of the query real_escape_string() being completely useless.
An explanation:
Data validation rules can be changed.
While SQL building rules have to be explicit and unconditional. To make a developer never ask himself a question like this.
In fact, it's completely different matters: data validation and query building. Why keep in mind such details and build the query accordingly? Why not to build the query based on some set of general purpose rules, irrelevant of the data nature at all?
So, to your question again:
if you are adding your data to the query as is, without quotes, real_escape_string() going to be completely useless in this case, but casting/validation become essential.
if you are adding your data to the query using prepared statement, real_escape_string() going to be completely useless and even harmful.
if you are adding your data to the query in quotes - you ought to do real_escape_string() in this case.
it is also worth to mention that if you are adding your data to the query as a part of SQL language - as an identifier or an SQL keyword - real_escape_string() is completely useless too, as well as prepared statement. Whitelisting is your only friend here

Why mysql_real_escape_string() shouldn't avoid any SQL Injection?

I've heard people say (in relation to C#/SQL Server, but also in relation to PHP/MySql): Don't escape strings manually - use stored procedures instead.
Ok, I can accept this suggestion, but why? Many say (including on SO) mysql_real_escape_string() is quite enough, mysql_real_escape_string() is good, mysql_real_escape_string() is the first way of protection.
Why? Is there a case where mysql_real_escape_string() can fail? At least one... I don't need many :)
When mysql_real_escape_string FAIL:
$sql = "SELECT * FROM users WHERE id=" + mysql_real_escape_string($_GET['id']);
If $_GET['user_id'] is set to 1 OR 1=1, there are no special chars and it's not filtered.
The result: All rows are returned.
It gets worse. How about this... what if $_GET['user_id'] is set to 1 OR is_admin = 1?
The function is only designed to be used when inside single quotes.
There are two things that can go wrong with mysql_real_escape_string:
You can forget to use it
If you are using some specific multibyte connection encodings, and you have set these encodings with SET NAMES instead of mysql_set_charset as is proper, it can still leave you vulnerable
Update:
You are safe with UTF-8 (although this does not mean that you should continue using SET NAMES!)
For an explanation, see here
Just for info:
mysql_real_escape_string() does not escape % and _. These are wildcards in MySQL if combined with LIKE, GRANT, or REVOKE.
mysql_real_escape_string() can fail to clean the input.
Since mysql_real_esacpe_string() takes character set into account while cleaning strings.
There's the problem. You can change character via mysql_query function sending the query to change connection's character set. However, mysql_real_escape_string() is oblivious to the set you're using and it will escape some characters improperly.
The other thing is constantly invoking it manually. Even wrapping it in a function is a P.I.T.A. because it implies you have to create some sort of database wrapper / database abstraction layer of your own in order to be able to automate calls to mysql_real_escape_string().
That's why we have PDO in PHP which helps us alleviate all of the above and you get to use parametrized prepared statements which are preferable way of dealing with repeating queries that alter the data.
Prepare the statement, bind input variables and it will clean the input according to the database driver being used and connection's character set. It's less code and completely safe.
There is only one thing about mysql_real_escape_string() and SQL Injection -
The former does not have a slightest relation to the latter.
Although it sounds paradoxical, nothing can be more true.
Here are 2 statements proving it
You have to escape quoted strings only, as this function helps nothing else.
In fact, you have to escape every string you are adding to the query, even safiest one. just because it may contain some special character and thus break the query (just as an accident, not malicious plot).
Thus, when applicable, this function have to be used anyway, despite of whatever dangers or fears. And in any other case it will help nothing.
The only thing I have to add is that prepared statements do not provide full protection either. Here is the explanation and recommendations: https://stackoverflow.com/a/8255054/285587

Escaping user input from database necessary?

So I know about MySQL injection and always escape all my user input before putting it in my database. However I was wondering, imagine a user tries to submit a query to inject, and I escape it. What if I then at a later moment take this value from the database, and use it in a query. Do I have to escape it again?
So: (sql::escape() contains my escape function)
$userinput = "'); DROP `table` --";
mysql_query("INSERT INTO `table`
(`foo`,`bar`)
VALUES
('foobar','".sql::escape($userinput)."')");
// insert php/mysql to fetch `table`.`bar` into $output here
mysql_query("INSERT INTO `table2`
(`foo`,`bar`)
VALUES
('foobar','".$output."')");
Does MySQL automatically escape their output or something like that, or should I escape in the second query as well?
This is a testcase but this occurs in some other ways within my program and I'm wondering how tight the security has to be for cases like this.
EDIT
My escape function
static function escape($string){
if(get_magic_quotes_gpc())
$string = stripslashes($string);
return mysql_real_escape_string($string);
}
Does MySQL automatically escape their output or something like that, or should I escape in the second query as well?
You need to escape in the second query as well. MySQL does not do any escaping on its output.
Long answer: MySQL string escaping does not modify the string that is being inserted, it just makes sure it doesn't do any harm in the current query. Any SQL injection attempt still remains in the data.
Yes, you have to escape the string in the second query too.
Escaping the string sounds magical to many people, something like shield against some mysterious danger, but in fact it is nothing magical. It is just the way to enable special characters being processed by the query.
The best would be just to have a look what escaping really does. Say the input string is:
'); DROP `table` --
after escaping:
\'); DROP `table` --
in fact it escaped only the single slash. That's the only thing you need to assure - that when you insert the string in the query, the syntax will be OK!
insert into table set column = '\'); DROP `table` --'
It's nothing magical like danger shield or something, it is just to ensure that the resultant query has the right syntax! (of course if it doesn't, it can be exploited)
The query parser then looks at the \' sequence and knows that it is still the variable, not ending of its value. It will remove the backslash and the following will be stored in the database:
'); DROP `table` --
which is exactly the same value as user entered. And which is exactly what you wanted to have in the database!!
So this means that the if you fetch that string from the database and want to use it in the query again, you need to escape it again to be sure that the resultant query has the right syntax.
But, in your example, very important thing to mention is the magic_quotes_gpc directive!
This feature escapes all the user input automatically (gpc - _GET, _POST and _COOKIE). This is an evil feature made for people not aware of sql injection. It is evil for two reasons. First reason is that then you have to distinguish the case of your first and second query - in the first you don't escape and in the second you do. What most people do is to either switch the "feature" off (I prefer this solution) or unescape the user input at first and then escape it again when needed. The unescape code could look like:
function stripslashes_deep($value)
{
return is_array($value) ?
array_map('stripslashes_deep', $value) :
stripslashes($value);
}
if (get_magic_quotes_gpc()) {
$_POST = stripslashes_deep($_POST);
$_GET = stripslashes_deep($_GET);
$_COOKIE = stripslashes_deep($_COOKIE);
}
The second reason why this is evil is because there is nothing like "universal quoting".
When quoting, you always quote text for some particular output, like:
string value for mysql query
like expression for mysql query
html code
json
mysql regular expression
php regular expression
For each case, you need different quoting, because each usage is present within different syntax context. This also implies that the quoting shouldn't be made at the input into PHP, but at the particular output! Which is the reason why features like magic_quotes_gpc are broken (never forget to handle it, or better, assure it is switched off!!!).
So, what methods would one use for quoting in these particular cases? (Feel free to correct me, there might be more modern methods, but these are working for me)
mysql_real_escape_string($str)
mysql_real_escape_string(addcslashes($str, "%_"))
htmlspecialchars($str)
json_encode() - only for utf8! I use my function for iso-8859-2
mysql_real_escape_string(addcslashes($str, '^.[]$()|*+?{}')) - you cannot use preg_quote in this case because backslash would be escaped two times!
preg_quote()
I'd say that whole idea of this question is wrong.
You're taking this problem absolutely wrong way.
One doesn't have to count his queries, if it's first or second or 100th.
Same goes for the the user input: it doesn't matter, where the data come from!
Data destination, not source should be your concern. Is this string going to database? Escape it! With no questions. This rule is plain and simple and require no query counting or anything.
But that's not only fault in your question.
One:
Does MySQL automatically escape their output or something like that?
That's a very bad idea. Funny part, you're fighting with a consequence of the same idea in your code, by applying get_magic_quotes_gpc(). What are these magic quotes if not such automatic escaping?
Two:
moreover, using get_magic_quotes_gpc() in your escaping function is a very bad idea again :)
imagine you have magic quotes on and using your function to protect your "second query". And there is some blob that contain \' sequence in the data. Your function will strip the slash and spoil the data. In fact, stripslashes has absolutely nothing to do with any escaping function. do it separately, on the data where it belongs - on the user input.
Three:
mysql_real_escape_string() is not some magic function that "makes everything safe". In fact, to create dynamic mysql query, one have to escape four kinds of data:
strings
numbers
identifiers
operators
while mysql_real_escape_string() escaping only one of them. And your query stand absolutely naked in all three other cases. Funny, eh?
Most disappointing part:
I know that all this ultimate knowledge is in vain and would be read scarcely by few noobs and never change either overall knowledge level of PHP community in general, nor answers quality on SO in particular. :(
Try to use PHP's PDO for database access if you can. There are two important reasons for this:
You can use PDO's prepare function to compile your query. This is efficient if you need to issue the same query with different input (as is often the case). So, compile once and execute multiple times.
Compiling the query with prepare has other nice effects. Once the query is compiled, the database engine knows the exact syntactic structure of the query, and does not allow any input that changes this syntactic structure. This is good because in SQL injection, the injected input changes the syntax of the query.
Warning: This doesn't prevent all kinds of SQL injection, but it prevents the most common kind.
References:
Are PDO prepared statements sufficient to prevent SQL injection?
http://php.net/manual/en/pdo.prepare.php

Categories