📄 nikto_core.plugin
字号:
################################################################################## directory listing# 'pattern' is an optional regex to match the file names against# written by Thomas Reucker for the SETI-Web project (GPL)#################################################################################sub dirlist{ my $DIR=$_[0] || return; my $PATTERN=$_[1] || ""; my @FILES = (); # some basic security checks... REALLY basic # this should be better if ($DIR =~ /etc/) { return; } opendir(DIRECTORY,$DIR) || die print "+ ERROR: Can't open directory '$DIR': $@"; foreach my $file (readdir(DIRECTORY)) { if ($file =~ /^\./) { next; } # skip hidden files, '.' and '..' if ($PATTERN ne "") { if ($file =~ /$PATTERN/) { push (@FILES,$file); } } else { push (@FILES,$file); } }closedir(DIRECTORY); return @FILES;}######################################################################## dbcheck# checks the standard databases for duplicate entries#######################################################################sub dbcheck { my (@L, @ENTRIES, %ENTRIES)=(); my ($line, $entry) =""; my $ctr=0; #### scan_database.db print "-->\tDB Syntax ($FILES{dbfile})\n"; open(IN,"<$FILES{dbfile}") || die print "\tERROR: Unable to open '$FILES{dbfile}' for read: $@\n"; @ENTRIES=<IN>; close(IN); foreach $line (@ENTRIES) { if ($line !~ /^\"/) { next; } @L=parse_csv($line); if (($#L < 5) || ($#L > 6)) { print "\tERROR: Invalid syntax ($#L): $line"; next; } if ($line !~ /^\".*\",\".*\",\".*\",\".*\",\".*\"/) { print "\tERROR: Invalid syntax ($#L): $line\n"; next; } if (($L[1] =~ /^\@CGI/) && ($L[1] !~ /^\@CGIDIRS/)) { print "\tERROR: Possible \@CGIDIRS misspelling:$line"; } if ($line =~ /[^\\]\"\"/) { print "\tERROR: Possible double-quote syntax error:$line"; } # build entry based on all except output message $ENTRIES{"$L[0],$L[1],$L[2],$L[3],$L[5]"}++; } foreach $entry (keys %ENTRIES) { if ($ENTRIES{$entry} > 1) { print "\tERROR: Duplicate ($ENTRIES{$entry}): $entry\n"; } } $ctr=keys(%ENTRIES); print "\t$ctr entries\n"; #### user_scan_database.db if (-e $FILES{userdbfile}) { print "--> DB Syntax ($FILES{userdbfile})\n"; %ENTRIES=(); open(IN,"<$FILES{userdbfile}") || die print "\tERROR: Unable to open '$FILES{userdbfile}' for read: $@\n"; @ENTRIES=<IN>; close(IN); $ctr=0; foreach $line (@ENTRIES) { if ($line !~ /^\"/) { next; } @L=parse_csv($line); if (($#L < 5) || ($#L > 6)) { print "\tERROR: User DB: Invalid syntax ($#L): $line"; next; } if ($line !~ /^\".*\",\".*\",\".*\",\".*\",\".*\"/) { print "\tERROR: User DB: Invalid syntax ($#L): $line\n"; next; } if (($L[1] =~ /^\@CGI/) && ($L[1] !~ /^\@CGIDIRS/)) { print "\tERROR: User DB: Possible \@CGIDIRS misspelling:$line"; } if ($line =~ /[^\\]\"\"/) { print "\tERROR: User DB: Possible double-quote syntax error:$line"; } # build entry based on all except output message $ENTRIES{"$L[0],$L[1],$L[2],$L[3],$L[5]"}++; } foreach $entry (keys %ENTRIES) { if ($ENTRIES{$entry} > 1) { print "\tERROR: Duplicate ($ENTRIES{$entry}): $entry\n"; } } $ctr=keys(%ENTRIES); print "\t$ctr entries\n"; } #### outdated.db $ctr=0; print "-->\tDB Syntax ($NIKTO{plugindir}/outdated.db)\n"; %ENTRIES=(); open(IN,"<$NIKTO{plugindir}/outdated.db") || die print "\tERROR: Unable to open '$NIKTO{plugindir}/outdated.db' for read: $@\n"; @ENTRIES=<IN>; close(IN); foreach $line (@ENTRIES) { $line =~ s/^\s+//; if ($line =~ /^\#/) { next; } chomp($line); if ($line eq "") { next; } @L=parse_csv($line); if ($line !~ /^\".*\"\,\".*\"\,\".*\"$/) { print "\tERROR: Invalid syntax ($#L): $line\n"; next; } if ($#L ne 2) { print "\tERROR: Invalid syntax ($#L): $line\n"; next; } $ENTRIES{"$L[0]"}++; } foreach $entry (keys %ENTRIES) { if ($ENTRIES{$entry} > 1) { print "\tERROR: Duplicate ($ENTRIES{$entry}): $entry\n"; } } $ctr=keys(%ENTRIES); print "\t$ctr entries\n"; #### server_msgs.db $ctr=0; print "-->\tDB Syntax ($NIKTO{plugindir}/server_messages.db)\n"; %ENTRIES=(); open(IN,"<$NIKTO{plugindir}/server_msgs.db") || die print "\tERROR: Unable to open '$NIKTO{plugindir}/server_msgs.db' for read: $@\n"; @ENTRIES=<IN>; close(IN); foreach $line (@ENTRIES) { $line =~ s/^\s+//; if ($line =~ /^\#/) { next; } chomp($line); if ($line eq "") { next; } @L=parse_csv($line); if ($line !~ /^\".*\"\,\".*\"$/) { print "\tERROR: Invalid syntax ($#L): $line\n"; next; } if ($#L ne 1) { print "\tERROR: Invalid syntax ($#L): $line\n"; next; } # test regex "test" =~ /$L[0]/; $ENTRIES{"$L[0]"}++; } foreach $entry (keys %ENTRIES) { if ($ENTRIES{$entry} > 1) { print "\tERROR: Duplicate ($ENTRIES{$entry}): $entry\n"; } } $ctr=keys(%ENTRIES); print "\t$ctr entries\n"; #### check that all plugins are in nikto_plugin_order.txt print "-->\tPlugin order ($NIKTO{plugindir}/nikto_plugin_order.txt)\n"; my @FILES=dirlist($NIKTO{plugindir},"(\.plugin\$)"); my %PLUGS; foreach my $pluginf (@FILES) { chomp($pluginf); if ($pluginf =~ /\#/) { next; } $pluginf =~ s/\s+//; if ($pluginf eq "") { next; } $pluginf =~ s/\..*$//; if ($pluginf eq "nikto_core") { next; } $PLUGS{$pluginf}=0; } open(ORDERFILE,"<$NIKTO{plugindir}/nikto_plugin_order.txt") || die print "\tERROR: Unable to open '$NIKTO{plugindir}/nikto_plugin_order.txt' for read: $@\n"; foreach my $line (<ORDERFILE>) { chomp($line); if ($line =~ /^\#/) { next; } if ($line eq "") { next; } $PLUGS{$line}=1; } close(ORDERFILE); my $bad=0; foreach my $p (sort keys %PLUGS) { if ($PLUGS{$p} eq 0) { $bad=1; print "\tERROR: plugin '$p' not in nikto_plugin_order.txt\n"; } } if (!$bad) { print "\tOrder file okay\n"; } #### check that all plugins are named properly print "-->\tPlugin conventions ($NIKTO{plugindir}/*.plugin)\n"; my $bad=0; foreach my $pluginf (@FILES) { chomp($pluginf); if ($pluginf =~ /\#/) { next; } $pluginf =~ s/\s+//; if ($pluginf eq "") { next; } if ($pluginf eq "nikto_core") { next; } open(IN,"<$NIKTO{plugindir}/$pluginf.plugin") || die print "\tERROR: Unable to open '$NIKTO{plugindir}/$pluginf.plugin' for read: $@\n"; my @F=<IN>; close(IN); my $CT=grep(/sub $pluginf/,@F); if ($CT < 1) { print "\tERROR: file '$pluginf\.plugin' does not have 'sub $pluginf' defined.\n"; $bad++; } } if (!$bad) { print "\tPlugin syntax okay\n"; } exit;}######################################################################## spit out all the details#######################################################################sub dump_result_hash{ if (!$OUTPUT{debug}) { return; } # quick return nprint("- Result Hash:","d"); foreach my $item (sort keys %result) { if ($item eq "whisker") { next; } nprint("- $item \t\t$result{$item}","d"); } foreach my $item (sort keys %{$result{'whisker'}}) { nprint("- \$whisker-\>$item \t$result{'whisker'}->{$item}","d"); } return;}######################################################################## spit out all the details#######################################################################sub dump_request_hash{ if (!$OUTPUT{debug}) { return; } # quick return nprint("- Request Hash:","d"); foreach my $item (sort keys %request) { if ($item eq "whisker") { next; } nprint("- $item: $request{$item}","d"); } foreach my $item (sort keys %{$request{'whisker'}}) { if ($item eq "http_eol") { next; } nprint("- \$whisker-\>$item: $request{'whisker'}->{$item}","d"); } return;}######################################################################## check_responses# check what the 200/404 messages are...#######################################################################sub check_responses{ # get NOT FOUND response (404) # check for compliant 404 message # check for common strings to use as 'not found' matches from content ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound}, $CONTENT)=fetch("/$CLI{root}$NIKTO{fingerprint}","GET"); if (($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound} eq "400") || ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound} eq "")) # may need to use HTTP/1.? { my $old=$request{'whisker'}->{'http_ver'}; if ($request{'whisker'}->{'http_ver'} eq "1.1") { $request{'whisker'}->{'http_ver'}="1.0"; } else { $request{'whisker'}->{'http_ver'}="1.1"; } nprint("- Server did not understand HTTP $old, switching to HTTP $request{'whisker'}->{'http_ver'}"); ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound}, $CONTENT)=fetch("/$CLI{root}$NIKTO{fingerprint}","GET"); } if (($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound} ne "404") && ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound} ne "401")) { nprint("+ Server does not respond with '404' for error messages (uses '$TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound}')."); nprint("+ This may increase false-positives."); if ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound} eq "302") { nprint("+ Not found files redirect to: $result{'location'}"); } if ($CONTENT =~ /not found/i) { $TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound}="not found"; } # shorten it, content has "not found" in it elsif ($CONTENT =~ /404/i) { $TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound}="404"; } # shorten it, content has "404" in it else { $TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound} = $CONTENT; } } # get OK response (200) ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found}, $CONTENT)=fetch("/$CLI{root}","GET"); if ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found} eq 404) # assume server does not actually have a / & set it to 200 { $TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found}=200; nprint("+ No root document found, assuming 200 is OK response.","v"); } elsif ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found} != 200) { if ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found} eq "302") { nprint("+ The root file (/) redirects to: $result{'location'}"); # try to get redirected location to see if 200 is actually the valid response ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found}, $CONTENT)=fetch($result{'location'},"GET"); if ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found} ne 200) # still no good... just a 302, stop going in circles { $TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found}=302; } } } if ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found} eq "401") { auth_check(); } # if they're the same, something is amiss... just pick a 404/200 scheme, nothing better to do # except 403... which will cut down false positives if ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound} eq $TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found}) { if ($TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound} ne "401") { nprint("+ The found & not found messages appear to be the same, be skeptical of positives."); } $TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound}=404 unless $TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{notfound} eq 403; $TARGETS{$CURRENT_HOST_ID}{ports}{$CURRENT_PORT}{found}=200; }return;}######################################################################## figure out CGI directories# check_cgi#######################################################################sub check_cgi{ my ($gotvalid,$gotinvalid)=0; my @POSSIBLECGI=(); my @CFGCGI=(split(/ /,$VARIABLES{"\@CGIDIRS"})); my ($res, $possiblecgidir) =""; #force all possible CGI directories to be "true" if ($CLI{forcecgi} eq "all") { $VARIABLES{"\@CGIDIRS"} = join(" ",@CFGCGI); } # scan no CGI directories elsif ($CLI{forcecgi} eq "none") { $VARIABLES{"\@CGIDIRS"} = ""; } # scan a specific directory elsif ($CLI{forcecgi} =~ /[a-zA-Z0-9]/) { $VARIABLES{"\@CGIDIRS"} = $CLI{forcecgi}; } # or normal testing of each dir else { foreach $possiblecgidir (@CFGCGI) { ($res, $CONTENT)=fetch($possiblecgidir,"GET"); nprint("Checked for CGI dir\t$possiblecgidir\tgot:$res","d"); if (($res eq 302) || ($res eq 200) || ($res eq 403)) { push(@POSSIBLECGI,$possiblecgidir); $gotvalid++; } } if ($gotvalid eq 0) { nprint("+ No CGI Directories found (use '-C all' to force check all possible dirs)"); $VARIABLES{"\@CGIDIRS"} = ""; } elsif ($#CFGCGI eq $#POSSIBLECGI) { nprint("+ All CGI directories 'found', use '-C none' to test none"); $VARIABLES{"\@CGIDIRS"} = @CFGCGI; } else { $VARIABLES{"\@CGIDIRS"} = join(" ",@POSSIBLECGI); } } # end !$CLI{forcecgi} nprint("- Checking for CGI in: $VARIABLES{\"\@CGIDIRS\"}","v"); return;}######################################################################## get a page# fetch URI, METHOD#######################################################################sub fetch{ $request{'whisker'}->{'uri'} = $_[0] || return; $request{'whisker'}->{'method'} = $_[1]; LW::http_reset(); $request{'whisker'}->{'data'}=""; if ($_[2] ne "" && $_[2] ne " ") { $request{'whisker'}->{'data'} = $_[2]; $request{'whisker'}->{'data'} =~ s/\\\"/\"/g; } else { delete $request{'whisker'}->{'Content-Length'}; } $NIKTO{totalrequests}++;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -