Apache config and .htaccess - security

From Helpful
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Related to web development, lower level hosting, and such: (See also the webdev category)

Lower levels


Server stuff:


Higher levels


📃 These are primarily notes, intended to be a collection of useful fragments, that will probably never be complete in any sense.

AllowOverride

AllowOverride controls which directives will (not) be picked up from .htaccess files.


It knows about five categories, that Apache directives are sorted into:

  • FileInfo: document and handler related things, including SetHandler, ErrorDocument, DefaultType, input and output filters, mod_mime directives.
  • Options: refers mainly to the Options directive.
  • AuthConfig: user authentication directives, including Require, AuthName, AuthType, and AuthUserFile.
  • Limit: host limiting directives, particularly Allow, Deny, and Order.
  • Indexes: Apache-generated directory index details like DirectoryIndex, FancyIndexing, ReadmeName, AddIconByEncoding, AddIconByType, DefaultIcon, IndexIgnore, and IndexOptions.
  • You can also specify All and None


From an administrative view, it is cleaner to disable all overrides server-wide, then allow things that users argue are necessary.

If you trust your users enough, the admin can be relieved of continual tweaking for users if they are allowed to to do most things via .htaccess. You can try to balance these.

See also When (not) to use .htaccess files.


Disabling everything is overly paranoid, in that AuthConfig and Limit are not hugely abusable (users can only misconfigure their own access control). (And users may assume that their password-protected directories they protected elsewhere and copied to your server will be safe here too)


The vaguely-abusable options are FileInfo (handlers, filters) and Options (symlinks). Consider that the (now-common) scripting can do many of these things anyway, and you probably have it installed (think PHP). (There may be some difference in as what user they can do it, though.)

Options

Controls features per directory.

If used in .htaccess, it has to be allowed via AllowOverride.


See also:

Access restriction

TODO: clarify 2.2-versus-2.4-style in all of the below


Host/net restrictions

You might be tempted to think of these as ACLs (order matters, terminate once matched) but that would lead to wrong behaviour.


Instead, apache uses what the last applicable rule said, running though a set of allow and a set of deny rules. If any rule applies, the allowed-or-not is updated. It will run though one or the order set first, depending on the Order specified.

Usually, either allow or deny is 'all', meaning that you are effectively using order to control the default policy - whitelisting or blacklisting. (you can also do a conditionless allow or deny by haing one of the lists be empty)


Whitelisting takes the form of "deny everything, then allow specific cases":

Order deny,allow
deny from all
#allow only these two university networks
allow from 130.89.
allow from 129.125.

(Accidentally using Order allow,deny would deny everything)


Blacklisting is "allow everything, but deny specific cases".

Order allow,deny
allow from all
#block these two university networks:
deny from 130.89.
deny from 129.125.

(Accidentally using Order deny,allow would allow everthing)


Per method

You can do per-{HTTPmethod}x{net/host} combinations.

This is probably only useful when you know exactly why you need to block only a few methods instead of all access. Examples where it makes sense include WebDAV and Subversion access, which use methods like PUT, DELETE, OPTIONS, PROPFIND, COPY, MOVE, LOCK, UNLOCK and a few others.


In the following example, I want to block access to all hosts except some home and university computers. More exactly put:

  • Allow GET, POST and HEAD for hosts I work from, deny for all others
  • Deny all other methods from everywhere (optional paranoia against possible abuse of other methods)
<Limit GET POST HEAD>             #Actually redundant; GET implies HEAD
   order deny,allow
   allow from 129.125.                  #subnet, IP:   rug.nl network
   allow from .scarfboy.com             #domain, name: could include my server and workstation
   allow from workstation.scarfboy.com  #host, name:   doesn't exist, but you get the idea
   allow from 3.2.1.2                   #host, IP:    (also doesn't exist)
   deny from all                      #...and deny everyone else
 </Limit>
 <LimitExcept GET POST HEAD>   
   Order deny,allow           # Since there are no allow rules
   Deny from all              #   this means 'block all'
 </LimitExcept>

