Chapter 9. Protecting a Web Application

In this chapter, we will use the knowledge we have gained about ModSecurity to implement a protective ruleset for a real-world web application. The ruleset will be based on a positive security model, so anything which is not explicitly allowed through will be denied. You can compare the positive security model to a bouncer standing guard at a popular club. In his hand he has a list of all the celebrities that are allowed into the club. Anyone not on this list is denied entry. The positive security model works the same way—we explicitly define what is allowed and reject everything else.

We encountered the positive security model in the previous chapter when we saw how it could be implemented using the graphical Remo tool. However, for this chapter we will write the rules by hand to get a feel for how the details of this sort of security model are implemented in practice.

Considerations before beginning

Before implementing a positive security model for a web application, you need to weigh the pros and cons of this kind of model to determine whether or not it will be worthwhile to implement. There are some distinct advantages and disadvantages that come with the positive security model, and depending on the circumstances for each unique web application and the environment in which it exists, implementing it may not always be the best solution. Let's take a look at some of the advantages and drawbacks of this security model.

Advantages of implementing a positive security model:

  • High security

  • Protection against new and unknown forms of attack

  • The web application gets only data it knows how to handle as input, as opposed to being forced to accept any input that the user or an attacker provides

  • The model has the ability to protect third-party web applications without modifying their source code, and to protect legacy applications that are no longer supported

  • If a vulnerability is discovered that could still pass through the positive security model then guarding against it could be as simple as removing a single allowed character from the rule for an argument value

Drawbacks of implementing a positive security model:

  • Requires detailed knowledge of the web application you want to protect

  • Changes in the web application may cause it to break unless the security model is updated

  • Misimplementation, or failing to model uncommon use cases, may also break the web application

  • Takes a significant amount of time and effort

For the reasons just mentioned, you should probably think twice about implementing a positive security model for any web application that requires constant updates, or that is still in ongoing development. Each and every time an application is patched or updated, there is a risk that some functionality will not work with the implemented security model and thus break a part of the application. However, for mature web applications that rarely change implementing a positive security model can be an excellent choice.

Implementing the security model requires a lot of testing to make sure the web application still works as intended after the model is in place. Therefore, you would probably not want to implement the ruleset on a live application with existing users. It's much better to create a copy of the web application on a different server, develop the ruleset so that it can be verified to be working on the copy, and then apply it to the live application.

The web application

The web application we will be protecting is the discussion forum YaBB, short for "Yet Another Bulletin Board". YaBB has been around since the year 2000, and used to be one of the most popular discussion forums—long before the term "web application" became as popular as it is today. YaBB is written in the Perl programming language, and relies on plain-text files to store data, so no fancy database connections are needed to get it working.

Over the years, YaBB has had a number of security problems (as any forum software that has been around this long would). Many of these vulnerabilities have been a result of "creative" query string arguments being passed to the web application, leading to unexpected behavior which in the end could be exploited by an attacker. If you are running YaBB on any of your sites then securing it using a positive security model would be an excellent way to protect against these sort of exploits.

YaBB is available at http://yabbforum.com and the version we will be installing and securing is version 2.4. The following screenshot shows you what a typical YaBB installation looks like:

The web application

Installation instructions for YaBB on Linux are available at http://codex.yabbforum.com/YaBB.pl?num=1216527632. As long as your system meets the requirements outlined at http://www.yabbforum.com/requirements.php then installation should be straightforward.

Groundwork

No matter what sort of web application you want to protect, there is some preparatory work that needs to be done before you can get down to the details of writing rules. We will shortly learn about a four-step process to implement the positive security model, but even before beginning this process, some helpful information to have on hand is the following:

  • Language the web application is written in

  • Source code to the web application

  • Test accounts, including privileged accounts for any restricted/administrative parts of the application

  • Thorough knowledge of the user actions available in the application (make sure you are familiar with the application and know the actions a user would typically perform when working with it)

In our case, we have all of the source code readily available since the forum software is written in Perl. YaBB ships with a default administrative account that has full privileges—this will be valuable for testing all the features of the forum.

Let's now take a look at the four-step process of implementing the security model.

Step 1: Identifying user actions

The first step in working out the model for the web application is to identify which actions a user can take. In the case of our web forum, this includes simple things such as displaying the main board index to more complex actions such as updating the user profile.

The purpose of this step is to create a list of "known good" user actions around which we can then build the security model. The final result should be that each legitimate user action is allowed while everything else is blocked.

This step is important and it requires a very good understanding of the web application you want to protect. Ideally, you would want to be intimately familiar with how to use the web application as well as have access to the source code for it.

When this step is completed, you should have a list of the user actions available for the web application, and this list then forms the basis for the subsequent steps that aim to find out the details on each action so that the protective model can be created.

Step 2: Getting detailed information on each action

