How to hide only sensitive arguments in PHP's debug_backtrace?

Viewed 319

Consider the following code. In the event that an exception occurs, the trace (which will be logged and stored in a database) will include the sensitive password data. How can sensitive data in cases like this, while allowing other non-sensitive arguments, be hidden?

<?php
$user = 'john';
$pass = 'secret';

function auth($user, $pass) {
    // authentication logic
}

function login($user, $pass) {
    throw new Exception('Unexpected error');

    // various logic
    auth($user, $pass);
    // various logic
}

try {
    login($user, $pass);
} catch (Throwable $e) {
    send_to_log($e->getTrace()); // This reveals the password "secret"
}
3 Answers

Disclaimer: I (kind of) assume that you never really pipe the result of var_dump back to your user. It's (once again, kind of) obvious that end users rarely care about innards of your engine, so showing them traceroutes is almost never a good way to handle server errors. But you're right; even logging this info might actually not be a really good idea - for various reasons.

So, answering the original question: well, you can force your exception logging to either discard the params completely - or limit their length:

Note that PHP 7.4 introduced the setting zend.exception_ignore_args, which allowed removing argument information from exceptions completely (in getTrace(), getTraceAsString(), etc.).

Setting zend.exception_string_param_max_len=0 still provides more information than completely disabling tracking args (you still know the argument is a string, and types of non-strings).

Still that might complicate the debugging for other cases. This was somewhat mitigated in PHP 8.0 by introducing zend.exception_string_param_max_len config param:

zend.exception_string_param_max_len is a new INI directive to set the maximum string length in an argument of a stringified stack strace.

The idea behind this (quoted above) is, among other things, to limit the number of potentially exposed sensitive data when logging exceptions without actually compromising the data needed to debug the issue.

Note though that this setting only affects getTraceAsString() results (which you should consider using instead of var_dumping result of getTrace anyway).

With the upcoming PHP version 8.2 there is a new feature named "Redacting parameters in back traces". This will hide the parameter from any stack trace in your PHP application.

Here is an example from the RFC:

<?php
 
function test(
    $foo,
    #[\SensitiveParameter] $bar,
    $baz
) {
    throw new \Exception('Error');
}
 
test('foo', 'bar', 'baz');
 
/*
Fatal error: Uncaught Exception: Error in test.php:8
Stack trace:
#0 test.php(11): test('foo', Object(SensitiveParameterValue), 'baz')
#1 {main}
  thrown in test.php on line 8
*/

I ended up adding logic in the code that handles logging to file/database to clear the arguments of specific functions showing up in the trace:

<?php
function send_to_log(Throwable $e) {
    $noArgs = [
        'login' => true,
        'auth' => true,
        // ...
    ];

    $trace = $e->getTrace();
    foreach ($trace as &$err) {
        if (isset($noArgs[$err['function'] ?? ''])) {
            $cnt = count($err['args'] ?? []);
            if ($cnt > 0) {
                $err['args'] = array_fill(0, $cnt, 'REDACTED');
            }
        }
    }
    unset($err);

    var_dump($trace); /* This now shows "REDACTED" for all arguments
    to functions specified in the $noArgs array */

    // logging logic
}
Related