Last Updated: 2014-03-27 16:14:10 UTC
by Alex Stanford (Version: 1)
In writing web applications PHP developers often find themselves repeatedly calling the
htmlentities function, or the
htmlspecialchars function. These will encode the special characters of a string to their HTML entities, ensuring that output can safely avoid being executed by browser parsing engines.
The problem with this is a human one. Humans do make mistakes, and even those well aware of the consequences and solutions will eventually suffer from an oversight that results in an XSS vulnerability. How can we limit the possibility of creating vulnerabilities in such a situation?
We’ve seen a very fair share of approaches to mitigating XSS in PHP but one in particular seems to fly under the radar. PHP has a couple of configuration directives in php.ini which will automagically filter input by various sanitization and/or validation flags of your choosing. So, can we make it work like
In your (recent) default php.ini file you will find the following:
[filter] ; http://php.net/filter.default ;filter.default = unsafe_raw ; http://php.net/filter.default-flags ;filter.default_flags =
Modify these as follows:
[filter] ; http://php.net/filter.default filter.default = full_special_chars ; http://php.net/filter.default-flags filter.default_flags = 0
This will encode all $_GET, $_POST, $_COOKIE, $_REQUEST and $_SERVER values. (The original data can be accessed through the filter_input() function.)
Example In Action
As a quick proof of concept I built a simple login form that does no sanitization or encoding. The first (Username) field is pre-filled with the data you submitted if an error occurs, such as not providing any password. To exploit the first form field, I entered
"><script>alert("XSS");</script> with no password at all.
Without the php.ini configuration changes:
Now, with the php.ini configuration changes:
The input data is safely output into the form field as content instead of code, thereby mitigating the XSS vulnerability.
Do I still need to perform output encoding in my application?
Yes. This approach will handle a large portion of the repetitive cases, but some necessity for output encoding will remain. A simple example would be the importance of using the
urlencodefunction upon outputting a URL which contains user input.
Does this work for distributable web apps that run in shared hosting environments?
This approach may not always be feasible in shared environments due to the potentially limited access to php.ini directives. If you’re building distributable web apps which support running in shared environments, it is not safe to rely on this approach. However, if you’re working in an environment with a custom PHP back-end running on a dedicated server(s) this approach may be your best bet.
Won’t this result in double encoding?
Yes, quite possibly. That said, double encoding is far lower risk and more easily identifiable than XSS vulnerabilities.
How can I check that the directives are properly set before outputting anything from my application?
The following code will check that the php.ini settings are in place as expected and discontinue execution with a relevant error otherwise. It should be placed at the beginning of your application before any other code is executed.
if(ini_get('filter.default')!=='full_special_chars'||ini_get('filter.default_flags')!=='0') die('Missing and/or incorrect filter.default and/or filter.default_flags directives in php.ini');
Not a Replacement for Defense in Depth
While this approach can simplify output encoding and limit the risk of developer oversight, it should not be considered an end-all solution. You may have input data sources in your application other than PHP's superglobals. You should still consider the results of a SQL query or cURL request, for example, as potentially malicious. Finally, you should continue performing penetration testing and code reviews to catch that inevitable XSS vulnerability before they do.