The second step in creating the security model is analyzing each user action to see what the legitimate traffic between the web browser and server look like when a user performs the action.

To find these details we need a way to intercept requests so that things such as headers and request method can be examined. Something that is very helpful here is a HTTP debugging proxy. This is a program that acts as a proxy between the web browser and the web server, and allows you to see detailed information about each request.

If you are using Microsoft Windows then one excellent free web debugging proxy is Fiddler, available at http://www.fiddler2.com/fiddler2/. For Linux, one alternative is Ethereal, available at http://www.ethereal.com.

Step 2: Getting detailed information on each action

Fiddler, and other web debugging proxies, give you access to a treasure trove of information about web requests, as seen in the following screenshot:

Step 2: Getting detailed information on each action

In the image above we can see that a request was made to /yabb/YaBB.pl, along with the headers sent by the web browser. This request is actually for the main board index of the forum, and the data provided gives us a good idea of what a legitimate request for the board index looks like.

Performing each user action while the web debugging proxy is in operation allows you to gather important information about each request, such as the following:

  • Request URI

  • Request type (GET, POST, and so on)

  • Query string parameters

  • Request headers

  • Request body (for POST requests)

  • Cookies

Updating the list of user actions so that each action contains the information from the web debugging proxy will allow you to see what legitimate requests look like, and this will form the basis of writing the rules needed to protect the application.

Step 3: Writing rules

This is where we actually begin writing rules. This step consists of examining each action and creating rules that allow legitimate requests through, while blocking anything else. Using the information gathered in the previous step, this should be straightforward.

Some of the typical checks that should be done for each action:

  • Making sure only allowed arguments are present

  • Verifying that each argument contains only acceptable characters (for example by making sure that variables that represent a number only contain digits and not anything else)

  • Preventing requests with overly long arguments by limiting the length of argument values

  • Verifying that the request body conforms to a pre-determined format

There is also the additional step of checking the request headers. Since these will be very similar for the different actions, it makes sense to perform the header check separately, which means we will be able to check the headers using the same rules, no matter what action the user takes.

The same logic also applies to the cookies—in our case, YaBB uses the same cookies for all requests, so we can perform the cookie check in a separate step.

Step 4: Testing the new ruleset

The final step in creating the security model consists of testing our new ruleset. Each action should be performed in the web application to make sure it works as intended. Any cases where you get a denied request (and there will be a lot of these!) need to be resolved by looking at the ModSecurity debug log (make sure the log level is set to 9 to record the maximum amount of information in the log file) and correcting the ruleset so that the model works as intended.

Make sure you use different browsers and operating systems when performing the tests, as the application may work fine in one user environment but not at all in others (some browsers may for example send unusual request headers which may cause the request to be blocked unless taken into account by the security model).

Ideally, you would also want to create a scripted test for all of the most common user actions. This would allow you to test the ruleset in an automated fashion, and would make for easy testing when the web application is upgraded.

Actions

Let's get started with the first step in writing the ruleset—mapping which actions are available to users.

By getting familiar with the forum and using its different functions we can create a list of these actions. The following table shows the different actions and a typical path and query string corresponding to each of the actions. The table also shows whether the request uses the HTTP GET or POST method.

Action

Path and query string

Type

Display main board index

/yabb/YaBB.pl

GET

Display list of topics in board

/yabb/YaBB.pl?board=general

GET

Display topic

/yabb/YaBB.pl?num=1239494700

GET

Show reply to topic form

/yabb/YaBB.pl?action=post;num=1239494700;virboard=;title=PostReply

GET

Show new topic form

/yabb/YaBB.pl?board=general;action=post;title=StartNewTopic

GET

Show search page

/yabb/YaBB.pl?action=search

GET

Perform search

/yabb/YaBB.pl?action=search2

POST

Show user center

/yabb/YaBB.pl?action=mycenter

GET

Show login form

/yabb/YaBB.pl?action=login

GET

Perform login

/yabb/YaBB.pl?action=login2

POST

Logout

/yabb/YaBB.pl?action=logout

GET

Show recent posts

/yabb/YaBB.pl?action=recent

POST

Show new user registration form

/yabb/YaBB.pl?action=register

GET

Perform new user registration

/yabb/YaBB.pl?action=register2

POST

We can see that this is quite work-intensive. For the positive security model to work, each action that the user can take must be mapped. Missing an action would mean that it would be blocked in the final ruleset, something which would no doubt lead to problems when legitimate requests from users are blocked. The above list contains the most common actions a user can take, but for brevity's sake I've omitted some of the less common actions from the list.

As we can see, each action corresponds to a request to the script /yabb/YaBB.pl. The query string arguments passed to YaBB.pl then determine which action gets taken. For example, to show the page used to search for topics or posts, the query string used is action=search, which instructs YaBB.pl to show the search form.

