Yii logger and passwords

Hi everybody.

I’m using Yii2 for a project and i set the log configuration so it sends me an email for any error (except errors like 404, 403 etc).

That’s a fantastic feature, if an user gets an unexpected error i know it even before the user can tell me about it.

Today the logger logged a BadRequestHttpException error, which i think is dued to a wrong csrf token.
The request was a login attempt, and as always all the get and post data get logged.

The post data logged looked like this:

$_POST = [
'_csrf' =>
'3wgzhKjeBkNpurJQB69fs4NSLxTnQv3zgMXoH1Pv9OWyRlCw8bhrGSiL4gZY1yuFyQF6ctd0uqvZobEoPt-RtQ=='
'LoginForm' => [
'username' => '(the username)'
'password' => '(the password)'
'rememberMe' => '0'
]
'login-button' => ''
]

I was surprised to see even the password the user wrote was logged!
That could be a problem! How can i prevent that?
Is there some setting i missed?

That’s error handler. https://www.yiiframework.com/doc/api/2.0/yii-web-errorhandler#$displayVars-detail

1 Like

Thanks! And is there a standard way to hide the post content in certain urls/actions only?
Or even better, to filter some variable names inside post?

No. But you can configure log component anywhere in the code.

I don’t think so.

I’m using this component to hide passwords in logs.

First slightly modified VarDumper:

<?php

namespace common\components\log;

use yii\base\InvalidValueException;

/**
 * Class LogVarDumper
 * Extended to handle logs password fields darkening.
 * @package common\components\log
 */
class LogVarDumper extends \yii\helpers\VarDumper
{
    private static $_objects;
    private static $_output;
    private static $_depth;

    /**
     * Dumps a variable in terms of a string.
     * This method achieves the similar functionality as var_dump and print_r
     * but is more robust when handling complex objects such as Yii controllers.
     * @param mixed $var variable to be dumped
     * @param int $depth maximum depth that the dumper should go into the variable. Defaults to 10.
     * @param bool $highlight whether the result should be syntax-highlighted
     * @return string the string representation of the variable
     */
    public static function dumpAsString($var, $depth = 10, $highlight = false)
    {
        self::$_output = '';
        self::$_objects = [];
        self::$_depth = $depth;
        self::dumpInternal($var, 0);
        if ($highlight) {
            $result = highlight_string("<?php\n" . self::$_output, true);
            self::$_output = preg_replace('/&lt;\\?php<br \\/>/', '', $result, 1);
        }

        return self::$_output;
    }
    /**
     * @param mixed $var variable to be dumped
     * @param int $level depth level
     * @param bool $passwordKey whether password related key was present in previous iteration
     */
    private static function dumpInternal($var, $level, $passwordKey = false)
    {
        switch (\gettype($var)) {
            case 'boolean':
                self::$_output .= $var ? 'true' : 'false';
                break;
            case 'integer':
                self::$_output .= (string) $var;
                break;
            case 'double':
                self::$_output .= (string) $var;
                break;
            case 'string':
                if ($passwordKey) {
                    self::$_output .= "'** PASSWORD HIDDEN **'";
                } else {
                    self::$_output .= "'" . addslashes($var) . "'";
                }
                break;
            case 'resource':
                self::$_output .= '{resource}';
                break;
            case 'NULL':
                self::$_output .= 'null';
                break;
            case 'unknown type':
                self::$_output .= '{unknown}';
                break;
            case 'array':
                if (self::$_depth <= $level) {
                    self::$_output .= '[...]';
                } elseif (empty($var)) {
                    self::$_output .= '[]';
                } else {
                    $keys = array_keys($var);
                    $spaces = str_repeat(' ', $level * 4);
                    self::$_output .= '[';
                    foreach ($keys as $key) {
                        self::$_output .= "\n" . $spaces . '    ';
                        self::dumpInternal($key, 0);
                        self::$_output .= ' => ';
                        self::dumpInternal($var[$key], $level + 1, stripos($key, 'password') !== false);
                    }
                    self::$_output .= "\n" . $spaces . ']';
                }
                break;
            case 'object':
                if (($id = array_search($var, self::$_objects, true)) !== false) {
                    self::$_output .= \get_class($var) . '#' . ($id + 1) . '(...)';
                } elseif (self::$_depth <= $level) {
                    self::$_output .= \get_class($var) . '(...)';
                } else {
                    $id = array_push(self::$_objects, $var);
                    $className = \get_class($var);
                    $spaces = str_repeat(' ', $level * 4);
                    self::$_output .= "$className#$id\n" . $spaces . '(';
                    if ('__PHP_Incomplete_Class' !== \get_class($var) && method_exists($var, '__debugInfo')) {
                        $dumpValues = $var->__debugInfo();
                        if (!\is_array($dumpValues)) {
                            throw new InvalidValueException('__debuginfo() must return an array');
                        }
                    } else {
                        $dumpValues = (array) $var;
                    }
                    foreach ($dumpValues as $key => $value) {
                        $keyDisplay = strtr(trim($key), "\0", ':');
                        self::$_output .= "\n" . $spaces . "    [$keyDisplay] => ";
                        self::dumpInternal($value, $level + 1);
                    }
                    self::$_output .= "\n" . $spaces . ')';
                }
                break;
        }
    }
}

Then to make log target using it:

<?php

namespace common\components\log;

use yii\helpers\ArrayHelper;

/**
 * Class FileTarget
 * @package common\components\log
 */
class FileTarget extends \yii\log\FileTarget
{
    /**
     * {@inheritdoc}
     */
    protected function getContextMessage()
    {
        $context = ArrayHelper::filter($GLOBALS, $this->logVars);
        $result = [];
        foreach ($context as $key => $value) {
            if (\is_string($value) && stripos($key, 'password') !== false) {
                $result[] = "\${$key} = '** PASSWORD HIDDEN **'";
            } else {
                $result[] = "\${$key} = " . LogVarDumper::dumpAsString($value);
            }
        }
        return implode("\n\n", $result);
    }
}

And finally log target config:

'components' => [
    'log' => [
        'targets' => [
            [
                'class'   => \common\components\log\FileTarget::class,
                // ...
            ],
        ],
    ],
],
1 Like

I just use this: https://www.yiiframework.com/doc/api/2.0/yii-log-target#$maskVars-detail
to mask all sensitive post data.

3 Likes

I totally forgot it exists :slight_smile:

‘log’ => [
‘traceLevel’ => YII_DEBUG ? 3 : 0,
‘targets’ => [
[
‘class’ => ‘yii\log\FileTarget’,
‘levels’ => [‘error’, ‘warning’],
‘maskVars’ => [
‘_SERVER.HTTP_AUTHORIZATION’,
‘_SERVER.PHP_AUTH_USER’,
‘_SERVER.PHP_AUTH_PW’,
‘_POST.LoginForm.password’
]
],
],
],

1 Like