LimitExcept is a relatively little used addition that limits everything except mentioned methods. It can be usefully combined with Limit to have rules cover all HTTP methods without being able to accidentally forget one.

It allows you to be more explicit about all the cases under consideration, rather than leave them up to defaults and implications.

Downtime notice trick

One simple trick to do a site-wide "this site is being upgraded" notice is to do the following inside a (virtual) host:

# deny from everyone, except from yourself (whatever IP/net)
order deny,allow
deny from all
allow from 192.0.2.199

# custom error document with a useful message
ErrorDocument 403 /updating.html

<Files updating.html>
 allow from all
</Files>

The allowed nets/hosts get the site as usual. Everyone else gets the contents of /updating.html, on every URL. (...because it causes a 403 Forbidden error, which with the ErrorDocument causes a redirect to that /updating.html page)


The above is for bother and security. If you care about crawler reaction you probably want to use 503 Service unavailable instead, because it's treated as temporary by most.

It requires more work, because you can't just piggyback on basic authorization as the above did.


Authentication

You can make apache do HTTP authentication, supported by most any browser. The simplest method is the built-in Apache authentication handling:

AuthType      Basic                  
AuthName      "Seeekrit area"

AuthType sets the type, and the fact authentication needs to happen. AuthName is displayed by the browser when it asks for the login.


AuthTypes

This article/section is a stub — some half-sorted notes, not necessarily checked, not necessarily correct. Feel free to ignore, or tell me about it.