Knowing that /yabb/YaBB.pl is invoked whenever a forum action is taken allows us to limit the scope of the rules we write so that they only apply to requests made to the forum. Wrapping the following Apache<Location> directive around our rules will make sure the rules apply only to forum requests:

<Location /yabb/>
# Our rules go here
</Location>

All the rules to implement the security model will be placed between these tags.

Blocking what's allowed&mdash;denying everything else

So how do we actually implement the positive security model where that which we explicitly allow can pass through and everything else is blocked? Say for example that an argument named foo must have a value of bar, and that any other value for foo should be blocked. One approach would be the following:

SecRule ARGS:foo "!^bar$" "deny"

This works fine and will block any value for foo other than bar. It will even work in cases where foo isn't specified in the query string, since the rule will not be evaluated if the foo argument isn't present. However, what if someone decided to provide an argument named fox in the query string, just to see what would happen? According to our security model, we should block any argument that isn't explicitly whitelisted, so the presence of an argument named fox should lead to a denied request. It's clear that we need to add another check to make sure that only whitelisted argument names are allowed through.

The following rule takes care of checking the argument names and blocks any request that contains arguments other than foo:

SecRule ARGS_NAMES "!^foo$" "deny"

While this works, there is a big problem with it: Some requests may require an argument named only foo, while others may require totally different arguments. Those other requests with different argument names would be blocked by this rule. It's clear that we need to separate the different requests according to which action is being performed, so that each action can be checked separately for exactly those arguments which are necessary for that request.

The ModSecurity action skipAfter, together with the SecMarker directive come in handy here. By defining rule markers using SecMarker we can then jump to the rule following the marker using the skipAfter action. This allows us to perform flow control actions very much like in a programming language that supports the goto statement.

So if we now want to take two separate rule evaluation paths depending on whether the action specified is post or search we could use the following rules to implement that logic:

SecRule ARGS:action "^post$" "pass,skipAfter:100"
SecRule ARGS:action "^search$" "pass,skipAfter:101"
# If we get to this point then an unknown action has
# been found, and the request is blocked
SecAction "deny,msg:'Unknown action'"
SecMarker 100
# Rules for "post" action go here
SecAction "pass,skipAfter:9999"
SecMarker 101
# Rules for "search" action go here
SecAction "pass,skipAfter:9999"
SecMarker 9999
# This is where we jump after we have finished all checks

In the above rules, each action is associated with a SecMarker that has a specific integer value. The first two rules check to see whether the action is post or search, and then use skipAfter to jump to the appropriate rules to handle each action. The pass directive is used to ensure that the requests don't get denied by the default action which will usually be set to deny. If neither of the two action types match then the rule that follows denies the request (this is the "default deny" part of the positive security model).

If you are familiar with programming languages, the following is what the above would correspond to in pseudo-Pascal syntax:

if action = "post" then goto label_100;
if action = "search" then goto label_101;
// Default action for unknown requests
block_request;
label_100:
// Handle "post" action
goto label_9999;
label_101:
// Handle "search" action
goto label_9999;
label_9999:
// We are done

After each action has been handled, a SecAction directive is used to skip to after the marker with ID 9999, which signifies the end of the ruleset for the security model. This ensures that after each action is handled the rule processing stops instead of continuing with the rules for the next action.

Cookies

YaBB uses cookies to keep track of logged-in users. Using our HTTP debugging proxy we can see that they are called Y2User-10491, Y2Pass-10491, and Y2Sess-10491. The second part of the cookie name is a random number unique to each YaBB installation. The cookie names are stored in the file Settings.pl, so looking there would be another way to find out what the cookies are named in a particular installation.

Knowing this, we can create a rule to allow only these three cookies:

SecRule REQUEST_COOKIES_NAMES "!^Y2(Pass|Sess|User)-10491$" "deny"

Now we need to enforce the content of the cookies. Again, our handy proxy tells us what the cookies should look like:

Cookies

These are the values set for the default username/password combination of admin/admin. We see that the first two cookies should contain only letters and digits, and that the final cookie should contain only characters that are acceptable in a username. This allows us to write these rules to make sure the cookies conform to this format:

SecRule REQUEST_COOKIES:Y2Pass-10491 "!^[0-9a-zA-Z]+$" "deny"
SecRule REQUEST_COOKIES:Y2Sess-10491 "!^[0-9a-zA-Z]+$" "deny"
SecRule REQUEST_COOKIES:Y2User-10491 "!^[-_0-9a-zA-Z+.]+$" "deny"

The cookie check can be performed at the top of the ruleset, before each individual action is checked, which saves a lot of repetition as we can perform this check once and then be done with it.

If the forum is not on its own sub-domain (for example, forum.example.com) then other cookies for the rest of the site may also be sent by the client, so make sure you take that into account when writing the rules.

