Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| ############################################################################### | |
| # SPDX-License-Identifier: GPL-3.0-only | |
| # PURPOSE: General HTTP headers checks | |
| ############################################################################### | |
| use vars qw/$HEADERSDB/; | |
| sub nikto_headers_init { | |
| my $id = { | |
| name => "headers", | |
| full_name => "HTTP Headers", | |
| author => "Sullo", | |
| description => "Performs various checks against the headers returned from an HTTP request.", | |
| hooks => { | |
| scan => { method => \&nikto_headers, }, | |
| postfetch => { method => \&nikto_headers_postfetch, }, | |
| start => { method => \&nikto_headers_load, }, | |
| }, | |
| copyright => "2008 Chris Sullo" | |
| }; | |
| # some global variables | |
| use vars qw/%ETAGS %BREACH %HEADERS_FOUND/; | |
| return $id; | |
| } | |
| sub nikto_headers_load { | |
| $HEADERSDB = init_db("db_headers_common"); | |
| } | |
| sub nikto_headers_postfetch { | |
| my ($mark, $parameters, $request, $response) = @_; | |
| return if $mark->{'terminate'}; | |
| # Skip OPTIONS | |
| return if $request->{'whisker'}{'method'} eq "OPTIONS"; | |
| # These headers are very common but unlikely to be useful, so we can quickly skip them | |
| my %skip_headers = map { $_ => 1 } qw( | |
| whisker | |
| date | |
| content-type | |
| content-length | |
| connection | |
| x-mod-pagespeed | |
| x-page-speed | |
| set-cookie | |
| ); | |
| # look for internal IPs | |
| foreach my $header (keys %$response) { | |
| next | |
| if exists $skip_headers{$header} | |
| || exists $HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{$header}; | |
| # Check for known headers | |
| nikto_headers_check($mark, $response, $header, 999986, $request); | |
| my $header_value = $response->{$header}; | |
| # Do not check the Location header's path target for internal IPs | |
| if ($header eq 'location') { | |
| if ($header_value =~ /^\//) { | |
| next; | |
| } | |
| else { | |
| my @uri_parts = LW2::uri_split($header_value); | |
| # reconstruct without path/query/fragment | |
| $header_value = $uri_parts[2]; | |
| } | |
| # Is it what we scanned? | |
| if (defined $header_value | |
| && ( $header_value eq $mark->{'ip'} | |
| || (defined $mark->{'vhost'} && $header_value eq $mark->{'vhost'}) | |
| || (defined $mark->{'hostname'} && $header_value eq $mark->{'hostname'}) | |
| || (defined $mark->{'displayname'} && $header_value eq $mark->{'displayname'})) | |
| ) { | |
| next; | |
| } | |
| } | |
| foreach my $ip (get_ips($header_value)) { | |
| my ($valid, $internal, $loopback) = is_ip($ip); | |
| if ($valid && !$loopback) { | |
| if ($ip ne $mark->{'ip'}) { | |
| # is it an internal, or just different? | |
| my $msg; | |
| $msg .= $request->{'whisker'}{'uri'} . ": "; | |
| if ($internal) { $msg .= "RFC-1918 "; } | |
| $msg .= "IP address found in the '$header' header. The IP is \"$ip\"."; | |
| add_vulnerability( | |
| $mark, | |
| $msg, | |
| 999979, | |
| "https://portswigger.net/kb/issues/00600300_private-ip-addresses-disclosed", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, | |
| $response | |
| ); | |
| $HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{$header} = 1; | |
| } | |
| } | |
| } | |
| } | |
| # look for inode in etag header | |
| if ( !defined $ETAGS{ $mark->{hostname} }{ $mark->{port} } | |
| && defined $response->{'etag'} | |
| && $response->{'etag'} =~ m/-/) { | |
| my $etag = $response->{'etag'}; | |
| $etag =~ s/"//g; | |
| my @fields = split("-", $etag); | |
| # Only report ETags which actually leak inodes... | |
| if (scalar(@fields) == 3) { | |
| my $message = | |
| $request->{'whisker'}{'uri'} | |
| . ": Server may leak inodes via ETags, header found with file " | |
| . $request->{'whisker'}->{'uri'}; | |
| # check for numbers that are too large | |
| my $ishex = 1; | |
| for (my $i = 0 ; $i < 3 ; $i++) { | |
| if ((length($fields[$i]) > 14) || ($fields[$i] !~ /^[0-9A-F]+$/i)) { | |
| $ishex = 0; | |
| } | |
| } | |
| use bignum; | |
| my ($inode, $size, $mtime, $ltime); | |
| if ($ishex) { | |
| $inode = "0x$fields[0]"; | |
| $size = "0x$fields[1]"; | |
| $mtime = "0x$fields[2]"; | |
| $ltime = substr(sprintf("%s", hex($mtime)), 0, 10); | |
| } | |
| else { | |
| $inode = $fields[0]; | |
| $size = $fields[1]; | |
| $mtime = $ltime = $fields[2]; | |
| } | |
| $message .= | |
| ($ishex == 1) | |
| ? sprintf(", inode: %d, size: %d, mtime: %s", | |
| hex($inode), hex($size), scalar(localtime($ltime))) | |
| : sprintf(", inode: %s, size: %s, mtime: %s", $inode, $size, $mtime); | |
| add_vulnerability($mark, $message, 999984, "CVE-2003-1418", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, $response); | |
| $ETAGS{ $mark->{hostname} }{ $mark->{port} } = 1; | |
| } | |
| } | |
| if (defined $response->{'link'} | |
| && !$HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'link'}) { | |
| my $values = report_multiheaders($response->{'link'}); | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} . ": Link header(s) found with value(s): $values.", | |
| "000427", | |
| "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, | |
| $response | |
| ); | |
| $HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'link'} = 1; | |
| } | |
| # Look for any uncommon headers | |
| foreach my $header (keys %$response) { | |
| next | |
| if defined $HEADERS_FOUND{ $mark->{'hostname'} }{ $mark->{'port'} }{"uncommon-$header"}; | |
| my $found = 0; | |
| foreach my $st_header (@$HEADERSDB) { | |
| if ($header eq $st_header->{'header'}) { | |
| $found = 1; | |
| last; | |
| } | |
| } | |
| if ($found == 0) { | |
| my $x = $response->{$header}; | |
| $x =~ s/\s+.*$//; | |
| $mark->{'components'}->{$x} = 1; | |
| my $values = report_multiheaders($response->{$header}); | |
| $vuln = $request->{'whisker'}{'uri'} | |
| . ": Uncommon header(s) '$header' found, with contents: $values."; | |
| add_vulnerability($mark, $vuln, 999100, "", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, $response); | |
| $HEADERS_FOUND{ $mark->{'hostname'} }{ $mark->{'port'} }{"uncommon-$header"} = 1; | |
| } | |
| } | |
| # Strict-Transport-Security | |
| if ( $mark->{'ssl'} | |
| && !$HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'strict-transport-security'} | |
| && defined $response) { | |
| if ($response->{'strict-transport-security'} =~ /max-age="?0"?/i) { | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} | |
| . ": The site uses TLS and the Strict-Transport-Security HTTP header is set with max-age=0, which effectively disables it.", | |
| 999971, | |
| "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, | |
| $response | |
| ); | |
| } | |
| $HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'strict-transport-security'} = 1; | |
| } | |
| # Alt-Svc (HTTP/3) | |
| if (!$HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'alt-svc'} | |
| && defined $response->{'whisker'}->{'code'}) { | |
| if (defined $response->{'alt-svc'}) { | |
| my ($protocol, $msg); | |
| $response->{'alt-svc'} =~ /.*(h[23])="([^"]+)"/; | |
| my $endpoint = $2; | |
| if ($1 eq "h2") { | |
| $protocol = "HTTP/2 over TLS"; | |
| } | |
| elsif ($1 eq "h2c") { | |
| $protocol = "HTTP/2 over TCP"; | |
| $msg = " Unencrypted."; | |
| } | |
| elsif ($1 eq "h3") { | |
| $protocol = "HTTP/3"; | |
| $msg = " Nikto cannot test HTTP/3 over QUIC."; | |
| } | |
| else { | |
| $protocol = $1; | |
| } | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} | |
| . ": An alt-svc header was found which is advertising $protocol. The endpoint is: '$endpoint'.$msg", | |
| "011799", | |
| "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/alt-svc", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, | |
| $response | |
| ); | |
| $HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'alt-svc'} = 1; | |
| } | |
| } | |
| # x-clacks-overhead | |
| if (!$HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'x-clacks-overhead'} | |
| && defined $response->{'whisker'}->{'code'}) { | |
| if (defined $response->{'x-clacks-overhead'}) { | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} | |
| . ": There appears to be Clacks Overhead on the server and the message is: $response->{'x-clacks-overhead'}", | |
| 999104, | |
| "https://xclacksoverhead.org/home/about", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, | |
| $response | |
| ); | |
| $HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'x-clacks-overhead'} = 1; | |
| } | |
| } | |
| # CSP Report URLs | |
| if (!$HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'report-uri'} | |
| && defined $response->{'whisker'}->{'code'}) { | |
| if (defined $response->{'report-uri'}) { | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} | |
| . ": A CSP report-uri header was found. The URL is: $response->{'report-uri'}", | |
| 000433, | |
| "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, | |
| $response | |
| ); | |
| $HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'report-uri'} = 1; | |
| } | |
| } | |
| # Cloudflare | |
| if ( !$HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'cf-ray'} | |
| && defined $response->{'cf-ray'} | |
| && !$CLI{'useproxy'}) { | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} | |
| . ": Cloudflare detected via cf-ray header. Recommend proxying via Burp or mitmproxy to avoid TLS fingerprint blocks.", | |
| 999106, | |
| "https://github.com/sullo/nikto/wiki/Using-a-Proxy", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, | |
| $response | |
| ); | |
| $HEADERS_FOUND{ $mark->{hostname} }{ $mark->{port} }{'cf-ray'} = 1; | |
| } | |
| return $request, $response; | |
| } | |
| # If multiple headers were sent with the same name, turn the array into a string for reporting | |
| sub report_multiheaders { | |
| my $header = $_[0] || return; | |
| if (ref($header) eq 'ARRAY') { | |
| return join(',', @{$header}); | |
| } | |
| else { | |
| return $header; | |
| } | |
| } | |
| sub nikto_headers_check { | |
| my ($mark, $response, $header, $tid, $request) = @_; | |
| return if $mark->{'terminate'}; | |
| my @interesting_headers = split(" ", $VARIABLES{'@INTERESTING_HEADERS'}); | |
| foreach my $header (@interesting_headers) { | |
| if (exists $response->{$header}) { | |
| if (!exists $HEADERS_FOUND{ $mark->{'hostname'} }{ $mark->{'port'} } | |
| {"interesting-$header"}) { | |
| my $value = ""; | |
| if (ref($response->{$header}) eq ARRAY) { | |
| $value = join(', ', @{ $response->{$header} }); | |
| } | |
| else { | |
| $value = $response->{$header}; | |
| } | |
| $HEADERS_FOUND{ $mark->{'hostname'} }{ $mark->{'port'} }{"interesting-$header"} = 1; | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} . ": Retrieved $header header: $value", | |
| $tid, | |
| "", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, | |
| $response | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| sub nikto_headers { | |
| my ($mark) = @_; | |
| return if $mark->{'terminate'}; | |
| ####################################################################### | |
| # look for to see whether its vulnerable to the Translate: f | |
| my %transheaders; | |
| $transheaders{'Translate'} = "f"; | |
| foreach | |
| my $f (qw/\/index.asp \/njkk999.asp \/index.aspx \/jkl988.aspx \/login.asp \/login.aspx/) { | |
| return if $mark->{'terminate'}; | |
| my ($res, $content, $error, $request, $response) = | |
| nfetch($mark, $f, "GET", "", "", "", "Headers"); | |
| if ($res eq "200") { | |
| ($res, $content, $error, $request, $response) = | |
| nfetch($mark, $f . "\\", "GET", "", \%transheaders, "", "Headers"); | |
| if ($res eq "200") { | |
| if ($content =~ /<asp:/ || $content =~ /<\/asp:/) { | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} | |
| . ": Host may be vulnerable to a source disclosure using the Translate: header", | |
| 999983, | |
| "CVE-2000-0778", | |
| "GET", | |
| $f, | |
| $request, | |
| $response | |
| ); | |
| last; | |
| } | |
| } | |
| } | |
| } | |
| ####################################################################### | |
| # Content-Location header in IIS | |
| my (%locheaders, %locflags, $cl, $l, $wa, $rt); | |
| $locheaders{'User-Agent'} = get_ua(); | |
| $locflags{'noclean'} = 1; | |
| $locflags{'nohost'} = 1; | |
| $cl = $l = $wa = $rt = 0; | |
| foreach my $uri ('/', '/images', | |
| '/Autodiscover/Autodiscover.xml', '/Autodiscover/', | |
| '/Microsoft-Server-ActiveSync', '/Microsoft-Server-ActiveSync/default.css', | |
| '/ECP', '/EWS', | |
| '/EWS/Exchange.asmx', '/Exchange', | |
| '/OWA', '/Microsoft-Server-ActiveSync/default.eas', | |
| '/Rpc', '/EWS/Services.wsdl', | |
| '/ecp', '/OAB', | |
| '/aspnet_client', '/PowerShell' | |
| ) { | |
| return if $mark->{'terminate'}; | |
| my ($res, $content, $errors, $request, $response) = | |
| nfetch($mark, $uri, "GET", "", \%locheaders, \%locflags, "Headers", "1.0"); | |
| if (!$cl && ($response->{'content-location'} ne "")) { | |
| my @ips = get_ips($response->{'content-location'}); | |
| my ($valid, $internal, $loopback) = is_ip($ips[0]); | |
| if (($ips[0] ne '') && ($ips[0] ne $mark->{'ip'})) { | |
| $cl = 1; | |
| add_vulnerability( | |
| $mark, | |
| $uri | |
| . ": IIS may reveal its internal or real IP in the Content-Location header via a request with HTTP/1.0. The value is \"$ips[0]\".", | |
| 999989, | |
| "CVE-2000-0649", | |
| "GET", | |
| $uri, | |
| $request, | |
| $response | |
| ); | |
| } | |
| } | |
| if (!$l && ($response->{'location'} ne "")) { | |
| my @ips = get_ips($response->{'location'}); | |
| my ($valid, $internal, $loopback) = is_ip($ips[0]); | |
| if (($ips[0] ne '') && ($ips[0] ne $mark->{'ip'})) { | |
| $l = 1; | |
| add_vulnerability( | |
| $mark, | |
| $uri | |
| . ": The web server may reveal its internal or real IP in the Location header via a request to with HTTP/1.0. The value is \"$ips[0]\".", | |
| 999988, | |
| "CVE-2000-0649", | |
| "GET", | |
| $uri, | |
| $request, | |
| $response | |
| ); | |
| } | |
| } | |
| if (!$wa && ($response->{'www-authenticate'} ne "")) { | |
| my @ips = get_ips($response->{'www-authenticate'}); | |
| my ($valid, $internal, $loopback) = is_ip($ips[0]); | |
| if (($ips[0] ne '') && ($ips[0] ne $mark->{'ip'})) { | |
| $wa = 1; | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} | |
| . ": Microsoft Exchange Systems (CAS and OWA) may reveal the internal or real IP in the WWW-Authenticate header via a request to $uri over HTTP/1.0. The value is \"$ips[0]\".", | |
| 999986, | |
| "CVE-2000-0649", | |
| "GET", | |
| $uri, | |
| $request, | |
| $response | |
| ); | |
| } | |
| } | |
| if (!$rt && ($response->{'report-to'} ne "")) { | |
| my @bits = split("url\": \"", $response->{'report-to'}); | |
| my $have = 0; | |
| my @urls; | |
| foreach my $b (@bits) { | |
| next if $b !~ /^http/; | |
| $b =~ s/\".*$//; | |
| if ($b ne "") { | |
| push(@urls, $b); | |
| $have = $rt = 1; | |
| } | |
| } | |
| if ($have) { | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} | |
| . ": A CSP report-to header was found with the following URLs: " | |
| . join(", ", @urls), | |
| 999945, | |
| "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to", | |
| "GET", | |
| $uri, | |
| $request, | |
| $response | |
| ); | |
| } | |
| } | |
| if ($cl && $l && $wa && $rt) { last; } | |
| } | |
| ####################################################################### | |
| # Location header in WebLogic | |
| # Clear out locheaders | |
| for (keys %locheaders) { | |
| delete $locheaders{$_}; | |
| } | |
| $locheaders{'User-Agent'} = get_ua(); | |
| ($res, $content, $errors, $request, $response) = | |
| nfetch($mark, '.', "GET", "", \%locheaders, \%flags, "Headers", "1.0"); | |
| my @ips = get_ips($response->{'location'}); | |
| if ($ips[0] ne '') { | |
| my ($valid, $internal, $loopback) = is_ip($ips[0]); | |
| if ($valid && !$loopback && ($ips[0] ne $mark->{'ip'})) { | |
| my $msg = $request->{'whisker'}{'uri'} . ": WebLogic may reveal "; | |
| if ($internal) { $msg .= "an RFC-1918 IP address. "; } | |
| else { $msg .= "a different IP address in the Location header."; } | |
| $msg .= "The IP is \"$ips[0]\"."; | |
| add_vulnerability( | |
| $mark, | |
| $msg, | |
| 999987, | |
| "https://web.archive.org/web/20080821223545/http://archives.neohapsis.com/archives/bugtraq/2003-04/0034.html", | |
| "GET", | |
| ".", | |
| $request, | |
| $response | |
| ); | |
| } | |
| } | |
| ####################################################################### | |
| # BREACH Checks | |
| for (keys %locheaders) { | |
| delete $locheaders{$_}; | |
| } | |
| $locheaders{'User-Agent'} = get_ua(); | |
| $locheaders{'Accept-Encoding'} = "deflate, gzip"; | |
| ($res, $content, $errors, $request, $response) = | |
| nfetch($mark, '/', "GET", "", \%locheaders, \%flags, "Headers", "1.1"); | |
| if (!$BREACH && defined $response && $mark->{'ssl'}) { | |
| if (defined $response->{'content-encoding'}) { | |
| if ($response->{'content-encoding'} =~ "(deflate|gzip)") { | |
| $BREACH = 1; | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} | |
| . ": The Content-Encoding header is set to \"deflate\" which may mean that the server is vulnerable to the BREACH attack.", | |
| 999966, | |
| "http://breachattack.com/", | |
| $request->{'whisker'}->{'method'}, | |
| $request->{'whisker'}->{'uri'}, | |
| $request, | |
| $response | |
| ); | |
| } | |
| } | |
| } | |
| ####################################################################### | |
| # Jetty CVE-2015-2080 | |
| for (keys %locheaders) { | |
| delete $locheaders{$_}; | |
| } | |
| $locheaders{'User-Agent'} = get_ua(); | |
| $locheaders{'Nikto'} = "\\x1f"; | |
| ($res, $content, $errors, $request, $response) = | |
| nfetch($mark, '/', "GET", "", \%locheaders, \%flags, "Headers", "1.0"); | |
| if (($res eq "400") && ($response->{'whisker'}->{'message'} =~ /x1f<<</)) { | |
| add_vulnerability( | |
| $mark, | |
| $request->{'whisker'}{'uri'} | |
| . ": Jetty > 9.2.3 and < 9.2.9 may reveal the contents of internal memory if illegal characters are passed.", | |
| "000477", | |
| "CVE-2015-2080", | |
| "GET", | |
| ".", | |
| $request, | |
| $response | |
| ); | |
| } | |
| ####################################################################### | |
| # Missing suggested headers | |
| ($res, $content, $errors, $request, $response) = | |
| nfetch($mark, '/', "GET", "", {}, {}, "Headers", "1.1"); | |
| while (my ($header, $ref) = each %{ $VARIABLES->{'SUGGESTED_HEADERS'} }) { | |
| if (!exists $response->{$header}) { | |
| add_vulnerability($mark, | |
| $request->{'whisker'}{'uri'} . ": Suggested security header missing: " . $header, | |
| "013587", $ref, "GET", "/", $request, $response); | |
| } | |
| } | |
| } | |
| 1; | |