diff --git a/lynis_report.pl b/lynis_report.pl index 97c7488..2278702 100755 --- a/lynis_report.pl +++ b/lynis_report.pl @@ -7,6 +7,8 @@ no if $] ge '5.018', warnings => "experimental::smartmatch"; use Term::ANSIColor; use Getopt::Long qw( :config no_ignore_case bundling ); use Data::Dumper; +#use Spreadsheet::WriteExcel; +use Excel::Writer::XLSX; my ($help,$verbose,$excel,$output); GetOptions( @@ -21,7 +23,11 @@ if ($help) { &usage; } my %to_bool = ( 0 => 'false', 1 => 'true' ); my %to_long_severity = ( 'C' => 'Critical', 'S' => 'Severe', 'H' => 'High', 'M' => 'Medium', 'L' => 'Low', 'I' => 'Informational' ); -$output = "report.html" unless ((defined($output)) and ($output ne "")); +if ($excel) { + $output = 'report.xlsx' unless ((defined($output)) and ($output ne "")); +} else { + $output = "report.html" unless ((defined($output)) and ($output ne "")); +} my $lynis_log = '/var/log/lynis.log'; my $lynis_report = '/var/log/lynis-report.dat'; @@ -112,8 +118,48 @@ delete($lynis_report_data{'tests_executed'}); #print Dumper(\%warnings); -open OUT, ">$output" or die colored("There was a problem opening the output file ($output): $! \n", "bold red"); -print OUT <new($output); + my $title_format = $wb->add_format(); + $title_format->set_size('32'); + + my $subtitle_format = $wb->add_format(); + $subtitle_format->set_size('24'); + + my $subsub_format = $wb->add_format(); + $subsub_format->set_size('16'); + + my $summary_ws = $wb->add_worksheet('Summary'); + $summary_ws->write('B2', "lynis Asset Report", $title_format); + $summary_ws->write('B3', "created by "); + $summary_ws->write_url('C3', "http://github.com/d4t4king/lynis_report.git", '', 'lynis_report'); + $summary_ws->write('A4', "Host Findings:", $subtitle_format); + $summary_ws->write('A5', "hardening index:"); + $summary_ws->write('B5', $lynis_report_data{'hardening_index'}); + $summary_ws->write('A7', "warnings \(".scalar(@{$lynis_report_data{'warning[]'}})."\):", $subsub_format); + my @table_data; + my $header_row = [ 'Warning ID', 'Description', 'Severity', 'F4' ]; + if (exists($lynis_report_data{'warning[]'})) { + if (ref($lynis_report_data{'warning[]'}) eq 'ARRAY') { + if ($lynis_report_data{'warning[]'}[0] =~ /\|/) { + foreach my $warn ( sort @{$lynis_report_data{'warning[]'}} ) { + my ($warn_id,$warn_desc,$warn_sev,$warn_f4) = split(/\|/, $warn); + push @table_data, [$warn_id,$warn_desc,$warn_sev,$warn_f4]; + } + } + } + } + my %params = ( + 'data' => \@table_data, + 'header_row' => $header_row, + ); + my $last_row_number = 8 + scalar(@table_data); + $summary_ws->add_table("A8:D$last_row_number", \%params); + +} else { + open OUT, ">$output" or die colored("There was a problem opening the output file ($output): $! \n", "bold red"); + print OUT < @@ -171,99 +217,99 @@ print OUT <hardening index: END -given ($lynis_report_data{'hardening_index'}) { - when (($lynis_report_data{'hardening_index'} < 100) and ($lynis_report_data{'hardening_index'} > 90)) { - # green - print OUT "\t\t\t\t$lynis_report_data{'hardening_index'}"; + given ($lynis_report_data{'hardening_index'}) { + when (($lynis_report_data{'hardening_index'} < 100) and ($lynis_report_data{'hardening_index'} > 90)) { + # green + print OUT "\t\t\t\t$lynis_report_data{'hardening_index'}"; + } + when (($lynis_report_data{'hardening_index'} <= 90) and ($lynis_report_data{'hardening_index'} > 80)) { + # yellow + print OUT "\t\t\t\t$lynis_report_data{'hardening_index'}"; + } + when (($lynis_report_data{'hardening_index'} <= 80) and ($lynis_report_data{'hardening_index'} > 65)) { + # orange + print OUT "\t\t\t\t$lynis_report_data{'hardening_index'}"; + } + when ($lynis_report_data{'hardening_index'} <= 65) { + # red + print OUT "\t\t\t\t$lynis_report_data{'hardening_index'}"; + } + default { + # error + } } - when (($lynis_report_data{'hardening_index'} <= 90) and ($lynis_report_data{'hardening_index'} > 80)) { - # yellow - print OUT "\t\t\t\t$lynis_report_data{'hardening_index'}"; - } - when (($lynis_report_data{'hardening_index'} <= 80) and ($lynis_report_data{'hardening_index'} > 65)) { - # orange - print OUT "\t\t\t\t$lynis_report_data{'hardening_index'}"; - } - when ($lynis_report_data{'hardening_index'} <= 65) { - # red - print OUT "\t\t\t\t$lynis_report_data{'hardening_index'}"; - } - default { - # error - } -} -print OUT "\t\t\t\n"; -if (!exists($lynis_report_data{'warning[]'})) { - print OUT "

