如果我使用下拉列表,是否需要防止 SQL 注入?

我明白你永远不应该信任来自表单的用户输入,主要是因为 SQL 注入的机会。

但是,这是否也适用于只有下拉列表输入的表单(见下文) ?

我将 $_POST['size']保存为一个 Session,然后在整个站点中使用它来查询各种数据库(使用 mysqli Select 查询) ,任何 SQL 注入都肯定会损害(可能会丢弃)它们。

没有用于类型化用户输入查询数据库的区域,只有下拉列表。

<form action="welcome.php" method="post">
<select name="size">
<option value="All">Select Size</option>
<option value="Large">Large</option>
<option value="Medium">Medium</option>
<option value="Small">Small</option>
</select>
<input type="submit">
</form>
11777 次浏览

Yes.

Anyone can spoof anything for the values that actually get sent --

SO, for validating dropdown menus, you can just check to make sure that the value that you're working with was in the dropdown - something like this would be the best(most sanely paranoid) way:

if(in_array($_POST['ddMenu'], $dropDownValues){


$valueYouUseLaterInPDO = $dropDownValues[array_search("two", $arr)];


} else {


die("effin h4x0rs! Keep off my LAMP!!");


}

You could do something as simple as the following example to make sure the posted size is what you expect.

$possibleOptions = array('All', 'Large', 'Medium', 'Small');


if(in_array($_POST['size'], $possibleOptions)) {
// Expected
} else {
// Not Expected
}

Then use mysqli_* if you are using a version of php >= 5.3.0 which you should be, to save your result. If used correctly this will help with sql injection.

One way of protecting against users changing your drop downs using the console is to only use integer values in them. Then you can validate that the POST value contains an integer, and use an array to convert that to text when needed. E.g:

<?php
// No, you don't need to specify the numbers in the array but as we're using them I always find having them visually there helpful.
$sizes = array(0 => 'All', 1 => 'Large', 2 => 'Medium', 3 => 'Small');
$size = filter_input(INPUT_POST, "size", FILTER_VALIDATE_INT);


echo '<select name="size">';
foreach($sizes as $i => $s) {
echo '<option value="' . $i . '"' . ($i == $size ? ' selected' : '') . '>' . $s . '</option>';
}
echo '</select>';

Then you can use $size in your query with knowledge that it will only ever contain FALSE or an integer.

As this question was tagged with , here is an answer regarding this particular kind of attack:

As you've been told in the comments, you have to use prepared statements for the every single query involving any variable data, with no exceptions.

Regardless of any HTML stuff!
It is essential to understand that SQL queries have to be formatted properly regardless of any external factors, be it HTML input or anything else.

Although you can use white-listing suggested in other answers for the input validation purpose, it shouldn't affect any SQL-related actions - they have to remain the same, no matter if you validated HTML input or not. It means you still have to use prepared statements when adding any variables into the query.

Here you may find a thorough explanation, why prepared statements is a must and how to properly use them and where they aren't applicable and what to do in such case: The Hitchhiker's Guide to SQL Injection protection

Also, this question was tagged with . Mostly by accident, I presume, but anyway, I have to warn you that raw mysqli is not an adequate substitution for the old mysq_* functions. Simply because if used in the old style, it will add no security at all. While it's support for the prepared statements is painful and troublesome, to the point that average PHP user is just unable to endeavor them at all. Thus, if no ORM or some sort of abstraction library is option, then PDO is your only choice.

Your web browser does not "know" that it is receiving a page from php, all it sees is html. And the http layer knows even less than that. You need to be able to handle nearly any kind of input that can cross the http layer (luckily for most input php will already give an error). If you are trying to prevent malicious requests from messing up your db, then you need to assume that the guy on the other end knows what he is doing, and that he is not limited to what you can see in your browser under normal circumstances (not to mention what you can fiddle with a browser's developer tools). So yes, you need to cater for any input from your dropdown, but for most input you can give an error.

The fact that you have restricted the user to only using values from a certain drop-down list is irrelevant. A technical user can capture the http request sent to your server before it leaves their network, alter it using a tool such as a local proxy server, and then continue it on it's way. Using the altered request, they can send parameter values that are not ones that you have specified in the drop down list. Developers have to have the mindset that client restrictions are often meaningless, as anything on a client can be altered. Server validation is required at every single point that client data enters. Attackers rely on the naivety of developers in this sole aspect.

It's best to use a parameterized query in order to ensure against SQL injection. In that case the look of the query would be this:

SELECT * FROM table WHERE size = ?

When you supply a query like the above with text that is unverified for integrity (the input isn't validated on the server) and it contains SQL injection code it will be handled correctly. In other words, the request will result in something like this happening in the database layer:

SELECT * FROM table WHERE size = 'DROP table;'

This will simply select 0 results as it returns which will make the query ineffective in actually causing harm to the database without the need for a whitelist, a verification check or other techniques. Please note that a responsible programmer will do security in layers, and will often validate in addition to parameterizing queries. However, there is very little cause to not parameterize your queries from a performance perspective and the security added by this practice is a good reason to familiarize yourself with parameterized queries.

Yes you need to protect against this.

Let me show you why, using Firefox's developer console:

i've edited one of the values in the dropdown to be a drop table statement

If you don't cleanse this data, your database will be destroyed. (This might not be a totally valid SQL statement, but I hope I've gotten my point across.)

Just because you've limited what options are available in your dropdown does not mean you've limited the data I can send your server.

If you tried to restrict this further using behaviour on your page, my options include disabling that behaviour, or just writing a custom HTTP request to your server which imitates this form submission anyway. There's a tool called curl used for exactly that, and I think the command to submit this SQL injection anyway would look something like this:

curl --data "size=%27%29%3B%20DROP%20TABLE%20*%3B%20--"  http://www.example.com/profile/save

(This might not be a totally valid curl command, but again, I hope I've gotten my point across.)

So, I'll reiterate:

NEVER trust user input. ALWAYS protect yourself.

Don't assume any user input is ever safe. It's potentially unsafe even if it arrives through some means other than a form. None of it is ever trustworthy enough to forgo protecting yourself from SQL injection.

The other answers already cover what you need to know. But maybe it helps to clarify some more:

There are TWO THINGS you need to do:

1. Validate form data.

As Jonathan Hobbs' answer shows very clearly, the choice of html element for the form input does not do any reliable filtering for you.

Validation is usually done in a way that does not alter the data, but that shows the form again, with the fields marked as "Please correct this".

Most frameworks and CMSes have form builders that help you with this task. And not just that, they also help against CSRF (or "XSRF"), which is another form of attack.

2. Sanitize/Escape variables in SQL statements..

.. or let prepared statements do the job for you.

If you build a (My)SQL statement with any variables, user-provided or not, you need to escape and quote these variables.

Generally, any such variable you insert into a MySQL statement should be either a string, or something that PHP can be reliably turn into a string that MySQL can digest. Such as, numbers.

For strings, you then need to choose one of several methods to escape the string, that means, replace any characters that would have side effects in MySQL.

  • In old-school MySQL + PHP, mysql_real_escape_string() does the job. The problem is that it is far too easy to forget, so you should absolutely use prepared statements or query builders.
  • In MySQLi, you can use prepared statements.
  • Most frameworks and CMSes provide query builders that help you with this task.

If you are dealing with a number, you could omit the escaping and the quotes (this is why the prepared statements allow to specify a type).

It is important to point out that you escape the variables for the SQL statement, and NOT for the database itself. The database will store the original string, but the statement needs an escaped version.

What happens if you omit one of these?

If you don't use form validation, but you do sanitize your SQL input, you might see all kinds of bad stuff happening, but you won't see SQL injection! (*)

First, it can take your application into a state you did not plan for. E.g. if you want to calculate the average age of all users, but one user gave "aljkdfaqer" for the age, your calculation will fail.

Secondly, there can be all kinds of other injection attacks you need to consider: E.g. the user input could contain javascript or other stuff.

There can still be problems with the database: E.g. if a field (database table column) is limited to 255 characters, and the string is longer than that. Or if the field only accepts numbers, and you attempt to save a non-numeric string instead. But this is not "injection", it is just "crashing the application".

But, even if you have a free text field where you allow any input with no validation at all, you could still save this to the database just like that, if you properly escape it when it goes to a database statement. The problem comes when you want to use this string somewhere.

(*) or this would be something really exotic.

If you don't escape variables for SQL statements, but you did validate form input, then you can still see bad stuff happening.

First, you risk that when you save data to the database and load it again, it won't be the same data anymore, "lost in translation".

Secondly, it can result in invalid SQL statements, and thus crash your application. E.g. if any variable contains a quote or double quote character, depending which type of quote you use, you will get invalid MySQL statement.

Thirdly, it can still cause SQL injection.

If your user input from forms is already filtered / validated, intentional SQl injection may become less likely, IF your input is reduced to a hardcoded list of options, or if it is restricted to numbers. But any free text input can be used for SQL injection, if you don't properly escape the variables in SQL statements.

And even if you have no form input at all, you could still have strings from all kinds of sources: Read from the filesystem, scraped from the internet, etc. Noone can guarantee that these strings are safe.

Whatever is submitted from your form comes to your server as text across the wires. There is nothing stopping anyone from creating a bot to mimic the client or type it in from a terminal if they wanted to. Never assume that because you programmed the client it will act like you think it will. This is really easy to spoof.

Example of what can and will happen when you trust the client.

A hacker can bypass the browser completely, including Javascript form checking, by sending a request using Telnet. Of course, he will look at the code of your html page to get the field names he has to use, but from then on it's 'everything goes' for him. So, you must check all values submitted on the server as if they did not originate from your html page.