Introducing Remo - An Easy Way to Secure an Insecure Online Application with ModSecurity

Say you have a nasty application on your Apache webserver that has been installed by some jerks from the marketing department and you can neither patch nor remove it. Maybe it is a problem of ressources, a lack of know-how, a lack of source-code, or possibly even due to political reasons. Consequently, you need to protect it without touching it. There is ModSecurity, but they say this is only for experts. A straightforward alternative is Remo, a graphical rule editor for ModSecurity that comes with a whitelist approach. It has all you need to lock down the application.

For the sake of this tutorial we will use a really simple test application. One that is so ugly, not even your marketing department would want to have it around. But hey, it's only a tutorial.

  echo '<pre>';
  echo $_POST['command'];
  echo '<hr />';
  system($_POST['command'], $retval); # needed for 'ls' commands
  echo '</pre>';
  echo '<hr />Return value: ' . $retval;
  echo '<hr />';
  echo '<form action="ls.php" method="post">';
  echo '<input type="text" name="command" value="' . $_POST['command'] . '" />';
  echo '<button>submit</button>';
  echo '</form>';
ls.php: A little script you better remove from your site while you can.

The direct execution of any shell command posted in form of the parameter command makes ls.php an unwelcome guest on just about any webserver - or a perfect test case to try out Remo in order to protect you from the dangers of this script. So what is Remo then? Over at you will learn, that it is meant as a simple way to configure ModSecurity without becoming a security wizard first. The second catch is, that Remo writes whitelist rules, while most people use ModSecurity with a blacklisting approach. Blacklisting means you tell ModSecurity everything about all know attacks. Whitelisting does it the other way round: It makes sure your application will only get the input, you really want it to get. So that ls.php will only get ls commands as its command parameter and furthermore you will make sure no dirty tricks are possible using backticks and semicolon within the parameter. To put it short: Remo helps doing input validation on the server without touching the application.



You can give Remo a shot in the online demo on the homepage. That one works nicely when you want to try Remo without installing it first. The installation is not very difficult either, even without distribution packages ready to date. Make sure you have ruby, irb and ruby-sqlite3 bindings installed before downloading.

I have done the following on my Ubuntu test box; it should work for Debian, too:

aptitude install ruby irb libsqlite3-ruby1.8

As for Remo, you can either download a release or take a snapshot of the latest subversion tree. The Remo download page tells you which trees have passed all the unit tests. Let's say we download and install a release. Go to the download page and grab the 0.2.0 beta release. Unpack the tarball and fire up the WEBrick rails server. From the command line this boils down to:

tar xvzf remo-0.2.0.tar.gz
cd remo-0.2.0
ruby script/server
direct browser to http://localhost:3000/main/index

You should get something like this:

Starting Remo the first time

Remo as it looks after the installation.

We are a bit in a hurry here, as this tutorial is meant to be brief. So I will only show you the things necessary to get the test application application secured. Kind of a hands on guide. There is plenty of time for you to explore Remo on your own.


Securing the Application

Remove all the default requests in the window and create a new one.

A new request in Remo

Adding a new request to Remo.

Click on the click to edit text and enter the path of your application's path: /ls.php, then change the http method to POST. Open the details of the request and add a new post-parameter using the p* button next to the post-parameters. Click on the name (click-to-edit) and make it command. This is the parameter submitted by our example application ls.php.

To the right of the name is the value domain of the parameter. This is the central part of Remo. Here you define which values are acceptable for the parameter. Remember, this is whitelisting: we are defining exactly what is acceptable. There are a couple of predefined values, but we will have to go with Custom in this case. If you open the details of the command parameter, there is a field called Custom regex. This is where Remo looks for the regex when the value domain is Custom. I will use ls[0-9a-zA-Z-\x20_.]{0,64}. This is the command ls followed by a optional group of characters consisting of numbers, letters, hyphen, the space character underscore and dot. No semicolon, no slashes, no backticks, no German umlauts, no unicode specialities: This is your server and you want no bullshit going on here.


Protection for ls.php is on its way with the definition of the POST  parameter command.

A request not meeting this condition will fail. Apache's default behaviour of a failing request will be to return a http status code 501: Method Not Implemented. But let's be nice: As Status code (failed domain match) you enter 302 for http redirect and beneath at Location (failed domain match) you enter the URL of your company like So somebody failing to provide the correct parameter will be redirected to your company's website. Actually you can make the parameter mandatory by clicking on optional and below you can set a special behaviour for failed mandatory condition, too. But in our case, the parameter need not be of mandatory character.

If you are wondering how this regular expression parameter will be turned into a ModSecurity rule, then take a look the following graphic:


This is how a Remo parameter configuration is translated to a ModSecurity rule.