warnings (0):

\n"; -} else { - print OUT "

warnings (".scalar(@{$lynis_report_data{'warning[]'}})."):

\n"; -} -print OUT <\n"; + if (!exists($lynis_report_data{'warning[]'})) { + print OUT "

warnings (0):

\n"; + } else { + print OUT "

warnings (".scalar(@{$lynis_report_data{'warning[]'}})."):

\n"; + } + print OUT < END -if (exists($lynis_report_data{'warning[]'})) { - if (ref($lynis_report_data{'warning[]'}) eq 'ARRAY') { - if (${$lynis_report_data{'warning[]'}}[0] =~ /\|/) { # more than one - foreach my $warn ( sort @{$lynis_report_data{'warning[]'}} ) { - my ($warn_id,$warn_desc,$warn_sev,$warn_f4) = split(/\|/, $warn); + if (exists($lynis_report_data{'warning[]'})) { + if (ref($lynis_report_data{'warning[]'}) eq 'ARRAY') { + if (${$lynis_report_data{'warning[]'}}[0] =~ /\|/) { # more than one + foreach my $warn ( sort @{$lynis_report_data{'warning[]'}} ) { + my ($warn_id,$warn_desc,$warn_sev,$warn_f4) = split(/\|/, $warn); + print OUT "\t\t\t\t\t\n"; + } + } elsif (${$lynis_report_data{'warning[]'}}[0] =~ /[A-Z]{4}\-\d{4}/) { # one warning + my $warn_id = ${$lynis_report_data{'warning[]'}}[0]; + my $warn_desc = ${$lynis_report_data{'warning[]'}}[1]; + my $warn_sev = ${$lynis_report_data{'warning[]'}}[2]; + my $warn_f4 = ${$lynis_report_data{'warning[]'}}[3]; print OUT "\t\t\t\t\t\n"; + } else { + die colored("Unexpected ARRAY format! \n", "bold red"); } - } elsif (${$lynis_report_data{'warning[]'}}[0] =~ /[A-Z]{4}\-\d{4}/) { # one warning - my $warn_id = ${$lynis_report_data{'warning[]'}}[0]; - my $warn_desc = ${$lynis_report_data{'warning[]'}}[1]; - my $warn_sev = ${$lynis_report_data{'warning[]'}}[2]; - my $warn_f4 = ${$lynis_report_data{'warning[]'}}[3]; - print OUT "\t\t\t\t\t\n"; } else { - die colored("Unexpected ARRAY format! \n", "bold red"); + die colored("warning[] not ARRAY ref!: ".ref($lynis_report_data{'warning[]'})."\n", "bold red"); } - } else { - die colored("warning[] not ARRAY ref!: ".ref($lynis_report_data{'warning[]'})."\n", "bold red"); } -} -print OUT < END -print OUT "\t\t\t

suggestions (".scalar(@{$lynis_report_data{'suggestion[]'}})."):

\n"; -print OUT <suggestions (".scalar(@{$lynis_report_data{'suggestion[]'}})."):\n"; + print OUT <
Warning IDDescriptionSeverityF4
$warn_id$warn_desc$to_long_severity{$warn_sev}$warn_f4
$warn_id$warn_desc$to_long_severity{$warn_sev}$warn_f4
$warn_id$warn_desc$to_long_severity{$warn_sev}$warn_f4
END -if ((ref($lynis_report_data{'suggestion[]'}) eq 'ARRAY') and - (${$lynis_report_data{'suggestion[]'}}[0] =~ /\|/)) { - foreach my $sug ( sort @{$lynis_report_data{'suggestion[]'}} ) { - my ($sug_id,$sug_desc,$sug_sev,$sug_f4,$sug_f5) = split(/\|/, $sug); - if ($sug_desc eq 'Consider hardening SSH configuration') { - $sug_desc .= ": $sug_sev"; $sug_sev = '-'; + if ((ref($lynis_report_data{'suggestion[]'}) eq 'ARRAY') and + (${$lynis_report_data{'suggestion[]'}}[0] =~ /\|/)) { + foreach my $sug ( sort @{$lynis_report_data{'suggestion[]'}} ) { + my ($sug_id,$sug_desc,$sug_sev,$sug_f4,$sug_f5) = split(/\|/, $sug); + if ($sug_desc eq 'Consider hardening SSH configuration') { + $sug_desc .= ": $sug_sev"; $sug_sev = '-'; + } + print OUT "\t\t\t\t\t"; + print OUT ""; + print OUT ""; + print OUT "\n"; } - print OUT "\t\t\t\t\t"; - print OUT ""; - print OUT ""; - print OUT "\n"; } -} -print OUT <

manual checks:

    END -if ((exists($lynis_report_data{'manual[]'})) and (scalar(@{$lynis_report_data{'manual[]'}}) > 0)) { - foreach my $man ( sort @{$lynis_report_data{'manual[]'}} ) { - #print Dumper($man); - chomp($man); - print OUT "
  • $man
  • \n"; + if ((exists($lynis_report_data{'manual[]'})) and (scalar(@{$lynis_report_data{'manual[]'}}) > 0)) { + foreach my $man ( sort @{$lynis_report_data{'manual[]'}} ) { + #print Dumper($man); + chomp($man); + print OUT "
  • $man
  • \n"; + } } -} -# It's easier to move stuff around if there is one cell (or cell group) per libe for the tables. Maybe this -# isn't ideal HTML writing, but it makes sense when writing the tool. -print OUT <

    lynis info:

    @@ -276,12 +322,12 @@ print OUT <
END -if ((defined($lynis_report_data{'license_key'})) and ($lynis_report_data{'license_key'} ne "")) { - print OUT "\n\n\n\n\n\n\n"; -} else { - print OUT "\n\n\n\n\n\n\n"; -} -print OUT <license key:\n"; + } else { + print OUT "\n\n\n\n\n\n\n"; + } + print OUT < @@ -293,15 +339,15 @@ print OUT < END -print OUT "\t\t\t\t\t\t\n"; -print OUT <phase 1 plugins enabled:\n"; + print OUT < @@ -318,12 +364,12 @@ print OUT <hostname: END -if ((defined($lynis_report_data{'resolv_conf_domain'})) and ($lynis_report_data{'resolv_conf_domain'} ne "")) { - print OUT "\t\t\t\t\t\t\n"; -} else { - print OUT "\t\t\t\t\t\t\n"; -} -print OUT <resolv.conf domain:\n"; + } else { + print OUT "\t\t\t\t\t\t\n"; + } + print OUT < @@ -342,20 +388,20 @@ print OUT < END -print OUT "\t\t\t\t\t\t\n"; -print OUT <Available shells:\n"; + print OUT <locate db: END -if ((defined($lynis_report_data{'vmtype'})) and ($lynis_report_data{'vmtype'} ne "")) { - print OUT "\t\t\t\t\t\t\n"; -} else{ - print OUT "\t\t\t\t\t\t\n"; -} -print OUT <vm_type:\n"; + } else{ + print OUT "\t\t\t\t\t\t\n"; + } + print OUT <uptime (secs):
Suggestion IDDescriptionSeverityF4
$sug_id$sug_desc".($sug_sev ? $sug_sev : " ")."".($sug_f4 ? $sug_f4 : " ")."
$sug_id$sug_desc".($sug_sev ? $sug_sev : " ")."".($sug_f4 ? $sug_f4 : " ")."
lynis update available:$to_bool{$lynis_report_data{'lynis_update_available'}}license key:$lynis_report_data{'license_key'}license key: $lynis_report_data{'license_key'}license key: 
report version:$lynis_report_data{'report_version_major'}.$lynis_report_data{'report_version_minor'}phase 1 plugins enabled:\n"; -print OUT "\t\t\t\t\t\t\t\n"; -foreach my $plug ( sort @{$lynis_report_data{'plugin_enabled_phase1[]'}} ) { - my ($n,$v) = split(/\|/, $plug); - print OUT "\t\t\t\t\t\t\t\t\n"; -} -print OUT "\t\t\t\t\t\t\t
name:$nversion:$v
\n"; -print OUT "\t\t\t\t\t\t
\n"; + print OUT "\t\t\t\t\t\t\t\n"; + foreach my $plug ( sort @{$lynis_report_data{'plugin_enabled_phase1[]'}} ) { + my ($n,$v) = split(/\|/, $plug); + print OUT "\t\t\t\t\t\t\t\t\n"; + } + print OUT "\t\t\t\t\t\t\t
name:$nversion:$v
\n"; + print OUT "\t\t\t\t\t\t
report start time:$lynis_report_data{'report_datetime_start'}report end time:$lynis_report_data{'report_datetime_end'}$lynis_report_data{'hostname'} domainname:$lynis_report_data{'domainname'}resolv.conf domain:$lynis_report_data{'resolv_conf_domain'}resolv.conf domain: $lynis_report_data{'resolv_conf_domain'}resolv.conf domain: 
os:$lynis_report_data{'os'}
Available shells:".join("
\n", @{$lynis_report_data{'available_shell[]'}})."
".join("
\n", @{$lynis_report_data{'available_shell[]'}})."
$lynis_report_data{'locate_db'} uptime (days):$lynis_report_data{'uptime_in_days'}
vm:$lynis_report_data{'vm'}vm_type:$lynis_report_data{'vmtype'}vm_type: $lynis_report_data{'vmtype'}vm_type: $lynis_report_data{'uptime_in_seconds'}
@@ -369,22 +415,22 @@ print OUT <IPv6 Only:$to_bool{$lynis_report_data{'ipv6_only'}} END -print OUT "\t\t\t\t\tnetwork interfaces:".join("
\n", @{$lynis_report_data{'network_interface[]'}})."\n"; -print OUT "\t\t\t\t\tipv4 addresses:".join("
\n", @{$lynis_report_data{'network_ipv4_address[]'}})."\n"; -print OUT "\t\t\t\t\tipv6 addresses:".join("
\n", @{$lynis_report_data{'network_ipv6_address[]'}})."\n"; -print OUT "\t\t\t\t\tDefault Gateway$lynis_report_data{'default_gateway[]'}\n"; -print OUT <network interfaces:".join("
\n", @{$lynis_report_data{'network_interface[]'}})."\n"; + print OUT "\t\t\t\t\tipv4 addresses:".join("
\n", @{$lynis_report_data{'network_ipv4_address[]'}})."\n"; + print OUT "\t\t\t\t\tipv6 addresses:".join("
\n", @{$lynis_report_data{'network_ipv6_address[]'}})."\n"; + print OUT "\t\t\t\t\tDefault Gateway$lynis_report_data{'default_gateway[]'}\n"; + print OUT < END -#print STDERR "Should be ARRAY: |".ref($lynis_report_data{'network_mac_address[]'})."|\n"; -if (ref($lynis_report_data{'network_mac_address[]'}) eq "ARRAY") { - print OUT "\t\t\t\t\t\tMAC Address:".join("
\n", @{$lynis_report_data{'network_mac_address[]'}})."\n"; -} elsif ((defined($lynis_report_data{'network_mac_address[]'})) and ($lynis_report_data{'network_mac_address[]'} ne "")) { - print OUT "\t\t\t\t\t\tMAC Address:$lynis_report_data{'network_mac_address[]'}\n"; -} else { - print OUT "\t\t\t\t\t\tMAC Address: \n"; -} -print OUT <MAC Address:".join("
\n", @{$lynis_report_data{'network_mac_address[]'}})."\n"; + } elsif ((defined($lynis_report_data{'network_mac_address[]'})) and ($lynis_report_data{'network_mac_address[]'} ne "")) { + print OUT "\t\t\t\t\t\tMAC Address:$lynis_report_data{'network_mac_address[]'}\n"; + } else { + print OUT "\t\t\t\t\t\tMAC Address: \n"; + } + print OUT <Name Cache Used:$to_bool{$lynis_report_data{'name_cache_used'}} @@ -393,22 +439,22 @@ print OUT <IP AddressPortProtocolDaemon/Process??? END -foreach my $obj ( sort @{$lynis_report_data{'network_listen_port[]'}} ) { - my ($ipp,$proto,$daemon,$dunno) = split(/\|/, $obj); - my ($ip,$port); - my $colon_count = grep(/\:/, split(//, $ipp)); - if ($colon_count > 1) { - # must be an IPv6 address; - my @parts = split(/\:/, $ipp); - $port = pop(@parts); - $ip = join(":", @parts); - } else { - # must be IPv4 - ($ip,$port) = split(/\:/, $ipp); + foreach my $obj ( sort @{$lynis_report_data{'network_listen_port[]'}} ) { + my ($ipp,$proto,$daemon,$dunno) = split(/\|/, $obj); + my ($ip,$port); + my $colon_count = grep(/\:/, split(//, $ipp)); + if ($colon_count > 1) { + # must be an IPv6 address; + my @parts = split(/\:/, $ipp); + $port = pop(@parts); + $ip = join(":", @parts); + } else { + # must be IPv4 + ($ip,$port) = split(/\:/, $ipp); + } + print OUT "\t\t\t\t\t$ip$port$proto$daemon$dunno\n"; } - print OUT "\t\t\t\t\t$ip$port$proto$daemon$dunno\n"; -} -print OUT <
@@ -441,8 +487,8 @@ print OUT < END -printf OUT "\t\t\t\t\t\tPassword Complexity Score:%#b\n", $pass_score; -print OUT <Password Complexity Score:%#b\n", $pass_score; + print OUT <PAM Cracklib Found:$to_bool{$lynis_report_data{'pam_cracklib'}} Password Strength Tested:$to_bool{$lynis_report_data{'password_strength_tested'}} Failed Logins Logged:$lynis_report_data{'auth_failed_logins_logged'} @@ -452,24 +498,24 @@ print OUT <File Integrity Tool:$lynis_report_data{'file_integrity_tool'} Automation Tool Present:$to_bool{$lynis_report_data{'automation_tool_present'}} END -if (ref($lynis_report_data{'automation_tool_running[]'}) eq 'ARRAY') { - print OUT "\t\t\t\t\t\tAutomation Tool:".join("
\n", @{$lynis_report_data{'automation_tool_running[]'}})."\n"; -} elsif ((defined($lynis_report_data{'automation_tool_running[]'})) and ($lynis_report_data{'automation_tool_running[]'} ne "")) { - print OUT "\t\t\t\t\t\tAutomation Tool:$lynis_report_data{'automation_tool_running[]'}\n"; -} else { - print OUT "\t\t\t\t\t\tAutomation Tool: \n"; -} -print OUT <Automation Tool:".join("
\n", @{$lynis_report_data{'automation_tool_running[]'}})."\n"; + } elsif ((defined($lynis_report_data{'automation_tool_running[]'})) and ($lynis_report_data{'automation_tool_running[]'} ne "")) { + print OUT "\t\t\t\t\t\tAutomation Tool:$lynis_report_data{'automation_tool_running[]'}\n"; + } else { + print OUT "\t\t\t\t\t\tAutomation Tool: \n"; + } + print OUT < Malware Scanner Installed:$to_bool{$lynis_report_data{'malware_scanner_installed'}} END -if (exists($lynis_report_data{'ids_ips_tooling[]'})) { - print OUT "\t\t\t\t\t\tIDS/IPS Tooling$lynis_report_data{'ids_ips_tooling[]'}\n"; -} else { - print OUT "\t\t\t\t\t\tIDS/IPS Tooling \n"; -} -print OUT <IDS/IPS Tooling$lynis_report_data{'ids_ips_tooling[]'}\n"; + } else { + print OUT "\t\t\t\t\t\tIDS/IPS Tooling \n"; + } + print OUT < @@ -478,30 +524,30 @@ print OUT <