Headers

One part of the request that we shouldn't be overlooking is the request headers sent by the client. Like the cookie check, rules to check the request headers can be placed before the individual checks for each action since the request headers will be similar no matter what type of request is sent.

Using our HTTP debugging proxy, we can look at the typical headers sent by the web browser when requesting pages from the forum. The following screenshot shows what headers we can expect to see:

Headers

Using a simple regular expression we can make sure that only headers that we have approved are allowed in requests:

SecRule REQUEST_HEADERS_NAMES "!^(Accept|Referer|Accept-Language|Content-Type|Content-Length|Cookie|User-Agent|Accept-Encoding|Host|Connection|Pragma|If-Modified-Since|If-None-Match)$" "deny"

If any header other than one defined in the list above is sent by a client then the request is denied. As before, the next step is to check each header to make sure it only contains characters we approve of:

# Header check
SecRule REQUEST_HEADERS_NAMES "!^(Accept|Referer|Accept-Language|Content-Type|Content-Length|Cookie|User-Agent|Accept-Encoding|Host|Connection|Pragma|If-Modified-Since|If-None-Match)$" 
"deny,msg:'Unknown request header'"
SecRule REQUEST_HEADERS:Accept "!^[-ws*/,.]+$" 
"deny, msg:'Bad Accept header'"
SecRule REQUEST_HEADERS:Referer "!^[-ws*/:^.?=~;]+$" 
"deny,msg:'Bad Referer header'"
SecRule REQUEST_HEADERS:Accept-Language "!^[-w*/]+$" 
"deny,msg:'Bad Accept-Language header'"
SecRule REQUEST_HEADERS:User-Agent "!^[-ws*/:;.,()=]+$" 
"deny,msg:'Bad User-Agent header'"
SecRule REQUEST_HEADERS:Content-Type "!^[-ws*/:;.,()=]+$" 
"deny,msg:'Bad Content-Type header'"
SecRule REQUEST_HEADERS:Content-Length "!^[d]{1,20}$" 
"deny,msg:'Bad Content-Length header'"
SecRule REQUEST_HEADERS:Accept-Encoding "!^[-ws*/:;.,()]+$" 
"deny,msg:'Bad Accept-Encoding header'"
SecRule REQUEST_HEADERS:Host "!^[w.]+$" 
"deny,msg:'Bad Host header'"
SecRule REQUEST_HEADERS:Connection "!^[-w]+$" 
"deny,msg:'Bad Connection header'"
SecRule REQUEST_HEADERS:Pragma "!^[-w*/]+$" 
"deny,msg:'Bad Pragma header'"
SecRule REQUEST_HEADERS:If-Modified-Since "!^[-w*/]+$" 
"deny,msg:'Bad If-Modified-Since header'"
SecRule REQUEST_HEADERS:If-None-Match "!^[-w*/]+$" 
"deny,msg:'Bad If-None-Match header'"
SecRule REQUEST_HEADERS:Cookie "!^[-ws=*/;]+$" 
"deny,msg:'Bad Cookie header'"

Note that whether or not to implement a strict header check like this depends on the trade-off you are willing to make between security and allowing your site to be accessible by as many people as possible. As an example, users browsing your site from behind a proxy server will typically have a X-Forwarded-For header in the request since that is added by the proxy server. With the above whitelisting of header values that is a request that would be denied. A tradeoff is to check that those headers we do know about conform to a pre-defined syntax, as is done above, but to leave out the strict check against a list of allowed headers.

Securing the "Start New Topic" action

If we have followed the methodology presented earlier we should now have a list of user actions along with information about what gets sent to the server when each action is taken by the user. Now that we have written rules to secure the request headers and cookies, we need to do the same for each individual action.

Let's look at how to secure the YaBB post action which is used when a user wants to start a new topic. When a user accesses the URI for this action, he is presented with a form to create a new topic.

We know from our previous investigation that the URI sent by the web browser when the user clicks on the "Start new topic" button will be of the form /yabb/YaBB.pl?board=general;action=post;title=StartNewTopic. We thus have the following request arguments to take into consideration:

Argument

Description

Remarks

board

Name of the board in which to create a new thread

Should be a valid board name consisting only of characters such as letters and digits

action

Action to take (display topic, create new post, show member profile, and so on)

Should be post when creating a new post

title

Title of the new thread

Should contain only valid characters for a thread title&mdash;that is letters, digits, dashes, underscores, and so on

To see why we need to secure these arguments, let's take a look at what would happen if we modified the value of the board argument a bit, perhaps so that it consisted of two dots (which in most operating systems represent the parent directory of the current directory, and is the basis for directory traversal attacks).

This is a screenshot of the page we reach after the URI /yabb/YaBB.pl?board=..;action=post;title=StartNewTopic is requested:

Securing the "Start New Topic" action

We can see that YaBB has taken the dots we provided and tried to open the file named ./Board/...txt. This is a typical scenario in which a sensitive file could be included if the value for the board argument is crafted carefully by an attacker. In fact, one of the original security vulnerabilities in YaBB used this technique in conjunction with a null byte attack to remove the .txt extension from the filename, resulting in full access to any file on the file system.

Knowing the sort of problems unfiltered argument values can lead to, wouldn't it be much better if we said that the board argument can only contain letters, digits, underscores, and dashes? That would prevent any special characters from being used to try to gain access to private files. As an added precaution, let's limit the board argument to at the most 20 characters. The rule to achieve this looks as follows:

SecRule ARGS:board "!^[-_0-9a-zA-Z]{1,20}$" "deny"

This will block board names containing anything other than the characters defined within the brackets. Using this simple rule we have prevented virtually all attacks that could be caused by an attacker tweaking the board name to do malicious things.

It's important to note the beginning-of-line and end-of-line anchors used (caret and dollar sign). These ensure that the entire argument value must conform to the regular expression&mdash;had these not been used then just a portion of the value matching would have sufficed, which is obviously not what we want.

When trying out this rule, we unfortunately get an access denied error. A quick look at the debug log reveals the source of the problem: YaBB uses a semicolon as an argument separator, causing ModSecurity to misidentify the query string arguments. Adding the following to the configuration file solves the problem:

SecArgumentSeparator ;

Now that we've secured the board argument, we need to do the same for the action and title arguments. The final rules to secure the "create new post" action look like this:

# Rules for "start new topic" action
SecRule ARGS_NAMES "!@pm board action title" "deny" SecRule ARGS:board "!^[-_0-9a-zA-Z]{1,20}$" "deny"
SecRule ARGS:action "!^[a-zA-Z]{1,20}$" "deny"
SecRule ARGS:title "!^w+$" "deny"

The ruleset so far

Now that we have secured the request headers, cookies, and "Start new topic" action, this is what the ruleset looks like thus far:

<Location /yabb/>
# Cookie check
SecRule REQUEST_COOKIES_NAMES "!^Y2(Pass|Sess|User)-10491$" 
"deny,msg:'Bad cookie name'"
SecRule REQUEST_COOKIES:Y2Pass-10491 "!^[0-9a-zA-Z]+$" 
"deny,msg:'Bad password cookie value'"
SecRule REQUEST_COOKIES:Y2Sess-10491 "!^[0-9a-zA-Z]+$" 
"deny,msg:'Bad session cookie value'"
SecRule REQUEST_COOKIES:Y2User-10491 "!^[-_0-9a-zA-Z+.]+$" 
"deny,msg:'Bad user cookie value'"
# Header check
SecRule REQUEST_HEADERS_NAMES "!^(Accept|Referer|Accept-Language|Content-Type|Content-Length|Cookie|User-Agent|Accept-Encoding|Host|Connection|Pragma|If-Modified-Since|If-None-Match)$" 
"deny,msg:'Unknown request header'"
SecRule REQUEST_HEADERS:Accept "!^[-ws*/,.]+$" 
"deny, msg:'Bad Accept header'"
SecRule REQUEST_HEADERS:Referer "!^[-ws*/:^.?=~;]+$" 
"deny,msg:'Bad Referer header'"
SecRule REQUEST_HEADERS:Accept-Language "!^[-w*/]+$" 
"deny,msg:'Bad Accept-Language header'"
SecRule REQUEST_HEADERS:User-Agent "!^[-ws*/:;.,()=]+$" 
"deny,msg:'Bad User-Agent header'"
SecRule REQUEST_HEADERS:Content-Type "!^[-ws*/:;.,()=]+$" 
"deny,msg:'Bad Content-Type header'"
SecRule REQUEST_HEADERS:Content-Length "!^[d]{1,20}$" 
"deny,msg:'Bad Content-Length header'"
SecRule REQUEST_HEADERS:Accept-Encoding "!^[-ws*/:;.,()]+$" 
"deny,msg:'Bad Accept-Encoding header'"
SecRule REQUEST_HEADERS:Host "!^[w.]+$" 
"deny,msg:'Bad Host header'"
SecRule REQUEST_HEADERS:Connection "!^[-w]+$" 
"deny,msg:'Bad Connection header'"
SecRule REQUEST_HEADERS:Pragma "!^[-w*/]+$" 
"deny,msg:'Bad Pragma header'"
SecRule REQUEST_HEADERS:If-Modified-Since "!^[-w*/]+$" 
"deny,msg:'Bad If-Modified-Since header'"
SecRule REQUEST_HEADERS:If-None-Match "!^[-w*/]+$" 
"deny,msg:'Bad If-None-Match header'"
SecRule REQUEST_HEADERS:Cookie "!^[-ws=*/;]+$" 
"deny,msg:'Bad Cookie header'"
# Check for valid actions and jump to appropriate handler
SecRule ARGS:action "^post$" "pass,nolog,skipAfter:100"
# If we reach this then this is an unknown action and the request is denied
rulesetviewingSecAction "deny,msg:'Unknown action'"
SecMarker 100
# Rules for "display post form" action
SecRule ARGS_NAMES "!@pm board action title" 
"deny,msg:'Unknown request parameter found'"
SecRule ARGS:board "!^[-_0-9a-zA-Z]{1,20}$" 
"deny,msg:'Bad board argument value'"
SecRule ARGS:action "!^[a-zA-Z]{1,20}$" 
"deny,msg:'Bad action argument value'"
SecRule ARGS:title "!^w+$" 
"deny,msg:'Bad title argument value'"
SecAction "pass,skipAfter:9999"
# All checks completed - request is allowed through SecMarker 9999
</Location>

