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.
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 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:
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.
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.
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.
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.
Fiddler, and other web debugging proxies, give you access to a treasure trove of information about web requests, as seen in the following screenshot:
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.
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.
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.
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 |
|
|
Display list of topics in board |
|
|
Display topic |
|
|
Show reply to topic form |
|
|
Show new topic form |
|
|
Show search page |
|
|
Perform search |
|
|
Show user center |
|
|
Show login form |
|
|
Perform login |
|
|
Logout |
|
|
Show recent posts |
|
|
Show new user registration form |
|
|
Perform new user registration |
|
|
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.
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.
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:
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.
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:
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.
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 |
---|---|---|
|
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 to take (display topic, create new post, show member profile, and so on) |
Should be |
|
Title of the new thread |
Should contain only valid characters for a thread title—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:
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—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"
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.
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>
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—take a look at http://www.modsecurity.org/projects/modprofiler/ for the latest release.
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.
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.
18.224.109.21