Using AuthType Basic or Aut`hType Digest causes apache to add its own auth handler to requests.

They are two rather different methods of handling password verification. Digest is more secure than Basic but not supported by quite everything. Even so, Digest should be your default choice unless you have specific reason to use Basic.


In both cases you'll also want a Require, for example:

Require valid-user

or

Require user specificusername


Basic

Basic authentication means the client sends the password itself (Base64'd) in every request that the password applies to (by host+realm(verify)).

This makes not only a plain-text setup, but also sent unnecessarily much (even a login token would be a vast improvement). It's easy to sniff the password from traffic, particularly in these WiFi days.

tl;dr: don't even consider Basic auth if not using HTTPS.


Apache's Basic auth handler requires we specify:

AuthType Basic
AuthUserFile  /path/to/.htpasswd
AuthGroupFile /dev/null              # in this case, no user-group map is used
Digest

Digest authentication is an alternative to Basic that uses a hash (also of a fixed name and of a nonce), which means both that the password itself is never sent, and that cryptanalysis of what is sent is non-trivial. (Web servers store a hash of the password. If someone stole that file, they can still do offline brute-forcing based on the stored hashes, though.)


It is intended to be a much better alternative to the insecure Basic auth - not necessarily as the best thing you could be using.

Not all minimal http clients and libraries support it, because it's a little more work than Basic.


Example config in apache:

AuthType       Digest
AuthDigestFile /path/to/.htdigest

In apache 2.2 it was decided that the directive wasn't necessary and you should use the following (you'll see a "Invalid command 'AuthDigestFile'" if you don't)

AuthType     Digest
AuthUserFile /path/to/.htdigest

The realm name is part of Digest authentication, which matters when adding user entries to the digest file, and in that changing the realm name means you need to set new entries.


If you get the log error Access to /path failed: verification of user id '<null>' not configured, this probably means the digest module isn't loaded (on linux you usually need to look to conf.d to add the define that httpd.conf uses as the condition to load it).

.htpasswd / .htdigest

See also HTTP_notes#WWW-Authenticate_and_Authorization for some intro of Basic and Digest auth.


The below is largely apache-specific.

The .htpasswd files (storing the base64) / .htdigest (storing the hashes) can be called anything, but is conventionally called .htpasswd and .htdigest because default apache static file serving is configured to not serve files with names that start with .ht, so you won't expose them even if you placed them under your DocumentRoot for organisation's sake.


The .htpasswd/.htdigest files can sit anywhere as on your filesystem, as long as they are readable by the user apache runs as.

You can choose to keep one central file for all virtual hosts, or keep one per host, or one per directory you configure auth on, or whatnot - the difference is mostly your organisation.


If you use one central file, note that for basic auth you probably want to use Require user and/or Require group, otherwise all users you have at all might be allowed into all protected areas

less of an issue for digest auth, as the realm is part of the hash

Using the htpasswd and htdigest utilities

htpasswd is used to create and alter the password file used Basic AuthType:

htpasswd htpasswdfile username

The first time you need to use -c, meaning 'create the file I'm mentioning'.



htdigest is much like htpasswd, except that you create a hash per usernames-realm combination, so:

htdigest [-c] passwdfile realm username

You'll often want to use quotes around the realm to avoid spaces being bothersome, e.g.:

htdigest .htdigest 'Remote access' username


Authorization

Requirements

apache 2.2

The above deals with authenication, and we still need to do authorisation.


Which means you require something. Options include:

Require valid-user               # any user listed in the htpasswd file
Require user  user1 user2 user3  # one of these specific users listed here
Require group group1 group2      # anyone in one of these groups

Group files consist of lines like:

mygroup: bob joe anne


Note: by the time you want groups, you may want a feature like hooking into a (remote) login server or into system accounts. You could start managing the user and group files, but it is often more convenient to hook into some established login system using apache modules like mod_pam, mod_radius or mod_auth_ldap. Note, however, that this may open such a backend to be more easily brute-forced/DoS, so be careful.


You can selectively apply this to specific HTTP methods by putting the details into <Limit> sections. It's generally easier to have it blanket unless until you are exposing an API of sorts.


apache 2.4
This article/section is a stub — some half-sorted notes, not necessarily checked, not necessarily correct. Feel free to ignore, or tell me about it.

In apache 2.4, the above is deprecated in favour of mod_authz_host

Differences from 2.2:

  • The Order Allow, Deny stuff is gone (verify)
  • Multiple requirements (of distinct types, you can e.g. provide various nets to ip) should be nested in either RequireAll or RequireAny (AND and OR, basically)
When you don't use this but do use multiple Require statements, they work as if they were nested in RequireAny (OR behaviour). You may want to nest them just to make it more intuitive.
  • (Replaces the earlier Satisfy any / Satisfy All stuff)


  • The Require directive has more types.
In 2.2 you mainly had valid-user, user, group, env
In 2.4 you have local, ip, host, http-method, expr
you can also negate things
Sometimes RequireNone is handier.



(If you don't want to convert just yet, look at mod_access_compat)

Satisfy: host and/or user

When you combine host-level (Allow/Deny) and user-level (Require) restrictions, the default behaviour is:

Satisfy all

...which means that both user auth and coming from the specified network has to be satisfied.


If, instead, you want behaviour like "passwordless when I'm home, password required from anywhere else", then you can use:

Satisfy any      # the default is all

...in combination with require and an Order deny,allow type setup. You want to think and double check what you wrote is what you intended, though.

Unsorted

See also


HTTPS (HTTP over TLS/SSL)

See also SSL and HTTPS notes


Note that [mod_ssl] is not the same as Apache-SSL. The two are alternatives, and some configuration is different.


Some background

  • HTTPS is generic HTTP over generic a SSL/TLS connection
  • SSL/TLS needs to handshake, and needs to fetch a certificate, and this happens before HTTP enters the picture
  • that certificate is sent by the Apache SSL plugin. When you serve more than one HTTPS-enabled website you may have a chicken-and-egg problem, in that apache must know which certificate to send, but since HTTP has not entered the picture yet, it can't rely on the HTTP Host header. Your options:
    • serve only one HTTPS website
    • set things up so that you have one certificate be valid for all HTTPS-served websites
    • specify a certificate per IP address+port combo
    • Use SNI (Server Name Indication), an extension of SSL/TLS, functionally very similar to the Host: header in HTTP requests. SNI is essentially required for when you have distict named virtual hosts that use HTTPS and share IP+port
MSIE on XP didn't support SNI. These days you can assume it works.


For many years, browsers would default to HTTP and you would want to serve on port 80, even if all that's there is to forward to HTTPS (port 443)



On redirects between HTTP and HTTPS