The basic skeleton for the ruleset is now in place, and the remaining work consists of repeating what we just did for the "Start new topic" action so that each action gets modeled with the appropriate rules.

The finished ruleset

This is the finished ruleset to protect the YaBB forum using a positive security model:

<Location /yabb/> # Cookie check SecRule REQUEST_COOKIES_NAMES "!^Y2(Pass|Sess|User)-10491$"  "deny,msg:'Bad cookie name'" SecRule REQUEST_COOKIES:Y2Pass-10491 "!^[0-9a-zA-Z]+$"  "deny,msg:'Bad password cookie value'" SecRule REQUEST_COOKIES:Y2Sess-10491 "!^[0-9a-zA-Z]+$"  "deny,msg:'Bad session cookie value'" SecRule REQUEST_COOKIES:Y2User-10491 "!^[-_0-9a-zA-Z+.]+$"  "deny,msg:'Bad user cookie value'"
# Header check SecRule REQUEST_HEADERS_NAMES "!^(Accept|Referer|Accept-Language|Content-Type|Content-Length|Cookie|User-Agent|Accept-Encoding|Host|Connection|Pragma|If-Modified-Since|If-None-Match)$"  "deny,msg:'Unknown request header'" SecRule REQUEST_HEADERS:Accept "!^[-ws*/,.]+$"  "deny, msg:'Bad Accept header'" SecRule REQUEST_HEADERS:Referer "!^[-ws*/:^.?=~;]+$"  "deny,msg:'Bad Referer header'" SecRule REQUEST_HEADERS:Accept-Language "!^[-w*/]+$"  "deny,msg:'Bad Accept-Language header'" SecRule REQUEST_HEADERS:User-Agent "!^[-ws*/:;.,()=]+$"  "deny,msg:'Bad User-Agent header'" SecRule REQUEST_HEADERS:Content-Type "!^[-ws*/:;.,()=]+$"  "deny,msg:'Bad Content-Type header'" SecRule REQUEST_HEADERS:Content-Length "!^[d]{1,20}$"  "deny,msg:'Bad Content-Length header'" SecRule REQUEST_HEADERS:Accept-Encoding "!^[-ws*/:;.,()]+$"  "deny,msg:'Bad Accept-Encoding header'" SecRule REQUEST_HEADERS:Host "!^[w.]+$"  "deny,msg:'Bad Host header'" SecRule REQUEST_HEADERS:Connection "!^[-w]+$"  "deny,msg:'Bad Connection header'" SecRule REQUEST_HEADERS:Pragma "!^[-w*/]+$"  "deny,msg:'Bad Pragma header'" SecRule REQUEST_HEADERS:If-Modified-Since "!^[-w*/]+$"  "deny,msg:'Bad If-Modified-Since header'" SecRule REQUEST_HEADERS:If-None-Match "!^[-w*/]+$"  "deny,msg:'Bad If-None-Match header'" SecRule REQUEST_HEADERS:Cookie "!^[-ws=*/;]+$"  "deny,msg:'Bad Cookie header'" # Display board index SecRule REQUEST_URI "^/yabb/(YaBB.pl)?$" "pass,nolog,skipAfter:9999" # Check for valid actions and jump to appropriate handler SecRule ARGS:action "^post$" "pass,nolog,skipAfter:100" SecRule ARGS:action "^search$" "pass,nolog,skipAfter:101" SecRule ARGS:action "^login$" "pass,nolog,skipAfter:102" SecRule ARGS:action "^login2$" "pass,nolog,skipAfter:103" SecRule ARGS:board "w+" "pass,nolog,skipAfter:105" SecRule ARGS:action "^search2$" "pass,nolog,skipAfter:106" SecRule ARGS:action "^logout$" "pass,nolog,skipAfter:107" SecRule ARGS:action "^help$" "pass,nolog,skipAfter:108" SecRule ARGS:action "^mycenter$" "pass,nolog,skipAfter:109" SecRule ARGS:action "^profileCheck$" "pass,nolog,skipAfter:110" SecRule ARGS:action "^profileCheck2$" "pass,nolog,skipAfter:111" SecRule ARGS:action "^myprofile" "pass,nolog,skipAfter:112" SecRule ARGS:action "^post2$" "pass,nolog,skipAfter:113"
SecRule ARGS:action "^register$" "pass,nolog,skipAfter:114" SecRule ARGS:action "^register2$" "pass,nolog,skipAfter:115" SecRule ARGS:num "d+" "pass,nolog,skipAfter:104" # If we reach this then this is an unknown action and the request is denied SecAction "deny,msg:'Unknown action'" SecMarker 100 # Rules for "display post form" action SecRule ARGS_NAMES "!@pm board action title"  "deny,msg:'Unknown request parameter found'" SecRule ARGS:board "!^[-_0-9a-zA-Z]{1,20}$"  "deny,msg:'Bad board argument value'" SecRule ARGS:action "!^[a-zA-Z]{1,20}$"  "deny,msg:'Bad action argument value'" SecRule ARGS:title "!^w+$"  "deny,msg:'Bad title argument value'" SecAction "pass,skipAfter:9999" SecMarker 101 # Rules for "display search form" action SecRule ARGS_NAMES "!@pm action"  "deny,msg:'Unknown request parameter found'" SecAction "pass,skipAfter:9999" SecMarker 102 # Rules for "display login form" action SecRule ARGS_NAMES "!@pm action sesredir"  "deny,msg:'Unknown request parameter found'" SecRule ARGS:sesredir "![w]+"  "deny,msg:'Bad sesredir value'" SecAction "pass,skipAfter:9999" SecMarker 103 # Rules for "perform login" action SecRule ARGS_GET_NAMES "!@pm action"  "deny,msg:'Unknown request parameter found'" SecRule ARGS_POST_NAMES "!@pm sredir username password cookielength formsession"  "deny,msg:'Unknown request parameter found'" SecRule ARGS:sredir "![w~]+"  "deny,msg:'Bad sredir argument'" SecRule ARGS:username "!w+"  "deny,msg:'Bad username'" SecRule ARGS:passwrd "![-sw!@#$%^&*()+|`~=:;'",./?[]{}]+"  "deny,msg:'Bad password'" SecRule ARGS:cookielength "!d+"  "deny,msg:'Bad cookielength'"
rulesetfinished viewSecRule ARGS:formsession "!^[0-9A-Fa-f]+$"  "deny,msg:'Bad formsession'" SecRule REQUEST_METHOD "!^POST$"  "deny,msg:'Incorrect request method'" SecAction "pass,skipAfter:9999" SecMarker 104 # Rules for "display topic" action SecRule REQUEST_METHOD "!^GET$"  "deny,msg:'Incorrect request method'" SecRule ARGS_GET_NAMES "!@pm num"  "deny,msg:'Unknown request parameter found'" SecAction "pass,skipAfter:9999" SecMarker 105 # Handle "display board" action SecRule ARGS_GET_NAMES "!@pm board"  "deny,msg:'Invalid argument found'" SecRule ARGS:board "!^[wd]{1,64}$"  "deny,msg:'Invalid board name'" SecAction "pass,skipAfter:9999" SecMarker 106 # Handle "perform search" action SecRule REQUEST_METHOD "!^POST$"  "deny,msg:'Incorrect request method'" SecRule ARGS_GET_NAMES "!@pm action"  "deny,msg:'Unknown request parameter found'" SecRule ARGS_POST_NAMES "!@pm search searchtype userspectext userspec userkind searchboards srchAll subfield msgfield age numberreturned submit formsession"  "deny,msg:'Unknown request parameter found'" SecRule ARGS:search "!^[-sw_+.]+(&|$)"  "deny,msg:'Bad search parameter'" SecAction "pass,skipAfter:9999" SecMarker 107 # Handle logout SecRule REQUEST_METHOD "!^GET$"  "deny,msg:'Incorrect request method'" SecRule ARGS_NAMES "!@pm action"  "deny,msg:'Unknown request parameter found'" SecAction "pass,skipAfter:9999" SecMarker 108 # Handle "help" action SecRule REQUEST_METHOD "!^GET$"  "deny,msg:'Incorrect request method'" SecRule ARGS_NAMES "!@pm action" 
rulesetfinished view"deny,msg:'Unknown request parameter found'" SecAction "pass,skipAfter:9999" SecMarker 109 # Handle "user center" action SecRule REQUEST_METHOD "!^GET$"  "deny,msg:'Incorrect request method'" SecRule ARGS_NAMES "!@pm action"  "deny,msg:'Unknown request parameter found'" SecAction "pass,skipAfter:9999" SecMarker 110 # Handle "profile check" action SecRule REQUEST_METHOD "!^GET$"  "deny,msg:'Incorrect request method'" SecRule ARGS_NAMES "!@pm action page username"  "deny,msg:'Unknown request parameter found'" SecAction "pass,skipAfter:9999" SecMarker 111 # Handle "profile check 2" action SecRule REQUEST_METHOD "!^POST$"  "deny,msg:'Incorrect request method'" SecRule ARGS_NAMES "!@pm action page username redir passwrd formsession"  "deny,msg:'Unknown request parameter found'" SecAction "skipAfter:9999" SecMarker 112 # Handle "display profile" action SecRule REQUEST_METHOD "!^GET$" deny SecRule ARGS_NAMES "!@pm action username sid"  "deny,msg:'Unknown request parameter found'" SecAction "pass,skipAfter:9999" SecMarker 113 # Handle "perform post" action SecRule REQUEST_METHOD "!^POST$"  "deny,msg:'Incorrect request method'" SecRule REQUEST_BODY "!^[-sw.:;~@_"]"  "deny,msg:'Invalid characters in request body'" SecRule ARGS_GET_NAMES "!@pm board action num"  "deny,msg:'Unknown request parameter found'" SecAction "pass,skipAfter:9999"
rulesetfinished viewSecMarker 114 # Handle "show registration page" action SecRule REQUEST_METHOD "!^GET$"  "deny,msg:'Incorrect request method'" SecAction "pass,skipAfter:9999" SecMarker 115 # Handle "perform new user registration" action SecRule REQUEST_METHOD "!^POST$"  "deny,msg:'Incorrect request method'" SecRule ARGS_GET_NAMES "!@pm action"  "deny,msg:'Unknown request parameter found'" SecRule ARGS_POST_NAMES "!@pm regusername language regrealname email hideemail passwrd1 passwrd2 regagree formsession"  "deny,msg:'Unknown request parameter found'" SecRule ARGS:regusername "![ws]+"  "deny,msg:'Bad regusername parameter'" SecRule ARGS:language "![w]+"  "deny,msg:'Bad language parameter'" SecRule ARGS:regrealname "![w]+"  "deny,msg:'Bad regerealname parameter'" SecRule ARGS:email "![-w.@_]+"  "deny,msg:'Bad email parameter'" SecRule ARGS:hideemail "!^(0|1)"  "deny,msg:'Bad hideemail parameter'" SecRule ARGS:passwrd1|ARGS:passwrd2 "![-sw!@#$%^&*()+|`~=:;'",./?[]{}]+"  "deny,msg:'Bad password parameter'" SecRule ARGS:regagree "!^(0|1)"  "deny,msg:'Bad regagree parameter'" SecRule ARGS:formsession "![0-9A-Fa-f]+"  "deny,msg:'Bad formsession parameter'" SecAction "pass,skipAfter:9999" # All checks completed - request is allowed through SecMarker 9999
</Location>

