Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use threads;
  5. use threads::shared;
  6.  
  7. use Socket;
  8. use Getopt::Std;
  9. use Net::IP;
  10. use LWP::UserAgent;
  11.  
  12. # See if String::Compare is installed
  13. my $compare_available = 0;
  14. eval
  15. {
  16.   require String::Compare;
  17.   String::Compare->import();
  18. };
  19. unless($@)
  20. {
  21.     $compare_available = 1;
  22. }
  23.  
  24. # Track how long we're running
  25. my $starttime = time();
  26.  
  27. # Share the search range and page queue so the threads know what to do
  28. my ($ip_queue, %page_queue, %header_queue, $fetching, $checking, $next_ip) :shared;
  29. $fetching = 0;
  30. $checking = 0;
  31.  
  32. # Get command line options
  33. my ($search_range, $search_url, $search_string, $omit_string, $http_timeout, $threads_retrieve, $threads_check, $verbose) = getparams();
  34.  
  35. # Set our first and last IP addresses to search
  36. $next_ip = unpack 'N', inet_aton($search_range->ip());
  37. $ip_queue = $search_range->size()->numify();
  38.  
  39. # Split the search URL out into a protocol, hostname, and path so we can mix and match the pieces later
  40. $search_url =~ m/^(\w+)\:\/\/(.+?)\/(.*)$/ or die('Could not extract URL components');
  41. my ($search_protocol, $search_hostname, $search_path) = ($1, $2, $3);
  42. print "Protocol: $search_protocol; Hostname: $search_hostname; Path: $search_path\n" if ($verbose > 0);
  43.  
  44. # If we're not string matching set up a variable to store the page we are comparing against and populate it with the contents of the original URL
  45. my $search_page;
  46. unless ($search_string) {
  47.         ($search_page) = retrieve_page($search_url) or die ('Could not retrieve search page');
  48. }
  49.  
  50. # Spin up retriever threads
  51. print "Starting $threads_retrieve retrieve threads\n" if ($verbose > 0);
  52. for (my $threadcount = 0; $threadcount < $threads_retrieve; $threadcount++) {
  53.         my $thread = threads->create('start_retrieve');
  54. }
  55.  
  56. # Spin up check threads
  57. print "Starting $threads_check check threads\n" if ($verbose > 0);
  58. for (my $threadcount = 0; $threadcount < $threads_check; $threadcount++) {
  59.         my $thread = threads->create('start_check');
  60. }
  61.  
  62. # Wait for threads to do their thing
  63. while (my @threads = threads->list()) {
  64.         my @joinable = threads->list(threads::joinable);
  65.     if (@joinable) {
  66.             foreach my $thread (@joinable) {
  67.                     $thread->join();
  68.             }
  69.     } else {
  70.             print "IPs in fetch queue: ". $ip_queue ."; Pages in check queue: ". keys(%page_queue) ."\n" if ($verbose > 0);
  71.         sleep(1);
  72.     }
  73. }
  74.  
  75. # And we're done
  76. my $runtime = time() - $starttime;
  77. print "Ran in $runtime seconds\n" if ($verbose > 0);
  78. exit;
  79.  
  80. sub start_retrieve {
  81.         $fetching++;
  82.         my $retrieve_done = 0;
  83.         RETRIEVE: until ($retrieve_done) {
  84.                 # Pull an IP address off the list in a locked block
  85.                 my $ip;
  86.                 {
  87.                         lock($next_ip);
  88.                         lock($ip_queue);
  89.                         if ($ip_queue) {
  90.                 $ip_queue--;
  91.                                 $ip = inet_ntoa(pack 'N', $next_ip);
  92.                 $next_ip++;
  93.                         } else {
  94.                                 $retrieve_done = 1;
  95.                                 last RETRIEVE;
  96.                         }
  97.                 }
  98.  
  99.                 # Retrieve the page
  100.                 my ($page, $headers) = retrieve_page($search_protocol ."://". $ip ."/". $search_path, $search_hostname);
  101.  
  102.                 # Lock the queue and add the page if we got one, skip it if we failed
  103.                 if ($page) {
  104.                         lock(%page_queue);
  105.                         $page_queue{$ip} = $page;
  106.                         $header_queue{$ip} = $headers;
  107.                         print "$ip retrieved and added to queue\n" if ($verbose > 1);
  108.                 } else {
  109.                         print "$ip retrieve failed\n" if ($verbose > 1);
  110.                 }
  111.         }
  112.         $fetching--;
  113. }
  114.  
  115. sub start_check {
  116.         $checking++;
  117.         my $check_done = 0;
  118.         my $wait = 0;
  119.         CHECK: until ($check_done) {
  120.                 my ($ip, $page, $headers);
  121.  
  122.                 # Lock the queue and check if there's anything we can grab from it
  123.                 {
  124.                         lock(%page_queue);
  125.                         my @keys = keys(%page_queue);
  126.                         if (@keys) {
  127.                                 $ip = shift(@keys);
  128.                                 $page = $page_queue{$ip};
  129.                                 $headers = $header_queue{$ip};
  130.                                 delete($page_queue{$ip});
  131.                                 delete($header_queue{$ip});
  132.                         } elsif ($ip_queue || $fetching) {
  133.                                 $wait = 1;
  134.                         } else {
  135.                                 $check_done = 1;
  136.                                 last CHECK;
  137.                         }
  138.                 }
  139.  
  140.                 # If there's nothing in the queue we should wait
  141.                 if ($wait) {
  142.                         sleep(1);
  143.                         $wait = 0;
  144.                 }
  145.  
  146.                 # If we're not waiting we process the page we got
  147.                 else {
  148.                         # Search string style if we have one
  149.                         if ($search_string) {
  150.                                 if ($page =~ /$search_string/) {
  151.                     if ($omit_string && $headers =~ /$omit_string/) {
  152.                                             print "$ip matched string but omitted based on headers\n" if ($verbose > 1);
  153.                     } else {
  154.                                             print "$ip matched string\n";
  155.                     }
  156.                                 } else {
  157.                                         print "$ip did not match string\n" if ($verbose > 1);
  158.                                 }
  159.                         }
  160.                         # Percent match if we don't
  161.                         else {
  162.                                 my $percent = compare_pages($search_page, $page);
  163.                                 print "$ip is a $percent\% match\n";
  164.                         }
  165.                 }
  166.         }
  167.         $checking--;
  168. }
  169.  
  170.  
  171. # Print help message, automatically called by getopts if '--help' is passed on the command line
  172. sub HELP_MESSAGE {
  173.         print <<EOF
  174. noclouds.pl command line options:
  175. -u: url to match against
  176. -i: ip range to search
  177. -s: string to search for instead of percent matching, mandatory if String::Compare is not installed
  178. -o: optionally omit results where the header matches the provided string, only works if used in conjunction with -s
  179. -t: optional HTTP connection timeout
  180. -r: optional retrieve thread count (default 128)
  181. -c: optional check thread count (default 4)
  182. -v: optional verbose mode (Current useful values 0-2)
  183. EOF
  184.         ;
  185. exit;
  186. }
  187.  
  188. # Get and return our command line parameters
  189. sub getparams {
  190.         my %opts;
  191.         getopts('u:i:s:o:t:r:c:v:', \%opts);
  192.  
  193.         # Optional verbose mode to increase the amount of noise we generate while scanning
  194.         my $verbose = $opts{'v'};
  195.         unless ($verbose) {
  196.                 $verbose = 0;
  197.         }
  198.  
  199.         # Target URL
  200.         my $search_url = $opts{'u'} or die ("No target URL provided");
  201.  
  202.         # IP range to search
  203.         my $search_range = new Net::IP ($opts{'i'}) or die ('No valid search range supplied');
  204.  
  205.         # String to search for in target pages, this is optional
  206.         my $search_string = $opts{'s'};
  207.  
  208.         # String to search for in target headers and omit results, this is optional
  209.         my $omit_string = $opts{'o'};
  210.  
  211.         # Optional timeout for HTTP requests
  212.         my $http_timeout = $opts{'t'};
  213.  
  214.         # Optional retreive thread count
  215.         my $threads_retrieve = $opts{'r'};
  216.         unless ($threads_retrieve) {
  217.                 $threads_retrieve = 128;
  218.         }
  219.  
  220.         # Optional check thread count
  221.         my $threads_check = $opts{'c'};
  222.         unless ($threads_check) {
  223.                 $threads_check = 4;
  224.         }
  225.  
  226.         # Set our search parameters
  227.         if ($search_string) {
  228.                 print "Searching for $search_string\n" if ($verbose > 0);
  229.         } else {
  230.         if ($compare_available) {
  231.                     print "Checking percentage match against $search_url\n" if ($verbose > 0);
  232.         } else {
  233.             die('String::Compare is not available, provide a search string with -s');
  234.         }
  235.         }
  236.  
  237.         return ($search_range, $search_url, $search_string, $omit_string, $http_timeout, $threads_retrieve, $threads_check, $verbose);
  238. }
  239.  
  240. # Retrieve a page and return its contents based on a URL, optionally set the hostname header
  241. sub retrieve_page {
  242.         my ($url, $hostname) = @_;
  243.  
  244.         # Create a user agent object, spoofing Safari 6.0.4 user agent headers so we can be stealthy
  245.         my $ua = LWP::UserAgent->new;
  246.         $ua->agent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.29.13 (KHTML, like Gecko) Version/6.0.4 Safari/536.29.13');
  247.         if ($http_timeout) {
  248.                 $ua->timeout($http_timeout);
  249.         }
  250.         my $req = HTTP::Request->new(GET => $url);
  251.  
  252.         # Set the Host header if requested to deal with name based virtual hosting
  253.         if ($hostname) {
  254.                 $req->header(Host => $hostname);
  255.         }
  256.  
  257.         # Get the page
  258.         my $res = $ua->request($req);
  259.  
  260.         # Mark our result whether or not we succeeded in the request and add the page content if we have it
  261.         if ($res->is_success()) {
  262.         my $headers = join('; ', $res->header_field_names());
  263.                 return($res->content(), $headers);
  264.         } else {
  265.                 return(undef(), undef());
  266.         }
  267. }
  268.  
  269. # Compare a master copy of a page to a page object and return percentage of how similar they are
  270. sub compare_pages {
  271.         my ($original_page, $target_page) = @_;
  272.         if ($target_page) {
  273.                 return(compare($original_page, $target_page) * 100);
  274.         } else {
  275.                 return(undef());
  276.         }
  277. }