It describes how your Remo parameter is being translated into a ModSecurity rule and what will happen during the evaluation. It boils down to this: Every http request parameter is matched against the value domain defined in Remo and if it does not fit, then the whole request is denied access.


Putting it to Practice

With this being said, let's generate the ModSecurity ruleset. Click on the generate button in the toolbar and save the file you will get. This is the rulefile in the ModSecurity rule language. Take this file and save it in a place where Apache can find it. It is worth a look by the way to get an understanding of how Remo uses ModSecurity. Then you have to make sure that ModSecurity is enabled on your webserver. Remo generates ModSecurity 2 rulesets, as there has been a major update and extension to the configuration language between 1.9 and 2.0 and Remo needs these new features. In case your distribution does not provide ModSecurity packages, you can get most of them from Otherwise you can always compile it yourself. It's not too much hassle.

It is best to include your rulefile into to your apache configuration as follows:

<IfModule mod_security2.c>
	Include /etc/apache2/rulefile.conf

Then reload your apache and give it a shot (I am using the command line tool curl in the demonstration, but your browser will be fine):

curl --data "command=ls -l" http://localhost/ls.php

-rw-r--r--  1 sam    staff     353 Feb  7 13:05 index.html
-rw-r--r--  1 sam    staff     248 Apr 23 05:35 ls.php

So this still works. How about an abuse of ls.php?

curl --data "command=ls -l; cat /etc/passwd" http://localhost/ls.php

<title>302 Found</title>
<p>The document has moved <a href="">here</a>.</p>
<address>Apache/2.2.3 (Debian) PHP/5.2.0-8 Server at railsmachine Port 80</address>

Got the attacker! There is an audit-log defined in the config file. Per default this is at /var/log/apache2/modsec_audit.log. It will read like the following in our case:

Message: Access denied with redirection to using status 302 (phase 2).
Match of "rx ^(ls\\x20[0-9a-zA-Z-_.]{0,64})$" against "ARGS:command" required. [id "1"]
[msg "Postparameter command failed validity check. Value domain: Custom."] [severity "ERROR"]
Action: Intercepted (phase 2)

Before going to production mode, make sure that this log is not growing too big. In fact you may want to disable too much logging when you are done with the setup. The logfile is configured by Remo to do full request logging for debugging purposes by default. This includes everything, even passwords. When are done with the testing, you should set the SecAuditLogParts parameter in the rulefile to a saner value. ABHZ is an option. That way it logs only http headers and the ModSecurity audit summary.

So this has been a quick overview of Remo. There is more to be found on the Remo website. has great documentation that tells you everything about the configuration language. Furthermore, there is a core-ruleset that will help you in situations, where you do not have the time to model your application in Remo. The core-ruleset is a always a good start as it will catch 90% of the attacks with very little effort.

Remo is far from being mature, but it is ready to use and given a helpful userbase it could develop into an interesting tool to secure web applications. Your application need not be as simple as the one used in this tutorial. Bigger applications with dozens of URLs and lots of parameters work nicely too.

Share this page:

Suggested articles

2 Comment(s)

Add comment


By: amir


You said that direct browser to http://localhost:3000/main/index ... because I am new to Linux so please tell me in baby step how to direct server....

By: Anonymous

I get this error, any help?


MissingSourceFile in MainController#index

no such file to load -- sqlite3

RAILS_ROOT: script/../config/..

Application Trace | Framework Trace | Full Trace
/usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
/usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
vendor/rails/activesupport/lib/active_support/dependencies.rb:495:in `require'
vendor/rails/activesupport/lib/active_support/dependencies.rb:342:in `new_constants_in'
vendor/rails/activesupport/lib/active_support/dependencies.rb:495:in `require'
vendor/rails/activesupport/lib/active_support/core_ext/kernel/requires.rb:7:in `require_library_or_gem'
vendor/rails/activesupport/lib/active_support/core_ext/kernel/reporting.rb:11:in `silence_warnings'
vendor/rails/activesupport/lib/active_support/core_ext/kernel/requires.rb:5:in `require_library_or_gem'
vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb:14:in `sqlite3_connection'
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb:262:in `send'
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb:262:in `connection_without_query_cache='
vendor/rails/activerecord/lib/active_record/query_cache.rb:54:in `connection='
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb:230:in `retrieve_connection'
vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb:78:in `connection'
vendor/rails/activerecord/lib/active_record/base.rb:763:in `columns'
vendor/rails/activerecord/lib/active_record/base.rb:782:in `content_columns'
This error occurred while loading the following files:


Parameters: None

Show session dump


Headers: {"Cache-Control"=>"no-cache", "cookie"=>[]}