Tools for making your web application more secure.
The Open Web App Security Project (OWASP) is an excellent guide
for increasing the security of your web application, and is highly recommended.
An important point to understand is the separation of validation into two distinct parts -
hard validation, and soft validation - see {@link hirondelle.web4j.security.ApplicationFirewall}
for more information.
Recommendations and Reminders
SafeText
Free-form user input should always be modeled as {@link hirondelle.web4j.security.SafeText}, not String.
This provides protection against XSS (Cross Site Scripting) attacks, without forcing you to continually
escape special characters in JSPs.
POST versus GET
Forms should always specify the correct method attribute. The general rule is that any
action that has a side effect (database edits, logging off) should be a POST, while an action without
any side-effect (list or search operations, reports) should be a GET.
Content-Type
Always specify the content-type. This can be done in template JSPs, to reduce the repetition of
identical markup. Example using a directive to specify an HTTP header :
<%@ page contentType="text/html" %>
Such directives must appear at the start of the page.
The jsp-property-group setting in web.xml can specify an include-prelude JSP, which
will be automatically included at the start of your JSPs.
Filter
The {@link hirondelle.web4j.security.CsrfFilter} should be configured, to
protect against CSRF attacks.
This filter requires configuration for FormSourceIdRead and FormSourceIdWrite. These
items reference SqlIds. As usual, these SqlIds need to be declared in one of your application's classes, and
also appear in an .sql file. (This ensures the usual matching of .sql file content to SqlIds will not fail.
See hirondelle.web4j.database for more information.)
ApplicationFirewall
The {@link hirondelle.web4j.security.ApplicationFirewallImpl} provided by the framework is an
excellent default for the {@link hirondelle.web4j.security.ApplicationFirewall} interface. It performs sanity checks on
every request, to make sure they aren't hacks.
You can subclass that implementation in order to add further checks, if desired.
(Replacing the implementation entirely is possible, but is usually an extensive task, and should likely be
attempted only if you are experienced.)
SpamDetector
Implementations for the {@link hirondelle.web4j.security.SpamDetector} interface can vary widely. The
{@link hirondelle.web4j.security.SpamDetectorImpl} is a simple default, but you will occasionally need to replace it with
something more appropriate> This will usually be easy to implement.
Cross Site Scripting (XSS) Attacks
The {@link hirondelle.web4j.security.SafeText} class is provided as the main defense against
XSS attacks. The {@link hirondelle.web4j.security.SafeText#toString()}
method performs proper escaping for HTML, and {@link hirondelle.web4j.security.SafeText#getXmlSafe()} performs proper
escaping for XML documents. For the most common case of presenting data in a web page, a SafeText
object will be rendered safely by default.
It is highly recommended that all free-form text input by the user be represented in a Model Object using SafeText instead of String.
Using SafeText will greatly increase your safety when using JSTL.
The JSTL is a bit defective when it comes to protecting you from XSS attacks:
- the <c:out> tag escapes characters by default, but it only escapes for XML, and not for HTML.
That is, it escapes only for 5 of the 12 characters recommended
by the OWASP. The same is true for JSTL's fn:escapeXml() function.
- JSTL's Expression Language is nice and concise, but it doesn't escape any characters at all. It is dangerously open to XSS attacks,
since the application programmer always needs to remember to manually escape special characters.
When JSTL is used with a SafeText object, however, these problems do not occur, since by default SafeText.toString()
will do the correct escaping for you in the background.
Cross Site Request Forgery (CSRF) Attacks
The central idea of protecting against CSRF attacks is that a
server should be able to answer the question "Did this POSTed form really come from me?", and not from some unknown third party.
The {@link hirondelle.web4j.security.CsrfFilter} is provided as a defense against CSRF attacks.
To use it, you must have the CsrfFilter configured as a Servlet Filter in web.xml.
As part of that configuration, you must provide two SqlId's, to tell the Filter what SQL statements to use to
read and write 'form-source ids', the tokens used to identify forms generated by your web app.
Although it is not really necessary for those simply wanting to use CsrfFilter, the following is a description of its
implementation.
Form-Source Ids
When a user logs in, {@link hirondelle.web4j.security.CsrfFilter} will create a 'form-source id', and store it in the
user's session under the key 'web4j_key_for_form_source_id'.
This form-source id is constant for each session, and is hard to guess.
The CsrfFilter will automatically modify all of your forms having method='POST'
to include a hidden request parameter of the same name (web4j_key_for_form_source_id),
whose value is simply the value created upon login.
Verifying Form-Source Ids
The default {@link hirondelle.web4j.security.ApplicationFirewallImpl} will verify that each POSTed
form includes a parameter named web4j_key_for_form_source_id, and that its value matches either
the current value stored in the user's session, or the form-source id used in the immediately preceding
session for the same user.
The form-source id used in the previous session is needed to ensure smooth behavior upon re-login.
Here is the use case in more detail :
- the user logs in
- the user navigates to a form (containing a hidden form-source id param)
- the session expires
- the user posts the form, without knowing the session has expired
When the form is POSTed, the user will of course need to log in a second time. After successful authentication, the action
will continue in the usual way. In this case, however, note that there is a new session. Thus, the old form-source id attached to the
first session is POSTed, not the current one. What is to be done? Well, if this use case is to be handled gracefully,
then the 'old' form-source id must be remembered (in the database) and treated as second valid value.
Then, a POSTed form will succeed only if its web4j_key_for_form_source_id has either
the current form-source id value, or the 'old' form-source id value of the immediately preceding session.
This is the reason for the two SqlId configuration settings for the CsrfFilter: they tell the Filter how to read and
write the form-source id for a given user. This allows the Filter to remember the previous form-source id value for each user.
When a user logs out, or when a session is about to expire, CsrfFilter will extract the user's form-source id from the session,
and store it in the database for possible future use.
Sessions With No Login
There are some common forms for which no valid login is possible :
- creating an account
- recovering a lost password
In this case, a session is used, but without the usual corresponding login.
Since the usual login mechanism does not apply, the Action itself must ensure that a session exists, and that a CSRF token is created.
This is done simply by calling {@link hirondelle.web4j.action.ActionImpl#createSessionAndCsrfToken()} (typically in the Action's constructor).
There is one difference when a session does not include a user login: when a session expires, there's no
way to recover using a 'previous' token from a previous session. Again, this is because the session is not attached to a specific user.