Secure your PHP (on Windows)

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!

Leave a Reply

Your email address will not be published. Required fields are marked *