| 1 | #VERSION,1.01 |
|---|
| 2 | # $Id$ |
|---|
| 3 | ############################################################################### |
|---|
| 4 | # Copyright (C) 2004 CIRT, Inc. |
|---|
| 5 | # |
|---|
| 6 | # This program is free software; you can redistribute it and/or |
|---|
| 7 | # modify it under the terms of the GNU General Public License |
|---|
| 8 | # as published by the Free Software Foundation; version 2 |
|---|
| 9 | # of the License only. |
|---|
| 10 | # |
|---|
| 11 | # This program is distributed in the hope that it will be useful, |
|---|
| 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 14 | # GNU General Public License for more details. |
|---|
| 15 | # |
|---|
| 16 | # You should have received a copy of the GNU General Public License |
|---|
| 17 | # along with this program; if not, write to the Free Software |
|---|
| 18 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 19 | ############################################################################### |
|---|
| 20 | # PURPOSE: |
|---|
| 21 | # Search content for known bad strings |
|---|
| 22 | ############################################################################### |
|---|
| 23 | use vars /$REALMS %REALMSMATCHED/; |
|---|
| 24 | |
|---|
| 25 | sub nikto_auth_init { |
|---|
| 26 | my $id = { name => 'auth', |
|---|
| 27 | full_name => 'Guess authentication', |
|---|
| 28 | author => 'Sullo/Deity', |
|---|
| 29 | description => 'Attempt to guess authentication realms', |
|---|
| 30 | recon_method => \&nikto_auth_load, |
|---|
| 31 | recon_weight => 1, |
|---|
| 32 | postfetch_cond => '$result->{whisker}->{code} eq 401', |
|---|
| 33 | postfetch_method => \&nikto_auth, |
|---|
| 34 | postfetch_weight => 19, |
|---|
| 35 | prefetch_method => \&nikto_auth_pre, |
|---|
| 36 | prefetch_weight => 19, |
|---|
| 37 | copyright => "2010 CIRT Inc" |
|---|
| 38 | }; |
|---|
| 39 | |
|---|
| 40 | return $id; |
|---|
| 41 | } |
|---|
| 42 | |
|---|
| 43 | sub nikto_auth_load { |
|---|
| 44 | # Load up the database as soon as we can |
|---|
| 45 | |
|---|
| 46 | $REALMS=init_db("db_realms"); |
|---|
| 47 | %REALMSMATCHED = (); |
|---|
| 48 | } |
|---|
| 49 | |
|---|
| 50 | sub nikto_auth_pre { |
|---|
| 51 | my ($mark, $parameters, $request, $result) = @_; |
|---|
| 52 | # If we know the realm then don't bother guessing it |
|---|
| 53 | # See whether we've already guessed it |
|---|
| 54 | |
|---|
| 55 | my ($uridir) = $request->{'whisker'}->{'uri'}; |
|---|
| 56 | $uridir =~ s#/[^/]*$#/#g; |
|---|
| 57 | |
|---|
| 58 | if (exists $REALMSMATCHED{$mark->{'hostname'}}{$uridir}) { |
|---|
| 59 | # Just set up the auth and return the valid result |
|---|
| 60 | LW2::auth_set($REALMSMATCHED{$mark->{'hostname'}}{$uridir}{'authtype'}, $request, $REALMSMATCHED{$mark->{'hostname'}}{$uridir}{'id'}, $REALMSMATCHED{$mark->{'hostname'}}{$uridir}{'password'}); |
|---|
| 61 | # Patch to fix short reads |
|---|
| 62 | $request->{'whisker'}->{'allow_short_reads'} = 1; |
|---|
| 63 | LW2::http_fixup_request($request); |
|---|
| 64 | } |
|---|
| 65 | return $request, $result; |
|---|
| 66 | } |
|---|
| 67 | |
|---|
| 68 | sub nikto_auth { |
|---|
| 69 | my ($mark, $parameters, $request, $result) = @_; |
|---|
| 70 | my ($authtype) = 'basic'; |
|---|
| 71 | my ($body) = $result->{'whisker'}->{'data'}; |
|---|
| 72 | my ($uri) = $result->{'whisker'}->{'uri'}; |
|---|
| 73 | my ($method) = $result->{'whisker'}->{'method'} || "GET"; |
|---|
| 74 | my ($realm, $save_auth); |
|---|
| 75 | |
|---|
| 76 | unless (defined $result->{'www-authenticate'}) { |
|---|
| 77 | nprint("+ ERROR: No authentication header defined: $uri"); |
|---|
| 78 | return $request, $result; |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | # Save to revert |
|---|
| 82 | $save_auth = $result{'www-authenticate'}; |
|---|
| 83 | |
|---|
| 84 | # Split up www-authenticate to realm and method |
|---|
| 85 | my @authenticate = split(/ /, $result->{'www-authenticate'}); |
|---|
| 86 | if ($#authenticate == 0) { # Only one parameter: realm |
|---|
| 87 | $realm = $authenticate[0]; |
|---|
| 88 | if ($realm =~ /^ntlm/i) { |
|---|
| 89 | $realm = ""; |
|---|
| 90 | $authtype = $authenticate[0]; |
|---|
| 91 | } |
|---|
| 92 | } |
|---|
| 93 | else { |
|---|
| 94 | $authtype = $authenticate[0]; |
|---|
| 95 | $realm = $authenticate[1]; |
|---|
| 96 | $realm =~ s/^realm=//; |
|---|
| 97 | } |
|---|
| 98 | |
|---|
| 99 | nprint("+ $uri - Requires Authentication for realm '$realm'") if $CLI{'display'} =~ /4/; |
|---|
| 100 | |
|---|
| 101 | # Now we have this we can try guessing the password |
|---|
| 102 | foreach my $entry (@{$REALMS}) { |
|---|
| 103 | unless ($realm =~ /$entry->{'realm'}/i || $entry->{realm} eq '@ANY') { next; } |
|---|
| 104 | |
|---|
| 105 | if ($result->{'www-authenticate'} =~ /^ntlm/i) { |
|---|
| 106 | $authtype='ntlm'; |
|---|
| 107 | } |
|---|
| 108 | # Set up LW hash |
|---|
| 109 | LW2::auth_set($authtype, $request, $entry->{'id'}, $entry->{'password'}); |
|---|
| 110 | # Patch to fix short reads |
|---|
| 111 | $request->{'whisker'}->{'allow_short_reads'} = 1; |
|---|
| 112 | LW2::http_fixup_request($request); |
|---|
| 113 | # pause if needed |
|---|
| 114 | if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; } |
|---|
| 115 | |
|---|
| 116 | LW2::http_do_request_timeout($request, $result); # test auth |
|---|
| 117 | $NIKTO{'totalrequests'}++; |
|---|
| 118 | dump_var("Auth Request", $request); |
|---|
| 119 | dump_var("Auth Response", $result); |
|---|
| 120 | |
|---|
| 121 | if ($result{'www-authenticate'} =~ /^ntlm/i) { |
|---|
| 122 | # Deal with ntlm |
|---|
| 123 | my @ntlm_x = split(/ /, $result{'www-authenticate'}); |
|---|
| 124 | if ($#ntlm_x == 1) { |
|---|
| 125 | LW2::http_do_request_timeout(\%request, \%result); |
|---|
| 126 | $NIKTO{'totalrequests'}++; |
|---|
| 127 | } |
|---|
| 128 | } |
|---|
| 129 | my $uridir = $request->{'whisker'}->{'uri'}; |
|---|
| 130 | $uridir =~ s#/[^/]*$#/#g; |
|---|
| 131 | |
|---|
| 132 | if ($result->{'www-authenticate'} eq '' |
|---|
| 133 | && !defined $result->{'whisker'}->{'error'}) { |
|---|
| 134 | |
|---|
| 135 | my $message="Default account found for '$realm' at $uridir ($request->{'whisker'}->{'uri'}) (ID '$entry->{'id'}', PW '$entry->{'password'}'). $entry->{message}"; |
|---|
| 136 | if ($entry->{'id'} eq '' && $entry->{'password'} eq '') { |
|---|
| 137 | $message="Blank credentials found at $uridir ($request{whisker}->{uri}), $entry->{'realm'}: $entry->{'msg'}" |
|---|
| 138 | } |
|---|
| 139 | unless ( $entry->{'checked'} == 1) { |
|---|
| 140 | add_vulnerability( |
|---|
| 141 | $mark, |
|---|
| 142 | $message, |
|---|
| 143 | $entry->{tid}, |
|---|
| 144 | 0, |
|---|
| 145 | "GET", |
|---|
| 146 | $uridir, |
|---|
| 147 | $result |
|---|
| 148 | ); |
|---|
| 149 | $entry->{checked} = 1; |
|---|
| 150 | } |
|---|
| 151 | |
|---|
| 152 | # Finally repeat the check |
|---|
| 153 | LW2::http_do_request_timeout($request, $result); # test auth |
|---|
| 154 | $NIKTO{'totalrequests'}++; |
|---|
| 155 | |
|---|
| 156 | # Set up so we don't have to repeat in future |
|---|
| 157 | # / isn't a valid entry in a hash - more stupid perl |
|---|
| 158 | |
|---|
| 159 | $REALMSMATCHED{$mark->{hostname}}{$uridir}{'id'}=$entry->{'id'}; |
|---|
| 160 | $REALMSMATCHED{$mark->{hostname}}{$uridir}{'password'}=$entry->{'password'}; |
|---|
| 161 | $REALMSMATCHED{$mark->{hostname}}{$uridir}{'authtype'}=$authtype; |
|---|
| 162 | |
|---|
| 163 | # and leave |
|---|
| 164 | last; |
|---|
| 165 | } |
|---|
| 166 | else { |
|---|
| 167 | $result->{'www-authenticate'} = $save_auth; |
|---|
| 168 | } |
|---|
| 169 | } |
|---|
| 170 | LW2::auth_unset(\%request); |
|---|
| 171 | |
|---|
| 172 | return $request, $result; |
|---|
| 173 | } |
|---|
| 174 | |
|---|
| 175 | 1; |
|---|