Although alot of people will be running PHP on an Apache environment I have several reasons to be running mine on Windows, specifically 2012 R2. Lately I’ve been seeing alot of activity of people trying to inject mallicious code into several of my clients websites and so have begun drilling down into securing PHP. To improve security I’ve looked at several different sites and came up with the following config changes that I now run on most of my production environments. I will give the list below and also why I made the changes:
Step 1: Global configuration options
engine = On short_open_tag = Off asp_tags = Off precision = 14 output_buffering = 4096 zlib.output_compression = Off implicit_flush = Off unserialize_callback_func = serialize_precision = 17 disable_functions = "curl_exec, curl_multi_exec, dl, error_log, error_reporting, eval, exec, fsockopen, ftp_connect, ftp_exec, ftp_get, ftp_login, ftp_nb_fput, ftp_put, ftp_raw, ftp_rawlist, get_current_user, getmyuid, getmygid, getmypid, getmyinode, getlastmod, ini_alter, ini_get, ini_set, parse_ini_file, php_uname, phpinfo, passthru, popen, proc_open, proc_close, proc_terminate, shell_exec, show_source, symlink, syslog, system" disable_classes = zend.enable_gc = On expose_php = Off max_input_time = 60 memory_limit = 128M error_reporting = E_ALL & ~E_STRICT & ~E_WARNING & ~E_NOTICE display_errors = Off display_startup_errors = Off log_errors = On log_errors_max_len = 1024 ignore_repeated_errors = On ignore_repeated_source = On report_memleaks = On track_errors = Off html_errors = On variables_order = "GPCS" request_order = "GP" register_argc_argv = Off auto_globals_jit = On post_max_size = 100M auto_prepend_file = auto_append_file = default_mimetype = "text/html" default_charset = "UTF-8" doc_root = user_dir = enable_dl = Off cgi.force_redirect = 0 fastcgi.impersonate = 1 file_uploads = On upload_max_filesize = 100M max_file_uploads = 20 allow_url_fopen = Off allow_url_include = Off default_socket_timeout = 60 magic_quotes_gpc = Off upload_tmp_dir = U:\PHP\Temp
- disable_functions; is very usefull. Alot of virusses that try to use POST inject will try to run commands like eval, shell_exec and exec on your server. The best way to secure this is to simply not allow it!
- allow_url_fopen & allow_url_include; both turned off. I don’t need this nor will you most likely ever do. Turning this off by default will help secure you even more.
- expose_php; hiding PHP is also a good move. Security through obscurity isn’t good as the only source of protection. But it sure helps!
- upload_tmp_dir; moving the temp dir to a folder only IUSR has access to will also help, more on this later however.
Step 2: Removing & updating extensions
Some extensions you don’t need. Some PHP has already deprecated (like mysql). Simply removing them will do you alot of good! Also, try to get the newest versions of extensions from https://windows.php.net/downloads/pecl/releases/
Step 3: Segment your websites
A feature I didn’t know about but has proved both helpfull and a bit of a pain in the ass. All my users websites are stored in a U:\InetPub\example.com\ folder structure. In this folder they have a public directory for storing their websites publically accessible files. In php.ini there is a feature to set parts of your config based only scripts running in certain directories. Like so:
[PATH=U:\InetPub\example.com] open_basedir = "U:\InetPub\example.com" error_log = "U:\InetPub\example.com\php-errors.log" upload_tmp_dir = "U:\InetPub\example.com\tmp" session.save_path = "U:\PHP\Sessions\example.com"
- open_basedir; will make sure that scripts can only access files running in that folder tree as a root. This means no getting to other files outside that specifics sites folders. If something manages to get in it is contained to that one site.
- error_log; per user error logs. Since we disabled showing errors and are not letting users mess with that (all the ini and error_reporting functions are locked down), I place the errorlog inside the user dir. They can see it using FTP.
- upload_tmp_dir; There he is again. This was the ‘pain in the ass’ part. First I moved this to outside the open_basedir but that caused issues with uploads no longer working. So I moved it back into the basedir and it now works fine. Again, limit the scope for cross-site attacks by not letting people get into each others tmp files.
- session.save_path; This can be safely stored outside the basedir and should. No letting PHP scripts hi-jack session data. This is stored in a folder structure where only that specific site has access to it’s session data.
Todo
I’d also like to make it in such a way that all the worker processes run under different users, this would add another level of complexity but would make it even harder for scripts to mess with the OS or other sites. When I manage to add that part I will update this post or add a new one!