<a href="http://www.yiiframework.com/wiki/275/how-to-write-secure-yii-applications">http://www.yiiframework.com/wiki/275/how-to-write-secure-yii-applications</a>
<a href="http://www.yiiframework.com/doc/guide/1.1/zh_cn/topics.security">http://www.yiiframework.com/doc/guide/1.1/zh_cn/topics.security</a>
<a href="http://yjlblog.com/yii%E6%A1%86%E6%9E%B6%E5%BC%80%E5%8F%91%E5%AE%89%E5%85%A8%E8%80%83%E8%99%91/">http://yjlblog.com/yii%e6%a1%86%e6%9e%b6%e5%bc%80%e5%8f%91%e5%ae%89%e5%85%a8%e8%80%83%e8%99%91/</a>
warning: While this security guide tries to be quite complete, is not exhaustive. If security matters for you, you ought to check several other references.
Validate the user input (see below for details).
Protect (escape) your application output according to context (see below for a few output types, mostly HTML and SQL).
Test your application in debug mode.
Set the constant <code>YII_DEBUG</code> to true (by default, it is defined in <code>index.php</code>) and put alongside <code>error_reporting(E_ALL);</code>. Then errors and warnings will stop the execution and Yii will display the message, the source code and the call stack. Even an undefined key in an array (which is just a "E_NOTICE" level)
can cause security problems.
Disable the debug mode in production.
Make sure your error messages don't contain sensitive information.
Whenever possible, filter by white-list instead of by black-list, i.e. allow only data that is in an authorized list.
In production, keep logs. Parse them regularly for warnings and errors.
logs are usually on by default. Please check your server configuration and your rights on the file system for accessing these log files.
If a user can add its birth date to its profile, you have to make sure he gives a valid date. It's not only helpful to prevent mistypes, it also provides better security. Verifying the input is in the form "1951-01-25" will forbid dangerous texts that try
to attack your database's SQL or your website's HTML. Validation is not a perfect protection, but it's an excellent first step.
Validating a form with JavaScript has absolutely no impact on the security! It should only be meant as a way to enhance the interface and its comfort of use.
The HTML restriction are the same. For instance, if a page has a form containing:
The data received in the PHP application can contain anything. The "id", "date" and "list" fields can be big strings or arrays. For example, a user can modify the HTML source of the page to replace both fields by text areas.
Most of the times, the user input will be sent to a model. Models will generally extend<code>CFormModel</code> or
behaviors or the<code>beforeValidate()</code> method.
The controller:
The model:
You should pay extra care to your validation. It keeps your data clean, and that not only useful for security. Many kind of rules are already declared, and you can add your own. You can also apply some rules only in a given context, e.g. validate a field
only when the record is modified ("update" scenario"), not when the record is created ("insert" scenario).
<a href="http://yii.googlecode.com/files/yii-1.1.0-validator-cheatsheet.pdf">Yii 1.1 validator cheat-sheet</a>
Some light user input will be handled directly by the controller. In this case,you should use a PHP type cast. This happens frequently for numeric IDs where you should use<code>(int)</code>.
If the input is not expected to be an integer, then consider making go through a model validation.
where this automatic protection might not be enough. What if a malicious user enters the query<code>comment/delete?id[]=2\&id[]=1</code>? Then
<code>$_GET['id']</code> would become an array, and if this id is not validated, it can induce strange effects, and possible security breaches (but not with<code>findByPk</code>).
If the application prints unfiltered user input inside a HTML page, then it allows a malicious user to change the display of this page, and to inject client code (usually JavaScript) that can be run by other users. One typical use of these XSS attacks is
to steal user sessions.
One note on vocabulary: filtering data for security concerns is often called
escaping.
Here is a extract of a view. The page just shows a user profile.
Now suppose the user's name is:
Then everyone that consults this profile will send an HTTP request for an external image, and this request will contain data describing the visitor's cookies.
PHP provides several functions that protect the output. The most useful one is<code>htmlspecialchars()</code> but just in the example above,
<code>rawurlencode()</code> and<code>htmlspecialchars(, ENT_QUOTES)</code> would also be necessary.
If you want to print plain text in a HTML page, use <code>CHtml::encode()</code>. Here is an example:
This function is in fact a wrapper on <code>htmlspecialchars()</code> with your application's characters set (to be exact, it's not a charset but a character encoding). So if your texts are not (yet) in UTF-8, you should declare a charset in the global config
(e.g. <code>'charset' => 'ISO-8859-1'</code> in the first level of "protected/config/main.php").
You may want to apply <code>strip_tags()</code>, to remove HTML/XML tags before escaping. Beware, this function is not secure, so do not use it without<code>CHtml::encode()</code>.
If you want to allow HTML in the user input, then you have to display it raw. Soyou should filter the HTML data (before saving it or after reading it, at your choice, though the former is recommended for performance reasons). Do not try
Allowing the user to enter HTML text can be useful, especially with Rich Text Editors like TinyMCE or FckEditor, but you may alsoconsider using templating languages, like Markdown or wiki syntax. Regarding security, the benefit is that the
application converts to HTML, so the risk of XSS is low.
<a href="http://www.yiiframework.com/doc/api/1.1/CMarkdown/">CMarkdown</a>
To escape a string in an URL:
use <code>rawUrlEncode()</code> for url parts,
use <code>urlEncode()</code> for url parameters.
Here is an example of several cases in JavaScript and HTML:
<code>CHtml::encode()</code> cannot be used alone here because it could produce an invalid URL, for example with <code>$query = '?x="N & B"'</code>. But it cannot be removed since ampersands "&" have to be replaced by "&amp;".
There is a special case where you do not want Yii to quote a string that is already valid in JS. In this case, you have to prefix your string with "js:", the prefix will be removed and the rest will be unchanged.
When some user data is put unfiltered in a SQL query, it allows a malicious user to send its own SQL in the query.
If the GET parameter id is "4 OR 1=1" then someone who has the right to delete comment #4 will probably be able to delete all the other comments (it depends on how the authorization is granted, but that's another story). In the second request, it would be
possible to read the content of the whole DB with input like "2 UNION SELECT ...".
Instead of the code of the example above, what follows is far more secure:
This is a general principle: if you build your SQL condition in pure text, you take more risks than a more PHP approach. For most DB functions,prefer array parameters to string parameters. Here is another example using PHP arrays:
The are still cases where writing raw SQL is needed. Consider a simple query that has 2 parameters:
There are 2 ways to secure this:
Escape each parameter (not recommended).
Use a prepared statement (recommended).
Prepared statements is a way to declare parameters in your SQL. Depending on your configuration, the incomplete query will be compiled by the SQL server, then the values will be inserted at the right places (the actual behavior may vary because this process
could be emulated from PHP, especially if the SQL engine doesn't allow it).
Prepared statements removes any risk of SQL injection in the parameters. (Yet, beware, not everything is a parameter).
There are two ways to write this in Yii:
The first syntax with explicit bindings is a bit heavier, but it has the advantage of defining the parameter's type.
When retrieving models from the DB, the syntax is simple:
Even if no SQL injection in possible in the previous queries, there is still room for improvement. The SQL function<code>LIKE</code> has a special treatment for the characters "_" and "%". In many cases, this is not a problem, with mostly unexpected results.
But if the data queried is huge, then transforming a<code>"begin%"</code> condition into
<code>"%s%a%"</code> condition can make the query so heavy that it slows the SQL server, because no index can be used for the later. Soyou should protect the characters "%" and "_" when the user input is going into a LIKE condition, for example
As of now (Yii 1.1.8), the framework does not recognized positional parameters marked with "?". You have to usednamed parameters whose names begin with ":".
A lone prepared statement is a bit slower than a non prepared one. This is probablynot a performance bottleneck for your application. But if you want to run several times the same query with variable bound parameters, then the prepared statements
will be faster. Of course, none of this does apply when PHP emulates preparation.
As written above, prepared statements remove any risk of SQL injection in the parameters. Alas, there will be times when you need to use variables for parts of the SQL query that cannot use prepared statements.
The traditional way to solve this problem in pure PHP is to have white-lists of accepted values for each part. But Yii provide several ways to help. The first one is that Yii knows your DB schema, so you can write:
Most of the time, your expected result is to be parsed as models, so you can use<code>find*()</code> methods with
To be complete, here is another syntax for the previous example:
In the following lists, the firsts choices are the easiest to secure, but it doesn't mean the last items are not secure.
When results are models, chose the first element of the list that matches your needs:
<code>X::model()->find($criteria, array(':param1' => $value1))</code> or <code>->findAll(...)</code>
<code>X::model()->find($sql, array(':param1' => $value1))</code> or <code>->findAll(...)</code>
<code>X::model()->findBySql($sql, array(':param1' => $value1))</code> or <code>->findAll(...)</code>
When results are not model, use prepared statements:
And don't forget to validate the input before this!
Please note that HTTP requests that modify the server state (create, update, delete) should be with the POST protocol. This is a good practice, as recommended by REST, and it helps web browser to prevent accidental re-send of these requests. But a POST request
in itself does not prevent CSRF, it provides almost no improvement on security. Fortunately, Yii has a mechanism (disabled by default) that can be used to protect them from forgery.
This section will only consider a UNIX (Linux, BSD, OSX) web server Apache with PHP as a module. Other configurations (Windows, nginx, PHP-fpm, etc) may require different settings, though the principles are the same.
When Yii runs with the constant <code>YII_DEBUG</code> set to true, it can show valuable information to an attacker (setting aside the performance penalty). For instance, suppose an attacker finds a validation miss in your application: when a form is spoofed
to send an array value in a field, a PHP function will receive incorrect parameters. In debug mode, Yii will then print the call stack, with the context of each call made in user code.
Unfortunately, by default the debug mode is set up in the <code>index.php</code> file of your application. So the code has to be changed when running a development, a testing or a production instance. One solution can be to use a DVCS to track local changes
and rebase them (fast-forward in git jargon). The drawback is that the various configurations are handled locally, in several branches.
The recommended solution is to rewrite the <code>index.php</code> file so that it reads the debug configuration:
from an external file,
or from the web server environment.
Apache can set environment variables with the syntax
This can be set in the global configuration files (in a VirtualHost or Directory), or in a<code>.htaccess</code> file. Then PHP can access this variable through
<code>$_SERVER["YII_ENV"]</code> and default to "production mode" if it isn't set.
The directory containing the framework should not be under the document root of your server (there is no reason a user could access to files like "yiilite.php" in its web browser).
Three directories must be writable by the web server: "assets", "protected/data" and "protected/runtime".The web server should only have read access to everything else. This way, an attacker could only create/modify a file
in these directories. The folder "assets" is especially dangerous since it is writable, and there is a direct HTTP access to it. Therefore, the PHP files it contains should not be interpreted but treated as plain text (see the example below).
Yii's default application have ".htaccess" files to forbid direct web access to "protected/" and "themes/classic/views/". It is a bit safer (and faster) to put this configuration in the global configuration of Apache. Here is an example that also disables
PHP files in "assets/".
Instead of the previous configuration, here is an example of putting a Yii application in a Virtual Host.
A few useful directives:
Directive
Comment
<code>allow_url_include</code>
Should be off (PHP 5.2).
<code>register_globals</code>
This is obsolete and dangerous. Should be off.
<code>magic_quotes_gpc</code>
Important for many PHP applications, but Yii negates its effect. Should be off.
<code>open_basedir</code>
Can restrict PHP to access only some directories. Use with caution.
<code>display_errors</code>
Should be off in production.
<code>error_reporting</code>
This directives can be set in the global "php.ini" file. If Apache has <code>AllowOverride Options</code>, then ".htaccess" can be used.
One can also use <code>php_admin_flag</code> and <code>php_admin_flag</code> to set config parameters that can't be changed dynamically with ".htaccess" or<code>ini_set()</code>. Here is an example in an Apache config file.
SSL is out of the scope of this wiki page.
Authorization is ensuring users only have access to the resources they have permissions on. This is a lengthy subject, and Yii provides many useful classes to handle permissions and roles. To learn about this, please readThe Definitive Guide to Yii
Providing a client-side validation in JavaScript can be useful, since the user will know immediately if its password is secure. But don't forget this must not replace the validation in PHP. In fact, it makes validation a bit harder because your PHP validation
This section considers only internal authentication, i.e. through passwords managed by the application. It does not consider LDAP, SSO, OpenID, or any other external service.
as the following "User" model:
What the library PHPass does is applying random salting, choosing the best encrypting algorithm available, iterating it a high number of times... Nothing really hard to code by oneself, but why reinvent the wheel. And the author is a security expert, he
wrote the famous "john the ripper" password-cracking tool (the successor of Jack the ripper ;). If you want to know more on passwords, the home page of the library contains links toward technical articles and a few advanced recommendations.
There are several tools that can detect potential security breaches in your application. First, some web security scanners:
Then a scanner of a different type:
There are many other security tools, but this should be more than enough for any beginner. And if you're experimented enough with web security, please contribute!