| 1 | #VERSION,2.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 qw/$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 | hooks => { |
|---|
| 31 | start => { |
|---|
| 32 | method => \&nikto_auth_load, |
|---|
| 33 | weight => 1, |
|---|
| 34 | }, |
|---|
| 35 | postfetch => { |
|---|
| 36 | method => \&nikto_auth, |
|---|
| 37 | weight => 19, |
|---|
| 38 | cond => '$result->{whisker}->{code} eq 401', |
|---|
| 39 | }, |
|---|
| 40 | prefetch => { |
|---|
| 41 | method => \&nikto_auth_pre, |
|---|
| 42 | weight => 19, |
|---|
| 43 | }, |
|---|
| 44 | }, |
|---|
| 45 | copyright => "2010 CIRT Inc" |
|---|
| 46 | }; |
|---|
| 47 | |
|---|
| 48 | return $id; |
|---|
| 49 | } |
|---|
| 50 | |
|---|
| 51 | sub nikto_auth_load { |
|---|
| 52 | |
|---|
| 53 | # Load up the database as soon as we can |
|---|
| 54 | |
|---|
| 55 | $REALMS = init_db("db_realms"); |
|---|
| 56 | %REALMSMATCHED = (); |
|---|
| 57 | |
|---|
| 58 | if (defined $CLI{'hostauth'}) { |
|---|
| 59 | my @x = split(/:/, $CLI{'hostauth'}); |
|---|
| 60 | |
|---|
| 61 | my $HOSTAUTH = { |
|---|
| 62 | nikto_id => "700500", |
|---|
| 63 | realm => (defined $x[2]) ? $x[2] : '@ANY', |
|---|
| 64 | password => $x[1], |
|---|
| 65 | id => $x[0], |
|---|
| 66 | message => "Credentials provided by CLI.", |
|---|
| 67 | }; |
|---|
| 68 | unshift(@{$REALMS}, $HOSTAUTH); |
|---|
| 69 | } |
|---|
| 70 | } |
|---|
| 71 | |
|---|
| 72 | sub nikto_auth_pre { |
|---|
| 73 | my ($mark, $parameters, $request, $result) = @_; |
|---|
| 74 | |
|---|
| 75 | # If we know the realm then don't bother guessing it |
|---|
| 76 | # See whether we've already guessed it |
|---|
| 77 | |
|---|
| 78 | my ($uridir) = $request->{'whisker'}->{'uri'}; |
|---|
| 79 | $uridir =~ s#/[^/]*$#/#g; |
|---|
| 80 | |
|---|
| 81 | if (exists $REALMSMATCHED{ $mark->{'hostname'} }{$uridir}) { |
|---|
| 82 | |
|---|
| 83 | # Just set up the auth and return the valid result |
|---|
| 84 | LW2::auth_set($REALMSMATCHED{ $mark->{'hostname'} }{$uridir}{'authtype'}, |
|---|
| 85 | $request, |
|---|
| 86 | $REALMSMATCHED{ $mark->{'hostname'} }{$uridir}{'id'}, |
|---|
| 87 | $REALMSMATCHED{ $mark->{'hostname'} }{$uridir}{'password'} |
|---|
| 88 | ); |
|---|
| 89 | |
|---|
| 90 | # Patch to fix short reads |
|---|
| 91 | $request->{'whisker'}->{'allow_short_reads'} = 1; |
|---|
| 92 | LW2::http_fixup_request($request); |
|---|
| 93 | } |
|---|
| 94 | return $request, $result; |
|---|
| 95 | } |
|---|
| 96 | |
|---|
| 97 | sub nikto_auth { |
|---|
| 98 | my ($mark, $parameters, $request, $result) = @_; |
|---|
| 99 | my ($authtype) = 'basic'; |
|---|
| 100 | my ($body) = $result->{'whisker'}->{'data'}; |
|---|
| 101 | my ($uri) = $result->{'whisker'}->{'uri'}; |
|---|
| 102 | my ($method) = $result->{'whisker'}->{'method'} || "GET"; |
|---|
| 103 | my ($realm, $save_auth); |
|---|
| 104 | |
|---|
| 105 | unless (defined $result->{'www-authenticate'}) { |
|---|
| 106 | nprint("+ ERROR: No authentication header defined: $uri"); |
|---|
| 107 | return $request, $result; |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | # Save to revert |
|---|
| 111 | $save_auth = $result{'www-authenticate'}; |
|---|
| 112 | |
|---|
| 113 | # Split up www-authenticate to realm and method |
|---|
| 114 | my @authenticate = split(/ /, $result->{'www-authenticate'}); |
|---|
| 115 | if ($#authenticate == 0) { # Only one parameter: realm |
|---|
| 116 | $realm = $authenticate[0]; |
|---|
| 117 | if ($realm =~ /^ntlm/i) { |
|---|
| 118 | $realm = ""; |
|---|
| 119 | $authtype = $authenticate[0]; |
|---|
| 120 | } |
|---|
| 121 | } |
|---|
| 122 | else { |
|---|
| 123 | $authtype = $authenticate[0]; |
|---|
| 124 | $realm = $authenticate[1]; |
|---|
| 125 | $realm =~ s/^realm=//; |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | nprint("+ $uri - Requires Authentication for realm '$realm'") if $OUTPUT{'show_auth'}; |
|---|
| 129 | |
|---|
| 130 | # Now we have this we can try guessing the password |
|---|
| 131 | foreach my $entry (@{$REALMS}) { |
|---|
| 132 | unless ($realm =~ /$entry->{'realm'}/i || $entry->{realm} eq '@ANY') { next; } |
|---|
| 133 | |
|---|
| 134 | if ($result->{'www-authenticate'} =~ /^ntlm/i) { |
|---|
| 135 | $authtype = 'ntlm'; |
|---|
| 136 | } |
|---|
| 137 | |
|---|
| 138 | # Set up LW hash |
|---|
| 139 | LW2::auth_set($authtype, $request, $entry->{'id'}, $entry->{'password'}); |
|---|
| 140 | |
|---|
| 141 | # Patch to fix short reads |
|---|
| 142 | $request->{'whisker'}->{'allow_short_reads'} = 1; |
|---|
| 143 | LW2::http_fixup_request($request); |
|---|
| 144 | |
|---|
| 145 | # pause if needed |
|---|
| 146 | if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; } |
|---|
| 147 | |
|---|
| 148 | LW2::http_do_request_timeout($request, $result); # test auth |
|---|
| 149 | $NIKTO{'totalrequests'}++; |
|---|
| 150 | dump_var("Auth Request", $request); |
|---|
| 151 | dump_var("Auth Response", $result); |
|---|
| 152 | |
|---|
| 153 | if ($result{'www-authenticate'} =~ /^ntlm/i) { |
|---|
| 154 | |
|---|
| 155 | # Deal with ntlm |
|---|
| 156 | my @ntlm_x = split(/ /, $result{'www-authenticate'}); |
|---|
| 157 | if ($#ntlm_x == 1) { |
|---|
| 158 | LW2::http_do_request_timeout(\%request, \%result); |
|---|
| 159 | $NIKTO{'totalrequests'}++; |
|---|
| 160 | } |
|---|
| 161 | } |
|---|
| 162 | my $uridir = $request->{'whisker'}->{'uri'}; |
|---|
| 163 | $uridir =~ s#/[^/]*$#/#g; |
|---|
| 164 | |
|---|
| 165 | if ($result->{'www-authenticate'} eq '' |
|---|
| 166 | && !defined $result->{'whisker'}->{'error'}) { |
|---|
| 167 | my $message = |
|---|
| 168 | "Default account found for '$realm' at $uridir ($request->{'whisker'}->{'uri'}) (ID '$entry->{'id'}', PW '$entry->{'password'}'). $entry->{message}"; |
|---|
| 169 | if ($entry->{'id'} eq '' && $entry->{'password'} eq '') { |
|---|
| 170 | $message = |
|---|
| 171 | "Blank credentials found at $uridir ($request{whisker}->{uri}), $entry->{'realm'}: $entry->{'msg'}"; |
|---|
| 172 | } |
|---|
| 173 | unless ($entry->{'checked'} == 1) { |
|---|
| 174 | add_vulnerability($mark, $message, $entry->{tid}, 0, "GET", $uridir, $result); |
|---|
| 175 | $entry->{checked} = 1; |
|---|
| 176 | } |
|---|
| 177 | |
|---|
| 178 | # Finally repeat the check |
|---|
| 179 | LW2::http_do_request_timeout($request, $result); # test auth |
|---|
| 180 | $NIKTO{'totalrequests'}++; |
|---|
| 181 | |
|---|
| 182 | # Set up so we don't have to repeat in future |
|---|
| 183 | # / isn't a valid entry in a hash - more stupid perl |
|---|
| 184 | |
|---|
| 185 | $REALMSMATCHED{ $mark->{hostname} }{$uridir}{'id'} = $entry->{'id'}; |
|---|
| 186 | $REALMSMATCHED{ $mark->{hostname} }{$uridir}{'password'} = $entry->{'password'}; |
|---|
| 187 | $REALMSMATCHED{ $mark->{hostname} }{$uridir}{'authtype'} = $authtype; |
|---|
| 188 | |
|---|
| 189 | # and leave |
|---|
| 190 | last; |
|---|
| 191 | } |
|---|
| 192 | else { |
|---|
| 193 | $result->{'www-authenticate'} = $save_auth; |
|---|
| 194 | } |
|---|
| 195 | } |
|---|
| 196 | LW2::auth_unset(\%request); |
|---|
| 197 | |
|---|
| 198 | return $request, $result; |
|---|
| 199 | } |
|---|
| 200 | |
|---|
| 201 | 1; |
|---|