#!/usr/bin/perl # # THC Web Vulnerability Scanner v3.8 # # $Id: thc.pl, v3.8 2010/20/06 16:15:56 jpp Exp $ # # # Description: THC is an effective open source web vulnerability # scanner for detecting security flaws and bugs in a # remote network. It also crawls for e-mail addresses # and sensitive files / directories. # # Author: Jean Pascal Pereira # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., 59 Temple # Place, Suite 330, Boston, MA 02111-1307 USA use strict; use threads; use warnings; use LWP::UserAgent; use Getopt::Std; use IO::Socket; my %params=(); getopts("hpgqsrfmok:u:a:c:e:",\%params); if (defined $params{h}) { print "\n [ THC WEB VULNERABILITY SCANNER ]\n\n"; print " -u -- defines the target url (required)\n"; print " -c -- defines the cookie for our http requests\n"; print " -a -- defines the user agent for our http request\n"; print " -p -- defines a http proxy for our http requests\n"; print " -e -- custom sql error string (usually not necessary)\n"; print " -s -- silent mode\n"; print " -q -- disable sound alerts\n"; print " -r -- do not save the vulnerability report\n"; print " -g -- enable google dorks (powerful, but get a cookie first!)\n"; print " -o -- enable file backup scan\n"; print " -f -- scan for sensitive directories / files (could be noisy)\n"; print " -m -- scans the target for e-mail addresses\n"; print " -k -- string from charcode encoder/decoder for web attacks\r\n\n"; exit; } elsif (defined $params{k}) { my $str = $params{k}; if($str =~ m/^-?[\.|\d|,]*\Z/) { my @strval = split (/,/, $str); foreach(@strval) { print chr($_); } } else { foreach(0..length($str)-1) { print ord(substr($str,$_, 1)); print "," if ($_ != length($str)-1); } } exit; } elsif (!defined $params{u}) { print "\n [ THC WEB VULNERABILITY SCANNER ]\n\n"; print "\a" if(!defined $params{q}); print " ERROR: missing argument(s), use the -h parameter for more information\r\n\n"; exit; } my $verbose = 1; my $scan_uri = $params{u}; my $emlscan; my $user_agent; my $http_cookie; my $http_proxy; my $sound_alerts; my $no_reporting = 0; my $sqli_error_str = $params{e} || "sql error"; my $xss_error_str = ""; my $uriregexp = "]*href=.((http:\/\/)?((www\.)?[-_a-zA-Z0-9\/.~?&,=;]*)).[^>]*>(.*)<\/a>"; $verbose = 0 if(defined $params{s}); $emlscan = 1 if(defined $params{m}); $user_agent = $params{a} if(defined $params{a}); $http_cookie = $params{c} if(defined $params{c}); $http_proxy = $params{p} if(defined $params{p}); if(defined $params{q}) { $sound_alerts = 0; } else { $sound_alerts = 1; } if(defined $params{r}) { $no_reporting = 1; } $scan_uri =~ s/http:\/\///i; my $host; my $banner; my @data_container; my @crawled; my @allowed_filetypes = ("php", "php3", "php4", "php5", "jsp", "html", "htm", "asp", "aspx", "cgi"); my $output_file = "THC_REPORT_".time().".html"; my $active = 0; sub start_crawling { $host = shift; $| = 1; if (!$no_reporting) { open(OUT,">".$output_file); print OUT "\n"; print OUT "\n\nTHC Scan Report\n"; print OUT "

THC SCAN REPORT

