8.6. Complete Source Code
The rest of this chapter contains the complete source code for the
two scripts developed and outlined in this chapter.
8.6.1. simpleScanner.pl
Example 8-8 shows the full source code for the
simpleScanner.pl script.
Example 8-8. Code for simpleScanner.pl
#!/usr/bin/perl
use LWP::UserAgent;
use strict;
use Getopt::Std;
my %args;
getopts('c:o:v', \%args);
printReport("\n** Simple Web Application Scanner **\n");
if ($#ARGV < 1) {
die "\n$0 [-o <file>] [-c <cookie data>] [-v] inputfile
http://hostname\n\n-c: Use HTTP Cookie\n-o: Output File\n-v: Be Verbose\n";
}
# Open input file
open(IN, "< $ARGV[0]") or die"ERROR => Can't open file $ARGV[0].\n";
my @requestArray = <IN>;
my ($oRequest,$oResponse, $oStatus, %dirLog, %paramLog);
printReport("\n** Beginning Scan **\n\n");
# Loop through each of the input file requests
foreach $oRequest (@requestArray) {
# Remove line breaks and carriage returns
$oRequest =~ s/(\n|\r)//g;
# Only process GETs and POSTs
if ($oRequest =~ /^(GET|POST)/) {
# Check for request data
if ($oRequest =~ /\?/) {
# Issue the original request for reference purposes
($oStatus, $oResponse) = makeRequest($oRequest);
#Populate methodAndFile and reqData variables
my ($methodAndFile, $reqData) = split(/\?/, $oRequest, 2);
my @reqParams = split(/\&/, $reqData);
my $pLogEntry = $methodAndFile;
# Build parameter log entry
my $parameter;
foreach $parameter (@reqParams) {
my ($pName) = split("=", $parameter);
$pLogEntry .= "+".$pName;
}
$paramLog{$pLogEntry}++;
if ($paramLog{$pLogEntry} eq 1) {
# Loop to perform test on each parameter
for (my $i = 0; $i <= $#reqParams; $i++) {
my $testData;
# Loop to reassemble the request parameters
for (my $j = 0; $j <= $#reqParams; $j++) {
if ($j == $i) {
my ($varName, $varValue) = split("=",$reqParams[$j],2);
$testData .= $varName."="."---PLACEHOLDER---"."&";
} else {
$testData .= $reqParams[$j]."&";
}
}
chop($testData);
my $paramRequest = $methodAndFile."?".$testData;
## Perform input validation tests
my $sqlVuln = sqlTest($paramRequest);
my $xssVuln = xssTest($paramRequest);
} # End of loop for each request parameter
} # End if statement for unique parameter combos
} # Close if statement checking for request data
my $trash;
($trash, $oRequest, $trash) = split(/\ |\?/, $oRequest);
my @directories = split(/\//, $oRequest);
my @checkSlash = split(//, $oRequest);
my $totalDirs = $#directories;
# Start looping through each directory level
for (my $d = 0; $d <= $totalDirs; $d++) {
if ((($checkSlash[(-1)] ne "/") && ($d == 0)) || ($d != 0)) {
pop(@directories);
}
my $dirRequest = "GET ".join("/", @directories)."\/";
# Add directory log entry
$dirLog{$dirRequest}++;
if ($dirLog{$dirRequest} eq 1) {
my $dListVuln = dirList($dirRequest);
my $dPutVuln = dirPut($dirRequest);
} # End check for unique directory
} # End loop for each directory level
} # End check for GET or POST request
} # End loop on each input file entry
printReport("\n\n** Scan Complete **\n\n");
sub dirPut {
my ($putRequest, $putStatus, $putResults, $putVulnerable);
($putRequest) = @_;
# Format the test request to upload the file
$putRequest =~ s/^GET/PUT/;
$putRequest .= "uploadTest.txt?ThisIsATest";
# Make the request and get the response data
($putStatus, $putResults) = makeRequest($putRequest);
# Format the request to check for the new file
$putRequest =~ s/^PUT/GET/;
$putRequest =~ s/\?ThisIsATest//;
# Check for the uploaded file
($putStatus, $putResults) = makeRequest($putRequest);
if ($putResults =~ /ThisIsATest/) {
$putVulnerable = 1;
# If vulnerable, print something to the user
printReport("\n\nALERT: Writeable Directory Detected:\n=> $putRequest\n\n");
} else {
$putVulnerable = 0;
}
# Return the test results.
return $putVulnerable;
}
sub dirList {
my ($dirRequest, $dirStatus, $dirResults, $dirVulnerable);
($dirRequest) = @_;
# Make the request and get the response data
($dirStatus, $dirResults) = makeRequest($dirRequest);
# Check to see if it looks like a listing
if ($dirResults =~ /(<TITLE>Index of \/|(<h1>|<title>)Directory Listing For|<title>Directory of|\"\?N=D\"|\"\?S=A\"|\"\?M=A\"|\"\?D=A\"| - \/<\/title>|<dir>| - \/<\/H1><hr>|\[To Parent Directory\])/i) {
$dirVulnerable = 1;
# If vulnerable, print something to the user
printReport("\n\nALERT: Directory Listing Detected:\n=> $dirRequest\n\n");
} else {
$dirVulnerable = 0;
}
# Return the test results.
return $dirVulnerable;
}
sub xssTest {
my ($xssRequest, $xssStatus, $xssResults, $xssVulnerable);
($xssRequest) = @_;
# Replace the "---PLACEHOLDER---" string with our test string
$xssRequest =~ s/---PLACEHOLDER---/"><script>alert('Vulnerable');<\/script>/;
# Make the request and get the response data
($xssStatus, $xssResults) = makeRequest($xssRequest);
# Check to see if the output matches our vulnerability signature.
if ($xssResults =~ /"><script>alert\('Vulnerable'\);<\/script>/i) {
$xssVulnerable = 1;
# If vulnerable, print something to the user
printReport("\n\nALERT: Cross-Site Scripting Vulnerability Detected:\n=> $xssRequest\n\n");
} else {
$xssVulnerable = 0;
}
# Return the test results
return $xssVulnerable;
}
sub sqlTest {
my ($sqlRequest, $sqlStatus, $sqlResults, $sqlVulnerable);
($sqlRequest) = @_;
# Replace the "---PLACEHOLDER---" string with our test string
$sqlRequest =~ s/---PLACEHOLDER---/te'st/;
# Make the request and get the response data
($sqlStatus, $sqlResults) = makeRequest($sqlRequest);
# Check to see if the output matches our vulnerability signature.
my $sqlRegEx = qr /(OLE DB|SQL Server|Incorrect Syntax|ODBC Driver|ORA-|SQL command
not|Oracle Error Code|CFQUERY|MySQL|Sybase| DB2 |Pervasive|Microsoft Access|MySQL|
CLI Driver|The string constant beginning with|does not have an ending string
delimiter|JET Database Engine error)/i;
if (($sqlResults =~ $sqlRegEx) && ($oResponse !~ $sqlRegEx)) {
$sqlVulnerable = 1;
printReport("\n\nALERT: Database Error Message Detected:\n=>
$sqlRequest\n\n");
} else {
$sqlVulnerable = 0;
}
# Return the test result indicator
return $sqlVulnerable;
}
sub makeRequest {
my ($request, $lwp, $method, $uri, $data, $req, $status, $content);
($request)=@_;
if ($args{v}) {
printReport("Making Request: $request\n");
} else {
print ".";
}
# Setup LWP UserAgent
$lwp = LWP::UserAgent->new(env_proxy => 1,
keep_alive => 1,
timeout => 30,
);
# Method should always precede the request with a space
($method, $uri) = split(/ /, $request);
# PUTS and POSTS should have data appended to the request
if (($method eq "POST") || ($method eq "PUT")) {
($uri, $data) = split(/\?/, $uri);
}
# Append the URI to the hostname and setup the request
$req = new HTTP::Request $method => $ARGV[1].$uri;
# Add request content for POST and PUTS
if ($data) {
$req->content_type('application/x-www-form-urlencoded');
$req->content($data);
}
# If cookies are defined, add a COOKIE header
if ($args{c}) {
$req->header(Cookie => $args{c});
}
my $response = $lwp->request($req);
# Extract the HTTP status code and HTML content from the response
$status = $response->status_line;
$content = $response->content;
if ($status =~ /^400/) {
die "Error: Invalid URL or HostName\n\n";
}
return ($status, $content);
}
sub printReport {
my ($printData) = @_;
if ($args{o}) {
open(REPORT, ">>$args{o}") or die "ERROR => Can't write to file $args{o}\n";
print REPORT $printData;
close(REPORT);
}
print $printData;
}
8.6.2. parseLog.pl
Example 8-9 contains the full source for the
parseLog.pl script.
Example 8-9. Code for parseLog.pl
#!/usr/bin/perl
use strict;
if ($#ARGV < 0) {
die "Usage: $0 LogFile\n";
}
open(IN, "< $ARGV[0]") or die"ERROR: Can't open file $ARGV[0].\n";
# Change the input record separator to select entire log entries
$/ = "=" x 54;
my @logData = <IN>;
# Loop through each request and parse it
my ($request,$logEntry, @requests);
foreach $logEntry (@logData) {
# Create an array containing each line of the raw request
my @logEntryLines = split(/\n/, $logEntry);
# Create an array containing each element of the first request line
my @requestElements = split(/ /, $logEntryLines[1]);
# Only parse GET and POST requests
if ($requestElements[0] eq "GET" || $requestElements[0] eq "POST" ) {
if ($requestElements[0] eq "GET" ) {
print $requestElements[0]." ".$requestElements[1]."\n";
}
# POST request data is appended after the question mark
if ($requestElements[0] eq "POST" ) {
print $requestElements[0]." ".$requestElements[1]."?".$logEntryLines[-2]."\n";
}
} # End check for GET or POST
} # End loop for input file entries
|