#!/usr/bin/perl -w use strict; use warnings; use feature qw( switch ); no warnings "experimental::smartmatch"; use Term::ANSIColor; use Getopt::Long qw( :config no_ignore_case bundling ); use Data::Dumper; my ($help,$verbose,$excel,$output); GetOptions( 'h|help' => \$help, 'v|verbose+' => \$verbose, 'E|excel' => \$excel, 'o|output=s' => \$output, ); 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 "")); my $lynis_log = '/var/log/lynis.log'; my $lynis_report = '/var/log/lynis-report.dat'; my $audit_run = 0; #assume false my %lynis_report_data; if (( -e $lynis_log) and ( ! -z $lynis_log )) { print colored("Found lynis output log. \n", "cyan") if ($verbose); $audit_run++; } if (( -e $lynis_report) and ( ! -z $lynis_report )) { print colored("Found lynis report. \n", "cyan") if ($verbose); $audit_run++; } if (($audit_run) and ($audit_run >= 1)) { print "Looks like the audit has been run. \n"; } else { print colored("Couldn't find one or more of the lynis output files. Try running the audit again. \n", "bold red"); } print "Outputting report to $output, in "; if ($excel) { print "Excel " } else { print "HTML "; } print "format. \n"; # the report is easy to process, and actually doesn't contain the "audit findings"....just the data. # but it is not our job to draw conclusions here, just present the findings of the tool. open RPT, "<$lynis_report" or die colored("There was a problem opening the lynis report: $! \n", "bold red"); while (my $line = ) { next if ($line =~ /^#/); # skip commented lines chomp($line); my ($k, $v) = split(/=/, $line); print "k=$k\n" if (($verbose) and ($verbose > 1)); print "v=$v\n" if (($verbose) and ($verbose > 1)); # if the key already exists, assume it's supposed to be an array value. Array values are handled a couple # different ways in the lynis report. This is just one. if (exists($lynis_report_data{$k})) { if (ref($lynis_report_data{$k}) eq 'ARRAY') { push @{$lynis_report_data{$k}}, $v; } else { my $tmp_v = $lynis_report_data{$k}; undef($lynis_report_data{$k}); push @{$lynis_report_data{$k}}, $tmp_v, $v; } } else { $lynis_report_data{$k} = $v; } } close RPT or die colored("There was a problem closing the lynis report: $! \n", "bold red"); my (%warnings, %suggestions); #foreach my $warn ( sort @{$lynis_report_data{'warning[]'}} ) { # my ($warn_id,$descr, $sev, $field4) = split(/\|/, $warn); # $warnings{$warn_id}{'id'} = $warn_id; # $warnings{$warn_id}{'descr'} = $descr; # $warnings{$warn_id}{'severity'} = $sev; # $warnings{$warn_id}{'f4'} = $field4; #} #delete($lynis_report_data{'warning[]'}); # process "string array" values delimited by a pipe (|) foreach my $key ( sort keys %lynis_report_data ) { print "$key, ".ref($lynis_report_data{$key})." \n" if (($verbose) and ($verbose >= 1)); if (((ref($lynis_report_data{$key}) ne 'ARRAY') and (ref($lynis_report_data{$key}) ne 'HASH')) and ($lynis_report_data{$key} =~ /\|/)) { my @fs = split(/\|/, $lynis_report_data{$key}); undef($lynis_report_data{$key}); push @{$lynis_report_data{$key}}, @fs; } } my (@tests_skipped, @tests_executed); my ($lynis_version); @tests_skipped = @{$lynis_report_data{'tests_skipped'}}; delete($lynis_report_data{'tests_skipped'}); @tests_executed = @{$lynis_report_data{'tests_executed'}}; 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 <

lynis Asset Report

created by lynis_report

lynis infohost info

host findings:

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"; } when (($lynis_report_data{'hardening_index'} <= 90) and ($lynis_report_data{'hardening_index'} > 80)) { # yellow print OUT "\t\t\t"; } when (($lynis_report_data{'hardening_index'} <= 80) and ($lynis_report_data{'hardening_index'} > 65)) { # orange print OUT "\t\t\t"; } when ($lynis_report_data{'hardening_index'} <= 65) { # red print OUT "\t\t\t"; } default { # error } } print OUT "\t\t
hardening index:$lynis_report_data{'hardening_index'}$lynis_report_data{'hardening_index'}$lynis_report_data{'hardening_index'}$lynis_report_data{'hardening_index'}
\n"; print OUT "

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