\n
\nTarget URI: "; print OUT "
".$host."\n
\n
\n
\n"; } filescan($host) if (defined $params{f}); if (!$verbose) { $banner = threads->new(\&statusbanner); $banner->detach; } if (defined $params{g}) { google_attack("site:".$scan_uri." php asc"); google_attack("site:".$scan_uri." php desc"); google_attack("site:".$scan_uri." php id"); google_attack("site:".$scan_uri." php order"); google_attack("site:".$scan_uri." php cat"); google_attack("site:".$scan_uri." php list"); google_attack("site:".$scan_uri." asp asc"); google_attack("site:".$scan_uri." asp desc"); google_attack("site:".$scan_uri." asp id"); google_attack("site:".$scan_uri." asp order"); google_attack("site:".$scan_uri." asp cat"); google_attack("site:".$scan_uri." asp list"); google_attack("site:".$scan_uri." jsp asc"); google_attack("site:".$scan_uri." jsp desc"); google_attack("site:".$scan_uri." jsp id"); google_attack("site:".$scan_uri." jsp order"); google_attack("site:".$scan_uri." jsp cat"); google_attack("site:".$scan_uri." jsp list"); } crawl($host); } sub relative { my $uri = shift; if($uri !~ m/^https?:\/\//) { $uri = $host."/".$uri; } return $uri; } sub get { my $uri = shift; my $agent = $user_agent || "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.1)"; my $ua = LWP::UserAgent->new; $ua->timeout(10); $ua->agent($agent); $ua->default_header("Cookie" => $http_cookie) if (defined $http_cookie); $ua->proxy(["http"], "http://".$http_proxy."/") if (defined $http_proxy); my $response = $ua->get($uri); if ($response->is_success) { return $response->content; } else { return -1; } } sub check { my $target_uri = shift; my $errormessage = shift; my $http_response; my @default_errors = ( "you have an error in your sql syntax", "database error", "server error in", "oracle error", "mysql_fetch_array", "mysql_fetch_assoc", "mysql_fetch_num", "mysql_fetch_row", "mysql_fetch_object", "syntax error", "empty delimiter", "unclosed quotation mark", "SQLServer JDBC Driver", "ORA-01756" ); $target_uri = relative($target_uri); $http_response = get($target_uri); if ((defined $params{o}) && ($target_uri =~ /(.*)\?/)) { if(get($1.".bak") ne "-1") { print "\a" if ($sound_alerts); print "Possible backup file found: ".$1.".bak\n\n"; if (!$no_reporting) { print OUT ""; print OUT $1.".bak\n
\n"; } } elsif(get($1."~") ne "-1") { print "\a" if ($sound_alerts); print "Possible backup file found: ".$1."~\n\n"; if (!$no_reporting) { print OUT ""; print OUT $1."~\n
\n"; } } } if($http_response) { if ($http_response =~ m/$errormessage/ig) { if($verbose) { print "\a" if ($sound_alerts); print "\nPossible vulnerability: ".$target_uri."\n\n"; } if (!$no_reporting) { print OUT ""; $target_uri =~ s//\>/g; print OUT $target_uri."\n
\n"; } } foreach(@default_errors) { if ($http_response =~ m/$_/ig) { if($verbose) { print "\n\aPossible vulnerability: ".$target_uri."\n\n"; } if(!$no_reporting) { print OUT " "x4; print OUT "Possible vulnerability: "; $target_uri =~ s//\>/g; print OUT $target_uri."\n
\n"; } } } } } sub attack { my $uri = shift; my $mode = shift; my $response = shift; my $attack_string; if($mode == 1) { $attack_string = shift || "%27"; } elsif($mode == 2) { $attack_string = shift || ""; } my @split_uri = split (/\&/,$uri); my $current_uri; while(scalar @split_uri) { foreach(@split_uri) { $current_uri .= $_."&"; } pop @split_uri; $current_uri = substr($current_uri,0,-1).$attack_string; $current_uri =~ s/\&/\&/g; if($verbose) { print "Attacking: ".$current_uri."\n"; } if($response) { check($current_uri, $response); } else { check($current_uri); } $current_uri = undef; } } sub google_attack { my $query = shift; $query =~ s/ /+/g; my $resp = get("http://www.google.com/search?q=".$query."&as_qdr=all&num=100&filter=0"); my @content = split(/<\/h3>/,$resp); foreach(@content) { if($_ =~ m/
  • ]*href="(.*?)" \b[^>]*>(.*?)<\/a>/) { attack($1, 1, $sqli_error_str); attack($1, 2, $xss_error_str); mailscan($_) if (defined ($emlscan)); } } } sub filescan { my $host = shift; my @sensitive_stuff = ("/admin/", "/phpinfo.php", "/server-status", "/old.htaccess", "/htaccess.old", "/icons/", "/cgi-bin/", "/cgi.cgi/", "/webcgi/", "/cgi-914/", "/cgi-915/", "/bin/", "/cgi/", "/log/", "/logs/", "/ows-bin/", "/cgi-sys/", "/cgi-local/", "/htbin/", "/scripts/", "/cgi-exe/", "/~root/", "/robots.txt", "/test/", "/test.php", "/test.asp", "/info.php", "/help/", "/old/", "/index.php.bak", "/config/", "/conf/", "/config.inc", "/phpmyadmin/", "/global.inc", "/inc/config.php", "/version.php", "/version.html", "/version.htm", "/README", "/control/", "/backup/", "/private/", "/stats/", "/traffic/", "/counter.cgi", "/sqladmin/", "/sql/", "/WS_FTP.LOG", "/Thumbs.db", "/settings/", "/autologon.html?10514", "/administrator/", "/backup.rar", "/backup.zip", "/backup.tar", "/backup.gz", "/backup.tar.gz", "/backup.bz2", "/logs.tar", "/logs.rar", "/logs.gz", "/logs.tar.gz", "/logs.bz2", "/logs.zip", "/access.log", "/guardian.log", "/services/", "/service/", "/sshome/", "/mail/", "/webmail/", "/roundcube/", "/cgi-perl/", "/php.ini", "/sqldump.sql", "/dump.sql", "/backup.sql", "/structure.sql", "/logicworks.ini", "/ip.txt", "/ips.txt", "/ip.log", "/ips.log", "/smssend.php", "/sms.php", "/servlet/", "/_vti_pvt/", "/webalizer/", "/doc/", "/usage/", "/setup/", "/setup.php", "/temp/", "/mysqldumper/", "/turbodbadmin/", "/tbda/", "/Mem/", "/data/", "/code/", "/boot/", "/pma/", "/samples/", "/upload/", "/publisher/", "/adserv/", "/incoming/", "/demo/", "/examples/", "/cert/", "/install.php", "/install/", "/install.php.bak", "/install.php.old", "/CHANGELOG.txt", "/foo/", "/foo.php", "/foo.txt", "/bla/", "/blah/", "/bleh/", "/default/", "/pdf/", "/_._.html"); foreach(@sensitive_stuff) { print "Checking: ".$_."\n"; if((get($host.$_) ne "-1") && (get($host.$_) !~ m/404/gi) && (get($host.$_) !~ m/not found/gi)) { print "\a" if ($sound_alerts); print "Possible sensitive stuff: ".$host.$_."\n"; if (!$no_reporting) { print OUT "Possible sensitive stuff: ".$host.$_."\n
    \n"; } } } } sub mailscan { my $uri = shift; $uri = get($uri); if($uri) { my @uri = split(/\n/, $uri); foreach(@uri) { if (get_mail($_)) { print "\a" if ($sound_alerts); print "Mail address detected: ".get_mail($_)."\n"; if (!$no_reporting) { print OUT "Mail address detected: ".get_mail($_); } } } } } sub get_mail { my $content = shift; my $mailadd; my @regexpr; my @domains = ("ac","ad","ae","aero","af","ag","ai","al","am","an","ao","aq","ar","arpa","as","asia","at","au", "aw","ax","az","ba","bb","bd","be","bf","bg","bh","bi","biz","bj","bm","bn","bo","br","bs","bt", "bv","bw","by","bz","ca","cat","cc","cd","cf","cg","ch","ci","ck","cl","cm","cn","co","com","coop", "cr","cu","cv","cx","cy","cz","de","dj","dk","dm","do","dz","ec","edu","ee","eg","er","es","et", "eu","fi","fj","fk","fm","fo","fr","ga","gb","gd","ge","gf","gg","gh","gi","gl","gm","gn","gov", "gp","gq","gr","gs","gt","gu","gw","gy","hk","hm","hn","hr","ht","hu","id","ie","il","im","in", "info","int","io","iq","ir","is","it","je","jm","jo","jobs","jp","ke","kg","kh","ki","km","kn", "kp","kr","kw","ky","kz","la","lb","lc","li","lk","lr","ls","lt","lu","lv","ly","ma","mc","md", "me","mg","mh","mil","mk","ml","mm","mn","mo","mobi","mp","mq","mr","ms","mt","mu","museum","mv", "mw","mx","my","mz","na","name","nc","ne","net","nf","ng","ni","nl","no","np","nr","nu","nz","om", "org","pa","pe","pf","pg","ph","pk","pl","pm","pn","pr","pro","ps","pt","pw","py","qa","re","ro", "rs","ru","rw","sa","sb","sc","sd","se","sg","sh","si","sj","sk","sl","sm","sn","so","sr","st","su", "sv","sy","sz","tc","td","tel","tf","tg","th","tj","tk","tl","tm","tn","to","tp","tr","travel","tt", "tv","tw","tz","ua","ug","uk","us","uy","uz","va","vc","ve","vg","vi","vn","vu","wf","ws","ye","yt", "yu","za","zm","zw"); my $domains = join("|", @domains); # my black magic mail collection regular expressions ;o) # they should "bypass" / parse the most mail obfuscations $regexpr[0] = "(\\w+.[a-zA-Z0-9]*\\s?_at_\\s?\\w+.[a-zA-Z0-9]*\(\.|[dot]|\(dot\))($domains))"; $regexpr[1] = "(\\w+.[a-zA-Z0-9]*\\@\\w+.[a-zA-Z0-9]*\(\.|[dot]|\(dot\))($domains))"; $regexpr[2] = "(\\w+.[a-zA-Z0-9]*\\s?\\[at\\]\\s?\\w+.[a-zA-Z0-9]*\(\.|[dot]|\(dot\))($domains))"; $regexpr[3] = "(\\w+.[a-zA-Z0-9]*\\s?\\(at\\)\\s?\\w+.[a-zA-Z0-9]*\(\.|[dot]|\(dot\))($domains))"; foreach(@regexpr) { if($content =~ m/$_/i) { $mailadd = $1; # some filtering crap $mailadd =~ s/_at_/\@/g; $mailadd =~ s/\[at\]/\@/g; $mailadd =~ s/\(at\)/\@/g; $mailadd =~ s/lt;//g; $mailadd =~ s/\>//g; $mailadd =~ s/(.*)>//g; $mailadd =~ s/mailto://g; return $mailadd; } } } sub crawl { my $uri = shift; $uri = relative($uri); if (($uri) && ($uri =~ m/$host/i)) { $uri = get($uri); if($uri) { my @content = split(/\n/,$uri); foreach(@content) { if (($_ =~ m/$uriregexp/i) && (file_match($_, @allowed_filetypes) == 1)) { if ((array_search($1, @data_container)) == -1) { push @data_container, $1; } } } foreach(@data_container) { if ((array_search($_, @crawled)) == -1) { attack($_, 1, $sqli_error_str); attack($_, 2, $xss_error_str); mailscan($_) if (defined ($emlscan)); push @crawled, $_; crawl($_); } } } } } sub array_search { my $var = shift; my @array = @_; my $index = 0; while ($array[$index]) { my $v = $array[$index]; if ($v eq $var) { last; } $index++; } if ($index > $#array) { return -1; } return 1; } sub file_match { my $var = shift; my @array = @_; my $index = 0; while ($array[$index]) { my $v = $array[$index]; if ($var =~ m/\.$v/i) { last; } $index++; } if ($index > $#array) { return -1; } return 1; } sub delay { select(undef, undef, undef, 0.0999999); } sub statusbanner { $| = 1; $active = 1; print "[ ] Crawling... "; while($active) { print "\r["."|]"; delay; print "\r["."/]"; delay; print "\r["."".chr(196)."]"; delay; print "\r["."\\]"; delay; } } sub finish { if(!$no_reporting) { print OUT "
    \n
    \n
    \n"; print OUT "Generated by THC web vulnerability scanner - "; print OUT "www.secbiz.de\n"; print OUT "\n"; close(OUT); } if(!$verbose) { $| = 0; $active = 0; print "\r[#] Scan finished!"; print "\n[#] For futher information see \"".$output_file."\"." if (!$no_reporting); print "\n\a" if ($sound_alerts); print "\r\n\n"; } else { print "\a" if ($sound_alerts); print "\n\nScan finished! "; print "For futher information see \"".$output_file."\"." if (!$no_reporting); print "\n\r\n\n"; } } start_crawling("http://".$scan_uri); finish;