Alternative approaches

In this chapter we implemented the ruleset by manual analysis of the user actions and by carefully breaking down each request to see exactly what should be allowed and denied. We could also have used the graphical tool Remo, as seen in the previous chapter, to create the ruleset in a more user-friendly way.

Another alternative is to use the tool ModProfiler, made available by Breach Security, to automatically analyze known-good traffic for the web application and use that knowledge to create a positive security ruleset with minimal effort. ModProfiler is still in ongoing development&mdash;take a look at http://www.modsecurity.org/projects/modprofiler/ for the latest release.

Keeping everything up to date

Now that the positive security model is in place and everything is working as expected, it's important to keep the ruleset up to date. Any changes to the web application should be scrutinized to make sure the model doesn't break the web application. This is of course easier if the web application is something that is developed in-house as opposed to a third-party application where you may not be sure exactly which changes have been made to a new release.

In both scenarios it would, as mentioned earlier, be beneficial to have a test set of requests (both requests that should be allowed through and ones that should be blocked) and run a scripted test to verify that everything is working as it should after a new version of the web application has been installed.

Summary

In this chapter we looked at how to implement a positive security model using a four-step process. We learned about the pros and cons for this sort of security model and how to assess whether the model is suitable for a particular web application. We then went on to implement the positive security model for the forum software YaBB.

We saw how to analyze user actions to find out exactly what should be allowed, and we learned how to use SecMarker in conjunction with the skipAfter directive to control the execution path for the rules. Putting all this together, we ended up with a ruleset implementing the security model. Finally, we learned about some alternative approaches that could have been used in developing the ruleset and the importance of keeping the model up to date so that the web application doesn't stop working when new releases of it are installed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.109.21