Security Tip: Hide Sensitive Parameters from Stack Traces
[Tip#60] Stack traces are essential for debugging complex (and even simple) issues, but there is a risk that something sensitive might be exposed within your trace... Let's ensure that doesn't happen!
Stack traces are incredibly useful debugging tools. They tell you exactly what methods were called in your app, making it trivial to step through exactly what happened leading up to an exception. While we all expect them to contain class, method, and function names, did you know they can also contain the values of attributes within the functions?
Consider this code:
function authorise(string $username, string $password)
{
throw new Exception("Error!");
}
authorise('Gandalf', 'mellon');
It produces the following error when run:
$ php /tmp/example.php
PHP Fatal error: Uncaught Exception: Error! in /tmp/example.php:5
Stack trace:
#0 /tmp/example.php(8): authorise('Gandalf', 'mellon')
#1 {main}
thrown in /tmp/example.php on line 5
If you check the first line of the stack trace (the line starting with #0
) you’ll see quite clearly both the username (“Gandalf”) and the password (“mellon”)! While this information can be incredibly helpful when debugging, it’s also a huge security risk.
We normally expect stack traces so only be accessible through our app logs, however it’s not that uncommon to find debug mode turned on on world-accessible apps. If this is the case, your parameters will be on public display. Even without that, your logs are sitting in plaintext on your server or sent off to some third-party collection system. This makes them incredibly vulnerable and is not a secure way to store sensitive information.
I actually started to write this tip before that one, but realised I hadn’t written about debug mode yet, so I went to write that one first. Now a week later, I’m finishing this tip!
On the subject of your logs, which may contain stack traces, just how tightly do you manage access to your logs? Who on your team can access them? What sensitive information could be included within your logs? What if the third-party log collector has a breach?
Now, at this point, some of you are probably wondering:
Why can’t I seeing parameters in my stack traces?
Well, it turns out that PHP has a php.ini
setting, zend.exception_ignore_args
, which excludes all arguments from stack traces. The setting was introduced in PHP 7.4, and is enabled by default on many configurations, including Laravel Forge.
Here’s how it appears on my Forge configuration:
; Allows to include or exclude arguments from stack traces generated for exceptions.
; In production, it is recommended to turn this setting on to prohibit the output
; of sensitive information in stack traces
; Default Value: Off
; Development Value: Off
; Production Value: On
zend.exception_ignore_args = On
So my advice here is quite simple: if you don’t need parameters in stack traces, make sure you’ve got this enabled.
There is a good chance you do, but it’s worth checking to make sure.
But wait, we’re not done…
If you do need parameters included in your stack traces, then PHP 8.2 introduces a new attribute: #[\SensitiveParameter]
for you.
If we add this attribute to our code above:
function authorise(
string $username,
#[\SensitiveParameter] string $password
) {
throw new Exception("Error!");
}
authorise('Gandalf', 'mellon');
And then run the code:
$ php /tmp/example.php
PHP Fatal error: Uncaught Exception: Error! in /tmp/example.php:5
Stack trace:
#0 /tmp/example.php(8): authorise('Gandalf', Object(SensitiveParameterValue))
#1 {main}
thrown in /tmp/example.php on line 5
You’ll see the sensitive $password
parameter is now excluded from the stack trace. 🎉
There is more information about it in the RFC: https://wiki.php.net/rfc/redact_parameters_in_back_traces