############################################################################### # SPDX-License-Identifier: GPL-3.0-only # PURPOSE: SQL Reporting ############################################################################### # Sample table create (mysql): # create table nikto_table (id int(11) not null auto_increment primary key, # scanid varchar(32), testid varchar(6) not null, ip varchar(15), # hostname text, port int(5), tls tinyint(1), refs text, httpmethod text, # uri text, message text, request blob, response mediumblob); # See documentation/nikto_schema_mysql.sql and nikto_schema_postgresql.sql for complete schemas ############################################################################### sub nikto_report_sqlg_init { my $id = { name => "report_sqlg", full_name => "Generic SQL reports", author => "Sullo", description => "Produces SQL inserts into a generic database.", report_head => \&sqlg_open, report_host_start => \&sqlg_host_start, report_item => \&sqlg_item, report_ssl_info => \&sqlg_ssl_info, report_format => 'sql', copyright => "2013 Chris Sullo" }; return $id; } ############################################################################### # open output file sub sqlg_open { my ($file) = @_; print STDERR "+ ERROR: Output file not specified.\n" if $file eq ''; # Open file with lexical handle and produce header my $fh; open($fh, ">>", $file) || die print STDERR "+ ERROR: Unable to open '$file' for write: $@\n"; # Enable autoflush $fh->autoflush(1); # Write header my $opt = $CLI{'all_options'}; $opt =~ s/'/\\'/g; print $fh "# $VARIABLES{'name'} - v$VARIABLES{'version'}/$VARIABLES{'core_version'}\n"; print $fh "# Options: $opt\n"; print $fh "# Start Time: " . localtime($COUNTERS{'scan_start'}) . "\n"; print $fh "# End Time: " . localtime($COUNTERS{'scan_end'}) . "\n"; print $fh "\n"; return $fh; } ############################################################################### # start output sub sqlg_host_start { my ($handle, $mark) = @_; my $banner = $mark->{'banner'} || ''; my $ssl = 0; if (defined $mark->{'ssl_cipher'}) { $ssl = 1; } my $hostname = $mark->{'vhost'} ? $mark->{'vhost'} : $mark->{'hostname'}; my $scanid = $mark->{'scanid'} || ''; my $msg = $banner ne '' ? "Server banner: $banner" : "Host scan started"; my $ip = $mark->{'ip'}; $ip =~ s/'/\\'/g; $hostname =~ s/'/\\'/g; $msg =~ s/'/\\'/g; $scanid =~ s/'/\\'/g; my $sql = "insert into nikto_table (scanid, testid, ip, hostname, port, tls, refs, httpmethod, uri, message) values("; $sql .= "'$scanid','999958','$ip','$hostname','$mark->{'port'}','$ssl','0','GET','/','$msg');\n"; print $handle $sql; return; } ############################################################################### # write SSL info sub sqlg_ssl_info { my ($handle, $mark) = @_; my $hostname = $mark->{'vhost'} ? $mark->{'vhost'} : $mark->{'hostname'}; my $ssl_cipher = $mark->{'ssl_cipher'} || ''; my $ssl_subject = $mark->{'ssl_cert_subject'} || ''; my $ssl_issuer = $mark->{'ssl_cert_issuer'} || ''; my $ssl_altnames = $mark->{'ssl_cert_altnames'} || ''; my $ssl = defined $mark->{'ssl_cipher'} ? 1 : 0; # Extract CN from subject my $ssl_cn = ''; if ($ssl_subject =~ /CN=([^$ \/]+)/) { $ssl_cn = $1; } # Build combined SSL info message my $ssl_message = "SSL/TLS Information - "; $ssl_message .= "Subject: $ssl_subject" if $ssl_subject ne ''; $ssl_message .= "; CN: $ssl_cn" if $ssl_cn ne ''; $ssl_message .= "; SAN: $ssl_altnames" if $ssl_altnames ne ''; $ssl_message .= "; Ciphers: $ssl_cipher" if $ssl_cipher ne ''; $ssl_message .= "; Issuer: $ssl_issuer" if $ssl_issuer ne ''; $ssl_message =~ s/'/\\'/g; # Single INSERT statement with all SSL info my $sql = "insert into nikto_table (scanid, testid, ip, hostname, port, tls, refs, httpmethod, uri, message) values("; $sql .= "'$mark->{'scanid'}','000137','$mark->{'ip'}','$hostname','$mark->{'port'}','$ssl','0','GET','/','$ssl_message');\n"; print $handle $sql; } ############################################################################### # print an item sub sqlg_item { my ($handle, $mark, $item) = @_; foreach my $uri (split(' ', $item->{'uri'})) { my $hostname = $mark->{'vhost'} ? $mark->{'vhost'} : $mark->{'hostname'}; $hostname = quotemeta($hostname); my $httpmethod = quotemeta($item->{'method'}); my $msg = quotemeta($item->{'message'}); my $root = quotemeta($mark->{'root'}); my $rootq = quotemeta($mark->{'root'}); # temporary, just for regex $uri = quotemeta($uri); my $ssl = $mark->{'ssl_cipher'} ? 1 : 0; # Get scanid (generated by core in report_host_start) my $scanid = $item->{'mark'}->{'scanid'} || ''; $scanid =~ s/'/\\'/g; # Escape single quotes my $sql = "insert into nikto_table (scanid, testid, ip, hostname, port, tls, refs, httpmethod, uri, message, request, response) values("; $sql .= "'$scanid','$item->{'nikto_id'}','$item->{'mark'}->{'ip'}','$hostname','$item->{'mark'}->{'port'}','$ssl',"; $sql .= "'$item->{'refs'}','$httpmethod',"; if (($uri ne '') && ($root ne '') && ($uri !~ /^$rootq/)) { $sql .= "'" . $root . $uri . "',"; } else { $sql .= "'$uri',"; } $msg =~ s/^$uri:\s//; $msg =~ s/^$rootq$uri:\s//; $sql .= "'$msg',"; # Rebuild the request from the hash -- no need to escape as it's base64 encoded my $req = rebuild_request($item->{'request'}, 1, 48000); $sql .= "'" . LW2::encode_base64($req, '') . "',"; # response content my $response = rebuild_response($$item{'response'}, 1, 12000000); $sql .= "'" . LW2::encode_base64($response, '') . "');"; print $handle "$sql\n"; } } 1;