\n"; print OUT < Warning IDDescriptionSeverityF4 END 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 "$warn_id$warn_desc$to_long_severity{$warn_sev}$warn_f4\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 "$warn_id$warn_desc$to_long_severity{$warn_sev}$warn_f4\n"; } else { die colored("Unexpected ARRAY format! \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

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

\n"; print OUT < Suggestion IDDescriptionSeverityF4 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 = '-'; } print OUT "\t\t\t$sug_id$sug_desc$sug_sev$sug_f4\n"; } } print OUT <

manual checks:

    END foreach my $man ( sort @{$lynis_report_data{'manual[]'}} ) { #print Dumper($man); print OUT "
  • $man
  • \n"; } print OUT <

    lynis info:

    END print OUT "\t\t\t\t\n"; print OUT <
    lynis version:$lynis_report_data{'lynis_version'}lynis tests done:$lynis_report_data{'lynis_tests_done'}
    lynis update available:$to_bool{$lynis_report_data{'lynis_update_available'}}license key:$lynis_report_data{'license_key'}
    report version:$lynis_report_data{'report_version_major'}.$lynis_report_data{'report_version_minor'}
    number of plugins enabled:$lynis_report_data{'plugins_enabled'}plugin directory:$lynis_report_data{'plugin_directory'}
    phase 1 plugins enabled:"; print OUT "\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\n"; } print OUT "\t\t\t\t\t
    name:$nversion:$v
    \n"; print OUT "
    report start time:$lynis_report_data{'report_datetime_start'}report end time:$lynis_report_data{'report_datetime_end'}
    hostid:$lynis_report_data{'hostid'}
    hostid:$lynis_report_data{'hostid2'}

    host info:

    END print OUT "\t\t\t\n"; print OUT <
    hostname:$lynis_report_data{'hostname'}domainname:$lynis_report_data{'domainname'}resolv.conf domain:$lynis_report_data{'resolv_conf_domain'}
    os:$lynis_report_data{'os'}os fullname:$lynis_report_data{'os_fullname'}os_version:$lynis_report_data{'os_version'}
    GRSecurity:$to_bool{$lynis_report_data{'framework_grsecurity'}}SELinux:$to_bool{$lynis_report_data{'framework_selinux'}}memory:$lynis_report_data{'memory_size'} $lynis_report_data{'memory_units'}
    linux version:$lynis_report_data{'linux_version'}pae enabled:$to_bool{$lynis_report_data{'cpu_pae'}}nx enabled:$to_bool{$lynis_report_data{'cpu_nx'}}
    network interfaces:".join("
    \n", @{$lynis_report_data{'network_interface[]'}})."
    ipv4 addresses:".join("
    \n", @{$lynis_report_data{'network_ipv4_address[]'}})."
    ipv6 addresses:".join("
    \n", @{$lynis_report_data{'network_ipv6_address[]'}})."
    kernel version:$lynis_report_data{'linux_kernel_version'}kernel release version:$lynis_report_data{'linux_kernel_release'}uptime (days):$lynis_report_data{'uptime_in_days'}
    vm:$to_bool{$lynis_report_data{'vm'}}vm_type:$lynis_report_data{'vmtype'}uptime (secs):$lynis_report_data{'uptime_in_seconds'}
    END close OUT or die colored("There was a proble closing the output file ($output): $! \n", "bold red"); my @indexes = qw( lynis_version lynis_tests_done lynis_update_available license_key report_datetime_start report_datetime_end plugins_directory plugins_enabled finish report_version_major report_version_minor hostid hostid2 plugin_enabled_phase1[] hardening_index warning[] hostname domainname linux_kernel_version linux_config_file memory_size nameserver[] network_interface[] framework_grsecurity vm vmtype uptime_in_seconds linux_kernel_release os framework_selinux uptime_in_days resolv_conf_domain os_fullname default_gateway cpu_nx cpu_pae linux_version os_version network_ipv6_address[] boot_loader suggestion[] manual manual[] linux_version cpu_pae cpu_nx network_ipv4_address[] network_ipv6_address[] network_interfaces[] ); foreach my $idx ( sort @indexes ) { delete($lynis_report_data{$idx}); } print Dumper(\%lynis_report_data);