Anatomy of a hardened Apache2 configuration
If you enjoy my work, sponsor or hire me! I work hard keeping oxasploits running!Bitcoin Address:
bc1qclqhff9dlvmmuqgu4907gh6gxy8wy8yqk596yp
Thank you so much and happy hacking!
Some Suggestions
This guide applies to Apache 2.4+.
I cover most things here, but anything else is easily found at the Apache2 documentation site.
So first I want to suggest that you break up your configuration files, and then include each site (and other major config types) into the main server config. Your configuration file with server wide settings will be called
apache2.conf
, or sometimes httpd.conf
and is usually going to be located in the/etc/apache2/
directory. Under this main apache2 configuration directory, you will probably also see some other directories, and these are going to be included from your primary configuration file later. The three we will be using today will be
mods-available
/ mods-enabled
,
sites-available
/ sites-enabled
, and conf-available
/
conf-enabled
. Now you can really organize all this however you want, so long as you include everything correctly. But I suggest using the default scheme, as most people, as well as documentation, follows this setup.
The Primary Config File
Now that that is out of the way, we’re going to take a look at the current config, if you have just installed apache2 from your package manager, it is going to look very generic, like this (except with a lot of documentation commented out, I will be omitting):
Hint: You can document your own configuration as you like by adding a hash “#
” character in front of what you want to remind yourself or someone else!
ServerRoot "/etc/apache2"
Mutex file:${APACHE_LOCK_DIR} default
PidFile ${APACHE_PID_FILE}
Timeout 300
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}
HostnameLookups Off
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf
Include ports.conf
<Directory />
Options FollowSymLinks
AllowOverride None
Require all denied
</Directory>
<Directory /usr/share>
AllowOverride None
Require all granted
</Directory>
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
AccessFileName .htaccess
<FilesMatch "^\.ht">
Require all denied
</FilesMatch>
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf
Most of these things should be left in, and can be unchanged.
So the first line ServerRoot "/etc/apache2"
just tells us what directory all our configuration things will be housed in. It should never have a trailing slash.
Our first security type feature is the User
and Group
declerations, which tell the apache2 binary what priveleges to run under. This is important because if the server
does happen to get pwnt, then the attacker does not get higher privs than what are listed here.
Warning: NEVER set either of these to root or your own personal account user.
Your next two important security type directives are ErrorLog, and LogLevel. I would recommend leaving these the same, except if you are having issues you are welcome to change the log level to anything from trace1 to trace8, as well as debug, info, notice, warn, error, crit, alert, emerg. This will give you some debugging output. Be careful not to leave the debugging output on in production, this will probably cause performance issues under load.
Your next three important things are your single Include
files and your IncludeOptional
directories, each with files to include in their first level. These will be important later on.
Now the only
Directory
directive that I leave in my prod servers is:
<Directory />
Options FollowSymLinks
AllowOverride None
Require all denied
</Directory>
So that in no way can the root directory of our server be accessed. We first tell it to follow every simlink under, and then explicityly disallow reading of this directory by anything client side, and allows no possiblity of .htaccess override.
We also then completely disallow client side loading of any file beginning with .ht. This is a reference to .htaccess
and
.htpasswd
, in case those are anywhere that are currently being served, and in the case of .htaccess
, it will be.
You’ll also have your LogFormat directives that tell the server how to generate your error.log
and
access.log
. Unless otherwise specified, these will appear in
/var/log/apache2/
.
Now Some Security Focused Additions
I’ll share a somewhat redacted version of one of my own apache2.conf
files. Some these things may need tweaking for your intended use case, please don’t just copy/paste it.
DefaultRuntimeDir ${APACHE_RUN_DIR}
PidFile ${APACHE_PID_FILE}
Timeout 300
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}
HostnameLookups Off
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf
IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf
Include ports.conf
SetEnv SERVER_ADMIN youremail@yoursite.com
SetEnv TZ America/New_York
DefaultLanguage en-US
LoadModule headers_module modules/mod_headers.so
Header always set X-XSS-Protection "1; mode=block"
Header always append X-Frame-Options deny
Header always set X-Content-Type-Options "nosniff"
Header always set Content-Security-Policy "default-src 'self' data: blob: cdn.jsdelivr.net;"
Header always set Content-Security-Policy "style-src 'self' *.google.com googleapis.com;"
Header always set Content-Security-Policy "script-src 'self' nonce-76dbe9426bd20a5ffbe536edc407958201af3a03 *.google.com;"
Header always set Content-Security-Policy "frame-src: 'self' feed.mikle.com;"
Header always set Permissions-Policy "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()"
Header always set Referrer-Policy "no-referrer-when-downgrade"
Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure;SameSite=None
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
ServerSignature Off
ServerTokens Prod
AccessFileName .htaccess
TraceEnable Off
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" detailed
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
<Directory />
Options +FollowSymLinks
AllowOverride None
Require all denied
</Directory>
<FilesMatch "^\.ht">
Require all denied
</FilesMatch>
Note: You can add the next few things I talk about to /etc/apache2/conf-enabled/security.conf
, or keep them all in one place if you would like!
So lots of this is the same with some minor modifications, but I’ll go over things you should consider adding.
First, in case of unexpected errors, you should include your email in the server config so a user can contact you in case of a service outage. We do this with the
SetEnv SERVER_ADMIN
server environment variable.
We shoudl also turn on inline header addition and modification for the next couple lines, we’ll do this with LoadModule headers_module modules/mod_headers.so
. As a result now we can set things like server-side XSS protection with: Header always set X-XSS-Protection "1; mode=block"
and it’s companion options:
Header always append X-Frame-Options deny
and
Header always set X-Content-Type-Options "nosniff"
. We do this via headers because some of this needs to direct our clients, the web browsers, how to interact wtih some of the data we share with them.
Another thing we can accomplish with header addition is using our CSP, or Content Security Policy, basically directing via the server what the client browser can pull and use on the page from an external source, and where specifically they can pull it from. We do this with
Header always set Content-Security-Policy
. If I say wanted to be able to run externally hosted javascript from js.google.com or script.google.com, we could set it as such:
Header always set Content-Security-Policy "script-src 'self' *.google.com;"
. As you can see, there are different types, for javascript, iframes, css, etc.
We can also set our Referrer-Policy header, which is important in the case of other insecure websites picking up which page our clients were on last. For my uses, I have set mine to
no-referrer-when-downgrade
, but you can also set to a number of others including: “no-referrer”, “no-referrer-when-downgrade”, “same-origin”, “origin”, “strict-origin”, “origin-when-cross-origin”, “strict-origin-when-cross-origin”, and “unsafe-url”. Each has it’s own pace, and you can read more about each policy specifically
here.
Our
Permissions-Policy
header also should be set on most modern webservers. This dictates what rights our server has to access on the client browser’s computer, like a camera, or their fine location. Your site and what you use the browser for is going to tell you what permissions you need, so here I will deferr to a nice
Permissions Policy Generator
I’ve found useful that can automagically generate this string for you without errors. I suggest only asking for what you really need, as asking for too many “unused” permissions is a great way to turn a user away.
One cool thing we can do with the Header edit
directive is do things like edit a cookie inline before sending it to the user, so say, we want to bump all cookies to
Httponly;Secure;
we would use the regular expression: Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure;
as a catchall.
I normally also remove access to insecure SSL protocols when connecting to my server. There is nothing worse than thinking your connection is secure, when it is not. You can do this by using the SSLProtocol directive and setting it as you wish. I set mine:
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
for good measure.
It is important to make sure our now secured server does not leak internal server information when an attacker is poking at it, generating error codes. So now that our server is tightened up, we should set
TraceEnable Off
, ServerSignature Off
and finally ServerTokens Prod
. Now our errors should be able to be read, and seen as an error by an end user, but won’t leak important serverside information like IP addresses and server versions. For future reference, trace should always be disabled on an internet facing production server, as it is an unsafe HTTP method.
The Site
Now we can head over to
/etc/apache2/sites-available
and create your site. I’ll assume you already have SSL/HTTPS setup and working for your site, where it be with LetsEncrypt, or something else. I’ll also assume you have a working site with at least a main page and a subdirectory with something in it.
I’ll show you a simple config of one of my sites with some more sensitive things removed.
So in /etc/apache2/sites-available
I have the following file called site.com-ssl.conf
. There should also a symbolic link that links this file to
/etc/apache2/sites-enabled
so that the data is parsed by then served by apache2.
<IfModule mod_ssl.c>
<VirtualHost *:443>
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
ServerAdmin marshall@site.com
DocumentRoot /var/www/site.com
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
ServerName site.com
ServerAlias site.com
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/site.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/site.com/privkey.pem
</VirtualHost>
</IfModule>
<Directory "/var/www/site.com/">
Options -Indexes +FollowSymLinks -ExecCGI
AllowOverride all
Require all granted
Header always set Access-Control-Allow-Origin "*"
RewriteEngine on
ErrorDocument 403 https://site.com/403.html
ErrorDocument 404 https://site.com/404.html
RewriteCond %{REQUEST_METHOD} !^(GET|HEAD|POST|OPTIONS)$ [NC]
RewriteRule .? - [F,NS,L]
RewriteCond %{THE_REQUEST} !^[A-Z]{3,9}\ .+\ HTTP/(0\.9|1\.0|1\.1) [NC]
RewriteRule .? - [F,NS,L]
</Directory>
<Location "/code/">
AddType text/plain .txt .cpp .c .pl .sh .py
Options +Indexes +FollowSymLinks -ExecCGI
require all granted
AllowOverride None
</Location>
So as you can see, a rather generic apache2 setup with a couple additions.
You will need a DocumentRoot
at minimum pointing to the lowest directory you would like to serve for this site.
Our Directory tags point to a place on the filesystem that we would like to add the enclosed directives to. The Location tags enclose a location that is served by the webserver that we would like to add the directives to. This is an important distinction.
If we are not using CGI (like Perl or PHP) in this directory, make sure to add -ExecCGI
to the Options directive. You may also add or omit Indexes
, with either a plus or minus, to tell it weather we want a directory listing in child directories or not. I leave this off here.
Assuming we have our header modifications on, we can have
Header always set Access-Control-Allow-Origin "*"
for access control, if you are not pulling from anything else, you can set this to your site only to tighten it up a little bit.
We want our url rewrite engine on so that we can tell it ONLY to alllow safe HTTP methods with RewriteCond %{REQUEST_METHOD} !^(GET|HEAD|POST|OPTIONS)$ [NC]
and then RewriteRule .? - [F,NS,L]
, and we would like to only have valid HTTP requests come through, hence the next directive RewriteCond %{THE_REQUEST} !^[A-Z]{3,9}\ .+\ HTTP/(0\.9|1\.0|1\.1) [NC]
and RewriteRule .? - [F,NS,L]
.
To wrap it up, we can optionally run a HTTP server as well as our HTTPS server, and just use the HTTP to bump over to HTTPS with a rewrite, so if you would like to do this, create a file called
site.com.conf
that looks something like this:
<VirtualHost *:80>
ServerAdmin marshall@site.org
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
RewriteEngine on
RewriteCond %{SERVER_NAME} =site.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
Conclusion
Now you’ll just need to run systemctl restart apache2
to restart the server with your new settings. Now that we have it tightened up, you may have a little more peace of mind against hackers. However, always make sure to check your logs for suspicious code.
Feel free to email me with comments and suggestions!
Happy serving!