| [215] | 1 | #VERSION,2.05 |
|---|
| [41] | 2 | # $Id$ |
|---|
| [26] | 3 | ############################################################################### |
|---|
| 4 | # Copyright (C) 2007 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 | ############################################################################### |
|---|
| [240] | 20 | # PURPOSE: |
|---|
| [26] | 21 | # General HTTP headers checks |
|---|
| 22 | ############################################################################### |
|---|
| [79] | 23 | sub nikto_headers_init |
|---|
| 24 | { |
|---|
| 25 | my $id = |
|---|
| 26 | { |
|---|
| 27 | name => "headers", |
|---|
| 28 | full_name => "HTTP Headers", |
|---|
| 29 | author => "Sullo", |
|---|
| 30 | description => "Performs various checks against the headers returned from a HTTP request.", |
|---|
| 31 | scan_method => \&nikto_headers, |
|---|
| 32 | copyright => "2008 CIRT Inc." |
|---|
| 33 | }; |
|---|
| 34 | return $id; |
|---|
| 35 | } |
|---|
| [26] | 36 | |
|---|
| 37 | sub nikto_headers |
|---|
| 38 | { |
|---|
| [130] | 39 | my ($mark)=@_; |
|---|
| [219] | 40 | my $dbarray = init_db("db_headers"); |
|---|
| [173] | 41 | my @interesting_headers = qw /microsoftofficewebserver ms-author-via dasl dav daap-server x-aspnet-version/; |
|---|
| [151] | 42 | my %headers; |
|---|
| [73] | 43 | # Standard headers, whisker is added to stop false positives |
|---|
| 44 | # Host Pragma |
|---|
| [26] | 45 | |
|---|
| [73] | 46 | ####################################################################### |
|---|
| 47 | # look for a powered-by header...could require a valid file, maybe not |
|---|
| 48 | my %xpb; |
|---|
| 49 | foreach my $f (qw/\/index.php \/junk999.php \/ \/index.php3 \/ \/junk999.php3 \/index.cfm \/junk999.cfm \/index.asp \/junk999.asp \/index.aspx \/junk988.aspx/ ) |
|---|
| 50 | { |
|---|
| [158] | 51 | (my $res, $content) = nfetch($mark,$f, "GET", "", \%headers); |
|---|
| [284] | 52 | if (defined $headers{'x-powered-by'}) { $xpb{ $headers{'x-powered-by'} } = 1; } |
|---|
| [73] | 53 | } |
|---|
| [26] | 54 | |
|---|
| [73] | 55 | foreach my $x (sort keys %xpb) |
|---|
| 56 | { |
|---|
| 57 | my $message; |
|---|
| [26] | 58 | |
|---|
| [73] | 59 | # push version to BUILDITEMS so it can be evaluated later |
|---|
| 60 | push(@BUILDITEMS, $x); |
|---|
| [26] | 61 | |
|---|
| [73] | 62 | $message .= " $x"; |
|---|
| [130] | 63 | add_vulnerability($mark,"Retrieved X-Powered-By header:$message", 999992, 0); |
|---|
| [73] | 64 | } |
|---|
| [158] | 65 | |
|---|
| 66 | ####################################################################### |
|---|
| 67 | # look for to see whether its vulnerable to the Translate: f |
|---|
| 68 | my %transheaders; |
|---|
| 69 | foreach my $f (qw/\/index.asp \/junk999.asp \/index.aspx \/junk988.aspx \/login.asp \/login.aspx/ ) |
|---|
| 70 | { |
|---|
| 71 | (my $res, $content) = nfetch($mark, $f , "GET", "", \%transheaders); |
|---|
| 72 | if ($res eq "200") |
|---|
| 73 | { |
|---|
| [284] | 74 | $transheaders{'Translate'}="f"; |
|---|
| [158] | 75 | ($res, $content) = nfetch($mark,$f . "\\", "GET", "", \%transheaders); |
|---|
| 76 | if ($res eq "200") |
|---|
| 77 | { |
|---|
| 78 | if ($content =~ /<asp:/ || $content =~ /<\/asp:/) |
|---|
| 79 | { |
|---|
| [214] | 80 | add_vulnerability($mark, "Host may be vulnerable to a source disclosure using the Translate: header", 999983, 390, "GET", $f, $content); |
|---|
| [158] | 81 | last; |
|---|
| 82 | } |
|---|
| 83 | } |
|---|
| 84 | } |
|---|
| 85 | } |
|---|
| [26] | 86 | |
|---|
| [73] | 87 | ####################################################################### |
|---|
| 88 | # Servlet-Engine info |
|---|
| [284] | 89 | if (defined $headers{'servlet-engine'}) |
|---|
| [73] | 90 | { |
|---|
| [284] | 91 | my $x = $headers{'servlet-engine'}; |
|---|
| [73] | 92 | $x = ~s/\(.*$//; |
|---|
| 93 | $x =~ s/\s+//g; |
|---|
| 94 | push(@BUILDITEMS, $x); |
|---|
| [26] | 95 | |
|---|
| [130] | 96 | add_vulnerability($mark, "Retrieved servlet-engine headers: $x",999991,0); |
|---|
| [26] | 97 | |
|---|
| [73] | 98 | my @bits = split(/;/, $x); |
|---|
| 99 | foreach my $bit (@bits) |
|---|
| 100 | { |
|---|
| 101 | push(@BUILDITEMS, $bit); |
|---|
| 102 | } |
|---|
| 103 | } |
|---|
| [26] | 104 | |
|---|
| [73] | 105 | ####################################################################### |
|---|
| 106 | # Content-Location header in IIS |
|---|
| 107 | LW2::http_close(\%request); # force-close any old connections |
|---|
| 108 | LW2::http_fixup_request(\%request); |
|---|
| 109 | LW2::http_reset(); |
|---|
| 110 | my $wh = $request{'whisker'}{'Host'}; |
|---|
| 111 | my $h = $request{'Host'}; |
|---|
| 112 | delete $request{'whisker'}{'Host'}; |
|---|
| 113 | delete $request{'Host'}; |
|---|
| 114 | $request{'whisker'}->{'uri'} = "/"; |
|---|
| 115 | $request{'whisker'}->{'method'} = "GET"; |
|---|
| 116 | $request{'whisker'}{'version'} = "1.0"; |
|---|
| 117 | |
|---|
| 118 | LW2::http_do_request_timeout(\%request, \%result); |
|---|
| [269] | 119 | $NIKTO{'totalrequests'}++; |
|---|
| [73] | 120 | if ( ($result{'content-location'} ne "") |
|---|
| 121 | && ($result{'content-location'} =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) |
|---|
| [130] | 122 | && ($result{'content-location'} !~ /$mark->{ip}/)) |
|---|
| [73] | 123 | { |
|---|
| [130] | 124 | add_vulnerability($mark,"IIS may reveal its internal IP in the Content-Location header via a request to the root file. The value is \"$result{'content-location'}\".", 999989, 630); |
|---|
| [73] | 125 | } |
|---|
| 126 | |
|---|
| 127 | LW2::http_close(\%request); # force-close any old connections |
|---|
| 128 | LW2::http_fixup_request(\%request); |
|---|
| 129 | LW2::http_reset(); |
|---|
| 130 | $request{'whisker'}->{'uri'} = "/images"; |
|---|
| 131 | $request{'whisker'}->{'method'} = "GET"; |
|---|
| 132 | $request{'whisker'}{'version'} = "1.0"; |
|---|
| 133 | delete $request{'whisker'}{'Host'}; |
|---|
| 134 | delete $request{'Host'}; |
|---|
| [268] | 135 | if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; } |
|---|
| [73] | 136 | LW2::http_do_request_timeout(\%request, \%result); |
|---|
| [269] | 137 | $NIKTO{'totalrequests'}++; |
|---|
| [26] | 138 | |
|---|
| [73] | 139 | if (($result{'location'} ne "") |
|---|
| 140 | && ($result{'location'} =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) |
|---|
| [130] | 141 | && ($result{'location'} !~ /$mark->{ip}/)) |
|---|
| [73] | 142 | { |
|---|
| [214] | 143 | add_vulnerability($mark,"IIS may reveal its internal IP in the Location header via a request to the /images directory. The value is \"$result{'location'}\".",999988,630,"GET","/images"); |
|---|
| [73] | 144 | } |
|---|
| [26] | 145 | |
|---|
| [73] | 146 | $request{'whisker'}{'Host'} = $wh; |
|---|
| 147 | $request{'Host'} = $h; |
|---|
| [26] | 148 | |
|---|
| [73] | 149 | ####################################################################### |
|---|
| 150 | # Location header in WebLogic |
|---|
| 151 | LW2::http_close(\%request); # force-close any old connections |
|---|
| 152 | LW2::http_fixup_request(\%request); |
|---|
| 153 | LW2::http_reset(); |
|---|
| 154 | $request{'whisker'}->{'uri'} = "."; |
|---|
| 155 | $request{'whisker'}->{'method'} = "GET"; |
|---|
| 156 | $request{'whisker'}{'version'} = "1.0"; |
|---|
| [268] | 157 | if ($CLI{'pause'} > 0) { sleep $CLI{'pause'}; } |
|---|
| [73] | 158 | LW2::http_do_request_timeout(\%request, \%result); |
|---|
| [269] | 159 | $NIKTO{'totalrequests'}++; |
|---|
| [26] | 160 | |
|---|
| [73] | 161 | if (($result{'location'} ne "") && ($result{'location'} =~ /http:\/\//)) |
|---|
| 162 | { |
|---|
| [214] | 163 | add_vulnerability($mark,"WebLogic may reveal its internal IP or hostname in the Location header. The value is \"$result{'location'}\".",999987,5737,"GET","."); |
|---|
| [73] | 164 | } |
|---|
| [26] | 165 | |
|---|
| [73] | 166 | ####################################################################### |
|---|
| 167 | # All other interesting headers |
|---|
| [139] | 168 | |
|---|
| 169 | # First let's hit something we know should return something |
|---|
| [152] | 170 | my ($res, $content)=nfetch($mark,"/","GET","",\%headers); |
|---|
| [139] | 171 | |
|---|
| [73] | 172 | foreach my $header (@interesting_headers) |
|---|
| 173 | { |
|---|
| [151] | 174 | if ($headers{$header} ne '') |
|---|
| [73] | 175 | { |
|---|
| [151] | 176 | my $x = $headers{$header}; |
|---|
| [73] | 177 | $x =~ s/\s+.*$//; |
|---|
| 178 | push(@BUILDITEMS, $x); |
|---|
| [151] | 179 | add_vulnerability($mark,"Retrieved $header header: $headers{$header}",999986,0); |
|---|
| [73] | 180 | } |
|---|
| 181 | } |
|---|
| [26] | 182 | |
|---|
| [73] | 183 | ####################################################################### |
|---|
| [194] | 184 | # Look for any uncommon headers |
|---|
| [151] | 185 | foreach my $header (sort keys %headers) |
|---|
| [73] | 186 | { |
|---|
| 187 | my $found = 0; |
|---|
| 188 | my $reportnum = 999100; |
|---|
| [139] | 189 | foreach my $st_header (@$dbarray) |
|---|
| [73] | 190 | { |
|---|
| [139] | 191 | if ($header eq $st_header->{header}) |
|---|
| [73] | 192 | { |
|---|
| 193 | $found=1; |
|---|
| 194 | } |
|---|
| 195 | } |
|---|
| 196 | if ($found == 0) |
|---|
| 197 | { |
|---|
| [151] | 198 | my $x = $headers{$header}; |
|---|
| [73] | 199 | $x =~s/\s+.*$//; |
|---|
| 200 | push(@BUILDITEMS, $x); |
|---|
| [194] | 201 | add_vulnerability($mark,"Uncommon header '$header' found, with contents: $headers{$header}",$reportnum,0); |
|---|
| [73] | 202 | $reportnum++; |
|---|
| 203 | } |
|---|
| 204 | } |
|---|
| [56] | 205 | |
|---|
| [73] | 206 | ####################################################################### |
|---|
| 207 | # ETag header |
|---|
| 208 | |
|---|
| 209 | # Try to grab a standard file |
|---|
| 210 | foreach my $f (qw/\/index.html \/index.htm \/robots.txt/) |
|---|
| 211 | { |
|---|
| [158] | 212 | (my $res, $content) = nfetch($mark,$f, "GET","", \%headers); |
|---|
| [215] | 213 | last if (defined $headers{'etag'}); |
|---|
| [73] | 214 | } |
|---|
| [151] | 215 | |
|---|
| [73] | 216 | # Now we have a header, let's check ETag for inode |
|---|
| [284] | 217 | if (defined $headers{'etag'}) |
|---|
| [73] | 218 | { |
|---|
| [284] | 219 | my $etag=$headers{'etag'}; |
|---|
| [73] | 220 | $etag =~ s/"//g; |
|---|
| 221 | my @fields = split("-",$etag); |
|---|
| 222 | my $message = "ETag header found on server"; |
|---|
| 223 | if ($#fields == 2) |
|---|
| 224 | { |
|---|
| 225 | my $inode="0x$fields[0]"; |
|---|
| 226 | my $size="0x$fields[1]"; |
|---|
| 227 | my $mtime="0x$fields[2]"; |
|---|
| 228 | # for some reason $mtime is mangled |
|---|
| 229 | $message .= sprintf(", inode: %d, size: %d, mtime: %s", hex($inode), hex($size), $mtime); |
|---|
| 230 | } |
|---|
| 231 | else |
|---|
| 232 | { |
|---|
| 233 | $message .= ", fields: "; |
|---|
| 234 | foreach my $field (@fields) |
|---|
| 235 | { |
|---|
| 236 | $message .= "0x$field "; |
|---|
| 237 | } |
|---|
| 238 | } |
|---|
| [130] | 239 | add_vulnerability($mark, $message, 999984, 0); |
|---|
| [73] | 240 | } |
|---|
| [26] | 241 | } |
|---|
| 242 | |
|---|
| 243 | 1; |
|---|