Network Security Tools: Writing, Hacking, and Modifying Security Tools
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 |