1#!/usr/local/bin/perl
2use strict;
3#VERSION,2.1.6
4###############################################################################
5# Modules are now loaded in a function so errors can be trapped and evaluated
6load_modules();
7###############################################################################
8#                               Nikto                                         #
9###############################################################################
10#  Copyright (C) 2001 CIRT, Inc.
11#
12#  This program is free software; you can redistribute it and/or
13#  modify it under the terms of the GNU General Public License
14#  as published by the Free Software Foundation; version 2
15#  of the License only.
16#
17#  This program is distributed in the hope that it will be useful,
18#  but WITHOUT ANY WARRANTY; without even the implied warranty of
19#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20#  GNU General Public License for more details.
21#
22#  You should have received a copy of the GNU General Public License
23#  along with this program; if not, write to
24#  Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25#
26# Contact Information:
27#     Sullo (sullo@cirt.net)
28#     http://cirt.net/
29#######################################################################
30# See the LICENSE.txt file for more information on the License Nikto is distributed under.
31#
32# This program is intended for use in an authorized manner only, and the author
33# can not be held liable for anything done with this program, code, or items discovered
34# with this program's use.
35#######################################################################
36
37# global var/definitions
38use vars qw/$TEMPLATES %CLI %VARIABLES %TESTS/;
39use vars qw/%NIKTO %CONFIGFILE %COUNTERS %db_extensions/;
40use vars qw/@RESULTS @PLUGINS @DBFILE @REPORTS %CONTENTSEARCH/;
41
42# setup
43Getopt::Long::Configure('no_ignore_case');
44$COUNTERS{'scan_start'}  = time();
45$VARIABLES{'DIV'}        = "-" x 75;
46$VARIABLES{'name'}       = "Nikto";
47$VARIABLES{'version'}    = "2.1.6";
48$VARIABLES{'configfile'} = "/usr/local/etc/nikto.conf";    ### Change if it's having trouble finding it
49
50# signal trap so we can close down reports properly
51$SIG{'INT'} = \&safe_quit;
52
53config_init();
54setup_dirs();
55require "$CONFIGFILE{'PLUGINDIR'}/nikto_core.plugin";
56nprint("T:" . localtime($COUNTERS{'scan_start'}) . ": Starting", "d");
57require "$CONFIGFILE{'PLUGINDIR'}/LW2.pm";
58$VARIABLES{'GMTOFFSET'} = gmt_offset();
59
60# use LW2;                   ### Change this line to use a different installed version
61
62#set SSL Engine
63LW2::init_ssl_engine($CONFIGFILE{'LW_SSL_ENGINE'});
64
65my ($a, $b) = split(/\./, $LW2::VERSION);
66die("- You must use LW2 2.4 or later\n") if ($a != 2 || $b < 4);
67
68general_config();
69load_databases();
70load_databases('u');
71nprint("- $VARIABLES{'name'} v$VARIABLES{'version'}");
72nprint($VARIABLES{'DIV'});
73
74# No targets - quit while we're ahead
75if ($CLI{'host'} eq "") {
76    nprint("+ ERROR: No host specified");
77    usage();
78}
79
80$COUNTERS{'total_targets'} = $COUNTERS{'hosts_completed'} = 0;
81load_plugins();
82
83# Parse the supplied list of targets
84my @MARKS = set_targets($CLI{'host'}, $CLI{'ports'}, $CLI{'ssl'}, $CLI{'root'});
85
86if (defined($CLI{'key'}) || defined($CLI{'cert'})) {
87    $CLI{'key'}  = $CLI{'cert'} unless (defined($CLI{'key'}));
88    $CLI{'cert'} = $CLI{'key'}  unless (defined($CLI{'cert'}));
89}
90
91# Now check each target is real and remove duplicates/fill in extra information
92foreach my $mark (@MARKS) {
93    $mark->{'test'} = 1;
94    $mark->{'failures'} = 0;
95
96    # Try to resolve the host
97    ($mark->{'hostname'}, $mark->{'ip'}, $mark->{'display_name'}) = resolve($mark->{'ident'});
98
99    # Skip if we can't resolve the host - we'll error later
100    if (!defined $mark->{'ip'}) {
101        $mark->{'test'} = 0;
102        next;
103    }
104
105    # Check that the port is open
106    my $open =
107      port_check(time(), $mark->{'hostname'}, $mark->{'ip'}, $mark->{'port'}, $CLI{'key'}, $CLI{'cert'});
108    if (defined $CLI{'vhost'}) { $mark->{'vhost'} = $CLI{'vhost'} }
109    if ($open == 0) {
110        $mark->{'test'} = 0;
111        next;
112    }
113    else {
114        $COUNTERS{'total_targets'}++;
115    }
116    $mark->{'ssl'} = $open - 1;
117
118    if ($mark->{'ssl'}) {
119        $mark->{'key'}  = $CLI{'key'};
120        $mark->{'cert'} = $CLI{'cert'};
121    }
122}
123
124# Open reporting
125report_head($CLI{'format'}, $CLI{'file'});
126
127# Load db_tests
128set_scan_items();
129
130# Start hook to allow plugins to load databases etc
131run_hooks("", "start");
132
133# Now we've done the precursor, do the scan
134foreach my $mark (@MARKS) {
135    next unless ($mark->{'test'});
136    $mark->{'start_time'} = time();
137    $VARIABLES{'TEMPL_HCTR'}++;
138
139    if (defined $CLI{'vhost'}) {
140        $mark->{'vhost'} = $CLI{'vhost'};
141    }
142
143    # Saving responses
144    if ($CLI{'saveresults'} ne '') {
145        $mark->{'save_dir'} = save_createdir($CLI{'saveresults'}, $mark);
146        $mark->{'save_prefix'} = save_getprefix($mark);
147    }
148
149    # Cookies
150    if (defined $CONFIGFILE{'STATIC-COOKIE'}) {
151        $mark->{'cookiejar'} = LW2::cookie_new_jar();
152
153        # parse conf line into name/value pairs
154        foreach my $p (split(/;/, $CONFIGFILE{'STATIC-COOKIE'})) {
155            $p =~ s/(?:^\s+|\s+$)//;
156            $p =~ s/"(?:[ ]+)?=(?:[ ]+)?"/","/g;
157            my @cv = parse_csv($p);
158
159            # Set into the jar
160            LW2::cookie_set(\%{ $mark->{'cookiejar'} }, $cv[0], $cv[1]);
161        }
162    }
163
164    $mark->{'total_vulns'}  = 0;
165    $mark->{'total_errors'} = 0;
166
167    my %FoF = ();
168
169    nfetch($mark, "/", "GET", "", "", { noprefetch => 1, nopostfetch => 1 }, "getinfo");
170
171    report_host_start($mark);
172    if ($CLI{'findonly'}) {
173        my $protocol = "http";
174        if ($mark->{'ssl'}) { $protocol .= "s"; }
175        if ($mark->{'banner'} eq "") {
176            $mark->{'banner'} = "(no identification possible)";
177        }
178
179        add_vulnerability($mark,
180                   "Server: $protocol://$mark->{'display_name'}:$mark->{'port'}\t$mark->{'banner'}",
181                   0);
182    }
183    else {
184        dump_target_info($mark);
185        unless ((defined $CLI{'nofof'}) || ($CLI{'plugins'} eq '@@NONE')) { map_codes($mark) }
186        run_hooks($mark, "recon");
187        run_hooks($mark, "scan");
188    }
189    $mark->{'end_time'} = time();
190    $mark->{'elapsed'}  = $mark->{'end_time'} - $mark->{'start_time'};
191    if (!$CLI{'findonly'}) {
192        if (!$mark->{'terminate'}) {
193            nprint(
194                "+ $COUNTERS{'totalrequests'} requests: $mark->{'total_errors'} error(s) and $mark->{'total_vulns'} item(s) reported on remote host"
195                );
196        }
197        else {
198            nprint(
199                "+ Scan terminated:  $mark->{'total_errors'} error(s) and $mark->{'total_vulns'} item(s) reported on remote host"
200                );
201        }
202        nprint(  "+ End Time:           "
203               . date_disp($mark->{'end_time'})
204               . " (GMT$VARIABLES{'GMTOFFSET'}) ($mark->{'elapsed'} seconds)");
205    }
206    nprint($VARIABLES{'DIV'});
207
208    $COUNTERS{'hosts_completed'}++;
209    report_host_end($mark);
210}
211$COUNTERS{'scan_end'}     = time();
212$COUNTERS{'scan_elapsed'} = ($COUNTERS{'scan_end'} - $COUNTERS{'scan_start'});
213report_summary();
214report_close();
215
216if (!$CLI{'findonly'}) {
217    nprint("+ $COUNTERS{'hosts_completed'} host(s) tested");
218    nprint("+ $COUNTERS{'totalrequests'} requests made in $COUNTERS{'scan_elapsed'} seconds", "v");
219
220    send_updates(@MARKS);
221}
222
223nprint("T:" . localtime() . ": Ending", "d");
224
225exit;
226
227#################################################################################
228# Load config files in order
229sub config_init {
230
231    # read just the --config option
232    {
233        my %optcfg;
234        Getopt::Long::Configure('pass_through', 'noauto_abbrev');
235        GetOptions(\%optcfg, "config=s");
236        Getopt::Long::Configure('nopass_through', 'auto_abbrev');
237        if (defined $optcfg{'config'}) { $VARIABLES{'configfile'} = $optcfg{'config'}; }
238    }
239
240    # Read the config files in order
241    my ($error, $home);
242    my $config_exists = 0;
243    $error = load_config("$VARIABLES{'configfile'}");
244    $config_exists = 1 if ($error eq "");
245
246    # Guess home directory -- to support Windows
247    foreach my $var (split(/ /, "HOME USERPROFILE")) {
248        $home = $ENV{$var} if ($ENV{$var});
249    }
250    $error = load_config("$home/nikto.conf");
251    $config_exists = 1 if ($error eq "");
252
253    # Guess Nikto current directory
254    my $NIKTODIR = $0;
255    chomp($NIKTODIR);
256    $NIKTODIR =~ s#[\\/]nikto.pl$##;
257    $error = load_config("$NIKTODIR/nikto.conf");
258    $config_exists = 1 if ($error eq "");
259
260    $error = load_config("nikto.conf");
261    $config_exists = 1 if ($error eq "");
262
263    if ($config_exists == 0) {
264        die "- Could not find a valid nikto config file\n";
265    }
266
267    return;
268}
269
270###############################################################################
271sub load_modules {
272        my $errors=0;
273	my @modules = qw/Getopt::Long Time::Local IO::Socket/;
274	push(@modules,"List::Util qw(sum)");
275	foreach my $mod (@modules) {
276		eval "use $mod";
277        	if ($@) {
278			print "ERROR: Required module not found: $mod\n";
279			$errors=1;
280		}
281	}
282
283	@modules = ();
284	push(@modules,"Time::HiRes qw(ualarm gettimeofday tv_interval)");
285	push(@modules,"POSIX qw(:termios_h)");
286	foreach my $mod (@modules) {
287		eval "use $mod";
288		if ($@ && $^O !~ /MSWin32/) {
289			# Allow this to work on Windows
290			if ($@) { print "ERROR: Required module not found: $mod\n"; $errors=1; }
291		}
292	}
293
294	if ($errors) { exit; }
295}
296
297#################################################################################
298# load config file
299# error=load_config(FILENAME)
300sub load_config {
301    my $configfile = $_[0];
302
303    open(CONF, "<$configfile") || return "+ ERROR: Unable to open config file '$configfile'";
304    my @CONFILE = <CONF>;
305    close(CONF);
306
307    foreach my $line (@CONFILE) {
308        $line =~ s/\#.*$//;
309        chomp($line);
310        $line =~ s/\s+$//;
311        $line =~ s/^\s+//;
312        next if ($line eq "");
313        my @temp = split(/=/, $line, 2);
314        if ($temp[0] ne "") { $CONFIGFILE{ $temp[0] } = $temp[1]; }
315    }
316
317    # add CONFIG{'CLIOPTS'} to ARGV if defined...
318    if (defined $CONFIGFILE{'CLIOPTS'}) {
319        my @t = split(/ /, $CONFIGFILE{'CLIOPTS'});
320        foreach my $c (@t) { push(@ARGV, $c); }
321    }
322
323    # Check for necessary config items
324    check_config_defined("CHECKMETHODS", "HEAD");
325    check_config_defined('@@MUTATE',     'dictionary;subdomain');
326    check_config_defined('@@DEFAULT',    '@@ALL,-@@MUTATE');
327
328    return "";
329}
330#################################################################################
331# find plugins directory
332sub setup_dirs {
333    my $CURRENTDIR = $0;
334    chomp($CURRENTDIR);
335    $CURRENTDIR =~ s#[\\/]nikto.pl$##;
336    $CURRENTDIR = "." if $CURRENTDIR =~ /^nikto.pl$/;
337
338    # First assume we get it from CONFIGFILE
339    unless (defined $CONFIGFILE{'EXECDIR'}) {
340        if (-d "$ENV{'PWD'}/plugins") {
341            $CONFIGFILE{'EXECDIR'} = $ENV{'PWD'};
342        }
343        elsif (-d "$CURRENTDIR/plugins") {
344            $CONFIGFILE{'EXECDIR'} = $CURRENTDIR;
345        }
346        elsif (-d "./plugins") {
347            $CONFIGFILE{'EXECDIR'} = $CURRENTDIR;
348        }
349        else {
350            print STDERR "Could not work out the nikto EXECDIR, try setting it in nikto.conf\n";
351            exit;
352        }
353    }
354    unless (defined $CONFIGFILE{'PLUGINDIR'}) {
355        $CONFIGFILE{'PLUGINDIR'} = "$CONFIGFILE{'EXECDIR'}/plugins";
356    }
357    unless (defined $CONFIGFILE{'TEMPLATEDIR'}) {
358        $CONFIGFILE{'TEMPLATEDIR'} = "$CONFIGFILE{'EXECDIR'}/templates";
359    }
360    unless (defined $CONFIGFILE{'DOCDIR'}) {
361        $CONFIGFILE{'DOCDIR'} = "$CONFIGFILE{'EXECDIR'}/docs";
362    }
363    unless (defined $CONFIGFILE{'DBDIR'}) {
364        $CONFIGFILE{'DBDIR'} = "$CONFIGFILE{'EXECDIR'}/databases";
365    }
366    return;
367}
368
369######################################################################
370## check_config_defined(item, default)
371## Checks whether config has been set, warns and sets to a default
372sub check_config_defined {
373    my $item    = $_[0];
374    my $default = $_[1];
375
376    if (!defined $CONFIGFILE{$item}) {
377        print STDERR
378          "- Warning: $item is not defined in Nikto configuration, setting to \"$default\"\n";
379        $CONFIGFILE{$item} = $default;
380    }
381
382    return;
383}
384