8

This question was asked in June 2021, long before the release of PHP 8.2 (Dec 2022). For PHP 8.2+ see Josh's answer. For PHP <8.2, no answer solves the question (including my own). The code I posted in my answer is the closest to a solution that fits my needs.

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"
}
hakre
  • 193,403
  • 52
  • 435
  • 836
reformed
  • 4,505
  • 11
  • 62
  • 88
  • 2
    Write data to log file. – u_mulder Jun 18 '21 at 22:26
  • WHY would you EVER show the results of a backtrace to your end users? Don't do it. – miken32 Jun 18 '21 at 22:33
  • 1
    @miken32 WHY do you assume that a code example reflects what is shown to end users? I'm talking about the data that gets inserted into the database or log files, which end users never see. – reformed Jun 18 '21 at 22:55

3 Answers3

15

Starting from the PHP version 8.2 (Dec 2022) there is a 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 that 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
*/
hakre
  • 193,403
  • 52
  • 435
  • 836
Josh
  • 765
  • 4
  • 10
6

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

raina77ow
  • 103,633
  • 15
  • 192
  • 229
  • I downvoted when your answer was only talking about string lengths. Turning off the storage of arguments altogether is a better solution, but OP's code suggests they're going to do something foolish with it. (And it's looking for an opinion.) – miken32 Jun 18 '21 at 22:48
  • I see, thank you for explanation. Yes, sometimes I do assume people won't do something that doesn't make sense to me - and sometimes this assumption fails. Hence the added disclaimer to the top of the post. – raina77ow Jun 18 '21 at 22:50
  • Well, I do understand miken32's reasoning: cutting corners in explanation (and assuming it's clearly visible that corners ARE cut) sometimes misfires here at SO, when others come and see the code as is. But yes, for me the question was essentially more about limiting the amount of data that gets passed to exception tracing than about particular ways of storing those traces. – raina77ow Jun 18 '21 at 23:13
  • Anyone who assumed that the original `var_dump()` would be shown to end users was only being deliberately difficult, as it is obvious enough that it is just an example. I am shocked at how nasty people like @miken32 are over a simple example for the sake of explanation. – reformed Jun 18 '21 at 23:19
0

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
}
reformed
  • 4,505
  • 11
  • 62
  • 88
  • Your question did not imply to redact all parameters at once - only sensitive ones. The other answers understood it the latter way, too. The code should be smart enough to only redact parameters of `is_string()` (recursively doing the same for `array` and `object` types). – AmigoJack Jul 25 '23 at 00:13
  • @AmigoJack That is not what I want. I want to hide only sensitive arguments, not all arguments and not all arguments of `is_string()`. – reformed Jul 25 '23 at 14:15
  • Then both your code **and** accepting that answer is wrong, because it **does** redact all parameters. Also I did not wrote you should redact "_all arguments_" of type `string`. – AmigoJack Jul 25 '23 at 15:49
  • @AmigoJAck No, it's not, and no, it doesn't. The code only redacts the parameters of specific functions specified in `$noArgs`. – reformed Jul 27 '23 at 16:06
  • Yes, of specific functions. No, **all** parameters of those, not selected of those. Your formulation "_allowing other non-sensitive arguments_" does not mean either none of a function's parameters is redacted or all of them - it means that only selected parameters of selected functions should be redacted. However, your code is an all-or-nothing approach. – AmigoJack Jul 27 '23 at 21:32
  • @AmigoJack Hey genius, congratulations on noticing that my answer already says its solution "clears the arguments of specific functions." Has it occurred to you that the code I provided is the closest I could get to what I wanted? How about instead of being annoying, you post a better answer that works – reformed Jul 27 '23 at 23:15
  • Neither the PHP version nor being _only somewhat_ close was any requirement in the Q - those come in now and by your A and your comments. Why don't you unaccept your own A and maybe accept the one that really fits your Q **already**? Just because it came in 1 year later doesn't mean it's an unacceptable/incorrect A. It's unfair to other posters to shift your requirements at will - that's why others are upvoted and you never got one. – AmigoJack Jul 28 '23 at 00:20
  • Stop whining about being unfair, that is a lie. I have not shifted my requirements at all. My question was posted 2021, PHP 8.2 was released November 2022, and one of the servers I maintain is not yet PHP 8.2. If I was running PHP 8.2 I would accept Josh's answer. In fact, I upvoted it because it is a great solution for PHP 8.2+. But as it stands, I've accepted my own because it is the only answer that solves my own problem with what I have. Again, if you have anything better, post an answer. Otherwise, begone, if you have nothing to offer except to whine. – reformed Jul 28 '23 at 19:49
  • @AmigoJack: Why not thank _reformed_ for Q&A this fully back then? You wouldn't have had the opportunity to comment more than a year later, but also the Q&A already collected quite some shared wisdom of the different options that are available in past, present and future (not seeing you're contributing much to it, sadly). And while you're at it, why not also thank the PHP developers that filed the changes for this new feature. And even if you fully disagree, it's the asking user who accepts an answer, don't question the question nor the green check mark. That's plain bad style. – hakre Jul 28 '23 at 21:42
  • @hakre I could contribute the PHP 5.4 compatible code I wrote in 2012 similar to this, which only redacts `string` parameters to chosen functions (because passwords and usernames are always strings) and with less strict function name matching, still working very well as of today. But I wonder who'd be interested in that (anymore) - my comments were largely rejected, too. I should report reformed's comment(s) for being subjective as contribution... – AmigoJack Jul 28 '23 at 23:04
  • Sounds to me that instead of running with heads against each other you two should put heads and hands together. But that would not play well if you still think that you need to report comments (which are subjective, this is what that are in part for, just don't clash). Why not put your example on 3v4l.org and drop the link here, then everyone can take a look and consider things?! @AmigoJack – hakre Jul 28 '23 at 23:14
  • 1
    @hakre https://3v4l.org/fKQAc#v5.6.40 sums up how I did it, redacting only per data type, but also recursively. It you guys think it's worth an answer I'll post it. Sadly it looks much more complex, but that's the price for precise redaction and not killing too much details for debugging purposes. – AmigoJack Jul 31 '23 at 17:50
  • Looks at least solid, perhaps more standard coding style could benefit a public presentation (but that is clearly subjective, just my commentary). support for both debug_backtrace() and Exception::getTrace(), error handler w/o args, and as it traverses structures, it's an addition. Maybe one or two feature flags, can imagine those handlers may bring memory to limits with the one or other code-base and E_ALL. Why not post it and add some infos on top so the context is clear. @reformed you're still with us? Maybe add your 2 cents? – hakre Jul 31 '23 at 21:22
  • @hakre my question was asked back in 2021 and when no one came up with a suitable solution, I settled for what I posted in my answer. This has been done and over with since then, and I don't have time to continue hashing this out. When I move to PHP8.2 I may revisit this, but there's nothing stopping anyone from asking a PHP8.2 specific question. – reformed Aug 01 '23 at 16:47
  • @reformed: Sure, but my intend was not to ask for an update, but if you're fine with an additional answer of that kind for your original question. If you don't have any objections I'd say very fine to go. – hakre Aug 01 '23 at 22:24