diff --git a/t/01_badssl.com.t b/t/01_badssl.com.t index 8c91e07..01ca476 100755 --- a/t/01_badssl.com.t +++ b/t/01_badssl.com.t @@ -16,6 +16,7 @@ my ( pass("Running testssl.sh against badssl.com to create a baseline (may take 2~3 minutes)"); $tests++; my $okout = `./testssl.sh -S -e -U --jsonfile tmp.json --color 0 badssl.com`; my $okjson = json('tmp.json'); +unlink 'tmp.json'; cmp_ok(@$okjson,'>',10,"We have more then 10 findings"); $tests++; # Expiration @@ -23,6 +24,7 @@ pass("Running testssl against expired.badssl.com"); $tests++; $out = `./testssl.sh -S --jsonfile tmp.json --color 0 expired.badssl.com`; like($out, qr/Certificate Expiration\s+expired\!/,"The certificate should be expired"); $tests++; $json = json('tmp.json'); +unlink 'tmp.json'; $found = 0; foreach my $f ( @$json ) { if ( $f->{id} eq "expiration" ) { @@ -39,6 +41,7 @@ pass("Running testssl against self-signed.badssl.com"); $tests++; $out = `./testssl.sh -S --jsonfile tmp.json --color 0 self-signed.badssl.com`; like($out, qr/Certificate Expiration\s+\d+/,"The certificate should not be expired"); $tests++; $json = json('tmp.json'); +unlink 'tmp.json'; $found = 0; foreach my $f ( @$json ) { if ( $f->{id} eq "expiration" ) { @@ -79,6 +82,7 @@ is($found,1,"We had a finding for this in the JSON output"); $tests++; #$out = `./testssl.sh -S --jsonfile tmp.json --color 0 wrong.host.badssl.com`; #unlike($out, qr/Certificate Expiration\s+expired\!/,"The certificate should not be expired"); $tests++; #$json = json('tmp.json'); +#unlink 'tmp.json'; #$found = 0; #foreach my $f ( @$json ) { # if ( $f->{id} eq "expiration" ) { @@ -95,6 +99,7 @@ pass("Running testssl against incomplete-chain.badssl.com"); $tests++; $out = `./testssl.sh -S --jsonfile tmp.json --color 0 incomplete-chain.badssl.com`; like($out, qr/Chain of trust.*?NOT ok\s+\(chain incomplete\)/,"Chain of trust should fail because of incomplete"); $tests++; $json = json('tmp.json'); +unlink 'tmp.json'; $found = 0; foreach my $f ( @$json ) { if ( $f->{id} eq "chain_of_trust" ) { @@ -113,6 +118,7 @@ is($found,1,"We had a finding for this in the JSON output"); $tests++; #$out = `./testssl.sh -e -U --jsonfile tmp.json --color 0 cbc.badssl.com`; #like($out, qr/Chain of trust.*?NOT ok\s+\(chain incomplete\)/,"Chain of trust should fail because of incomplete"); $tests++; #$json = json('tmp.json'); +#unlink 'tmp.json'; #$found = 0; #foreach my $f ( @$json ) { # if ( $f->{id} eq "chain_of_trust" ) { @@ -132,4 +138,4 @@ sub json($) { $file = `cat $file`; unlink $file; return from_json($file); -} \ No newline at end of file +} diff --git a/t/100_report_structure.t b/t/100_report_structure.t index 5fdfb47..b933b4b 100644 --- a/t/100_report_structure.t +++ b/t/100_report_structure.t @@ -19,6 +19,7 @@ $tests = 0; pass("Running testssl.sh against badssl.com to create a JSON report with severity level equal greater than LOW (may take 2~3 minutes)"); $tests++; $out = `./testssl.sh -S -e -U --jsonfile tmp.json --severity LOW --color 0 badssl.com`; $json = json('tmp.json'); +unlink 'tmp.json'; $found = 0; cmp_ok(@$json,'>',0,"At least 1 finding is expected"); $tests++; foreach my $f ( @$json ) { @@ -33,6 +34,7 @@ is($found,0,"We should not have any finding with INFO level"); $tests++; pass("Running testssl.sh against badssl.com to create a JSON-PRETTY report with severity level equal greater than LOW (may take 2~3 minutes)"); $tests++; $out = `./testssl.sh -S -e -U --jsonfile-pretty tmp.json --severity LOW --color 0 badssl.com`; $json_pretty = json('tmp.json'); +unlink 'tmp.json'; $found = 0; my $vulnerabilities = $json_pretty->{scanResult}->[0]->{vulnerabilities}; foreach my $f ( @$vulnerabilities ) { @@ -50,4 +52,4 @@ sub json($) { $file = `cat $file`; unlink $file; return from_json($file); -} \ No newline at end of file +} diff --git a/t/11_hpkp.t b/t/11_hpkp.t index b701558..dc5464b 100755 --- a/t/11_hpkp.t +++ b/t/11_hpkp.t @@ -16,6 +16,7 @@ my ( pass("Running testssl.sh against ssl.sectionzero.org"); $tests++; $out = `./testssl.sh -H --jsonfile tmp.json --color 0 ssl.sectionzero.org`; $json = json('tmp.json'); +unlink 'tmp.json'; # It is better to have findings in a hash # Look for a host cert match in the process. diff --git a/testssl.sh b/testssl.sh index 9bdcb7c..4697baa 100755 --- a/testssl.sh +++ b/testssl.sh @@ -186,6 +186,7 @@ GIVE_HINTS=false # give an addtional info to findings HAS_IPv6=${HAS_IPv6:-false} # if you have OpenSSL with IPv6 support AND IPv6 networking set it to yes UNBRACKTD_IPV6=${UNBRACKTD_IPV6:-false} # some versions of OpenSSL (like Gentoo) don't support [bracketed] IPv6 addresses SERVER_SIZE_LIMIT_BUG=false # Some servers have either a ClientHello total size limit or a 128 cipher limit (e.g. old ASAs) +CHILD_MASS_TESTING=${CHILD_MASS_TESTING:-false} # tuning vars, can not be set by a cmd line switch EXPERIMENTAL=${EXPERIMENTAL:-false} @@ -866,6 +867,8 @@ fileout_json_print_parameter() { } fileout_json_finding() { + local target + if "$do_json"; then "$FIRST_FINDING" || echo -n "," >> "$JSONFILE" echo -e " {" >> "$JSONFILE" @@ -884,9 +887,19 @@ fileout_json_finding() { if [[ $SERVER_COUNTER -gt 1 ]]; then echo " ," >> "$JSONFILE" fi - echo -e " { + if "$CHILD_MASS_TESTING" && ! "$JSONHEADER"; then + target="$NODE" + $do_mx_all_ips && target="$URI" + echo -e " { + \"target host\" : \"$target\", + \"port\" : \"$PORT\", \"service\" : \"$finding\", \"ip\" : \"$NODEIP\"," >> "$JSONFILE" + else + echo -e " { + \"service\" : \"$finding\", + \"ip\" : \"$NODEIP\"," >> "$JSONFILE" + fi $do_mx_all_ips && echo -e " \"hostname\" : \"$NODE\"," >> "$JSONFILE" else ("$FIRST_FINDING" && echo -n " {" >> "$JSONFILE") || echo -n ",{" >> "$JSONFILE" @@ -905,7 +918,22 @@ fileout_json_finding() { ##################### FILE FORMATING ######################### fileout_pretty_json_banner() { - echo -e " \"Invocation\" : \"$PROG_NAME $CMDLINE\", + local target + + if "$do_mass_testing"; then + echo -e " \"Invocation\" : \"$PROG_NAME $CMDLINE\", + \"at\" : \"$HNAME:$OPENSSL_LOCATION\", + \"version\" : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\", + \"openssl\" : \"$OSSL_VER from $OSSL_BUILD_DATE\", + \"startTime\" : \"$START_TIME\", + \"scanResult\" : [" + else + [[ -z "$NODE" ]] && parse_hn_port "${URI}" + # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place + target="$NODE" + $do_mx_all_ips && target="$URI" + + echo -e " \"Invocation\" : \"$PROG_NAME $CMDLINE\", \"at\" : \"$HNAME:$OPENSSL_LOCATION\", \"version\" : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\", \"openssl\" : \"$OSSL_VER from $OSSL_BUILD_DATE\", @@ -913,12 +941,10 @@ fileout_pretty_json_banner() { \"port\" : \"$PORT\", \"startTime\" : \"$START_TIME\", \"scanResult\" : [" + fi } fileout_banner() { - local target="$NODE" - $do_mx_all_ips && target="$URI" - #if ! "$APPEND"; then # if "$CSVHEADER"; then # : @@ -930,6 +956,13 @@ fileout_banner() { #fi } +fileout_separator() { + if "$JSONHEADER"; then + "$do_pretty_json" && echo " ," >> "$JSONFILE" + "$do_json" && echo -n "," >> "$JSONFILE" + fi +} + fileout_footer() { if "$JSONHEADER"; then fileout_json_footer @@ -956,18 +989,18 @@ fileout() { json_header() { local fname_prefix + local filename_provided=false + + [[ -n "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]] && filename_provided=true # Similar to HTML: Don't create headers and footers in the following scenarios: # * no JSON/CSV output is being created. # * mass testing is being performed and each test will have its own file. # * this is an individual test within a mass test and all output is being placed in a single file. + ! "$do_json" && ! "$do_pretty_json" && JSONHEADER=false && return 0 + "$do_mass_testing" && ! "$filename_provided" && JSONHEADER=false && return 0 + "$CHILD_MASS_TESTING" && "$filename_provided" && JSONHEADER=false && return 0 - if ( ! "$do_json" && ! "$do_pretty_json" ) || \ - ( "$do_mass_testing" && ( [[ -z "$JSONFILE" ]] || [[ -d "$JSONFILE" ]] ) ) || \ - ( "$APPEND" && [[ -n "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]] ); then - JSONHEADER=false - return 0 - fi if "$do_display_only"; then fname_prefix="local-ciphers" elif "$do_mass_testing"; then @@ -975,19 +1008,22 @@ json_header() { elif "$do_mx_all_ips"; then fname_prefix="mx-$URI" else - ( [[ -z "$JSONFILE" ]] || [[ -d "$JSONFILE" ]] ) && parse_hn_port "${URI}" + ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place fname_prefix="${NODE}"_p"${PORT}" fi - if [[ -n "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]]; then - rm -f "$JSONFILE" - elif [[ -z "$JSONFILE" ]]; then + if [[ -z "$JSONFILE" ]]; then JSONFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".json) - else + elif [[ -d "$JSONFILE" ]]; then JSONFILE=$JSONFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".json) fi - "$do_json" && printf "[\n" > "$JSONFILE" - "$do_pretty_json" && printf "{\n" > "$JSONFILE" + if "$APPEND"; then + JSONHEADER=false + else + [[ -e "$JSONFILE" ]] && fatal "\"$JSONFILE\" exists. Either use \"--append\" or (re)move it" 1 + "$do_json" && printf "[\n" > "$JSONFILE" + "$do_pretty_json" && printf "{\n" > "$JSONFILE" + fi #FIRST_FINDING=false return 0 } @@ -995,14 +1031,15 @@ json_header() { csv_header() { local fname_prefix + local filename_provided=false + + [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]] && filename_provided=true # CSV similar: - if ! "$do_csv" || \ - ( "$do_mass_testing" && ( [[ -z "$CSVFILE" ]] || [[ -d "$CSVFILE" ]] ) ) || \ - ( "$APPEND" && [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]] ); then - CSVHEADER=false - return 0 - fi + ! "$do_csv" && CSVHEADER=false && return 0 + "$do_mass_testing" && ! "$filename_provided" && CSVHEADER=false && return 0 + "$CHILD_MASS_TESTING" && "$filename_provided" && CSVHEADER=false && return 0 + if "$do_display_only"; then fname_prefix="local-ciphers" elif "$do_mass_testing"; then @@ -1010,18 +1047,22 @@ csv_header() { elif "$do_mx_all_ips"; then fname_prefix="mx-$URI" else - ( [[ -z "$CSVFILE" ]] || [[ -d "$CSVFILE" ]] ) && parse_hn_port "${URI}" + ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place fname_prefix="${NODE}"_p"${PORT}" fi - if [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]]; then - rm -f "$CSVFILE" - elif [[ -z "$CSVFILE" ]]; then + + if [[ -z "$CSVFILE" ]]; then CSVFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".csv) - else + elif [[ -d "$CSVFILE" ]]; then CSVFILE=$CSVFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".csv) fi - "$do_csv" && echo "\"id\",\"fqdn/ip\",\"port\",\"severity\",\"finding\",\"cve\",\"cwe\",\"hint\"" > "$CSVFILE" + if "$APPEND"; then + CSVHEADER=false + else + [[ -e "$CSVFILE" ]] && fatal "\"$CSVFILE\" exists. Either use \"--append\" or (re)move it" 1 + echo "\"id\",\"fqdn/ip\",\"port\",\"severity\",\"finding\",\"cve\",\"cwe\",\"hint\"" > "$CSVFILE" + fi return 0 } @@ -1030,17 +1071,17 @@ csv_header() { html_header() { local fname_prefix + local filename_provided=false + + [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]] && filename_provided=true # Don't create HTML headers and footers in the following scenarios: # * HTML output is not being created. # * mass testing is being performed and each test will have its own HTML file. # * this is an individual test within a mass test and all HTML output is being placed in a single file. - if ! "$do_html" || \ - ( "$do_mass_testing" && ( [[ -z "$HTMLFILE" ]] || [[ -d "$HTMLFILE" ]] ) ) || \ - ( "$APPEND" && [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]] ); then - HTMLHEADER=false - return 0 - fi + ! "$do_html" && HTMLHEADER=false && return 0 + "$do_mass_testing" && ! "$filename_provided" && HTMLHEADER=false && return 0 + "$CHILD_MASS_TESTING" && "$filename_provided" && HTMLHEADER=false && return 0 if "$do_display_only"; then fname_prefix="local-ciphers" @@ -1049,33 +1090,36 @@ html_header() { elif "$do_mx_all_ips"; then fname_prefix="mx-$URI" else - ( [[ -z "$HTMLFILE" ]] || [[ -d "$HTMLFILE" ]] ) && parse_hn_port "${URI}" + ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place fname_prefix="${NODE}"_p"${PORT}" fi - if [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]]; then - rm -f "$HTMLFILE" - elif [[ -z "$HTMLFILE" ]]; then + if [[ -z "$HTMLFILE" ]]; then HTMLFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".html) - else + elif [[ -d "$HTMLFILE" ]]; then HTMLFILE=$HTMLFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".html) fi - html_out "\n" - html_out "\n" - html_out "\n" - html_out "\n" - html_out "\n" - html_out "\n" - html_out "testssl.sh\n" - html_out "\n" - html_out "\n" - html_out "
\n"
+     if "$APPEND"; then
+          HTMLHEADER=false
+     else
+          [[ -e "$HTMLFILE" ]] && fatal "\"$HTMLFILE\" exists. Either use \"--append\" or (re)move it" 1
+          html_out "\n"
+          html_out "\n"
+          html_out "\n"
+          html_out "\n"
+          html_out "\n"
+          html_out "\n"
+          html_out "testssl.sh\n"
+          html_out "\n"
+          html_out "\n"
+          html_out "
\n"
+     fi
      return 0
 }
 
 html_banner() {
-     if "$APPEND" && "$HTMLHEADER"; then
+     if "$CHILD_MASS_TESTING" && "$HTMLHEADER"; then
           html_out "## Scan started as: \"$PROG_NAME $CMDLINE\"\n"
           html_out "## at $HNAME:$OPENSSL_LOCATION\n"
           html_out "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\n"
@@ -10946,7 +10990,8 @@ mybanner() {
      local openssl_location="$(which $OPENSSL)"
      local cwd=""
 
-     $QUIET && return
+     "$QUIET" && return
+     "$CHILD_MASS_TESTING" && return
      OPENSSL_NR_CIPHERS=$(count_ciphers "$($OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>/dev/null)")
      [[ -z "$GIT_REL" ]] && \
           idtag="$CVS_REL" || \
@@ -11133,30 +11178,34 @@ parse_hn_port() {
 # arg1: for testing mx records name we put a name of logfile in here, otherwise we get strange file names
 prepare_logging() {
      local fname_prefix="$1"
+     local filename_provided=false
+
+     [[ -n "$LOGFILE" ]] && [[ ! -d "$LOGFILE" ]] && filename_provided=true
+
+     # Similar to html_header():
+     ! "$do_logging" && return 0
+     "$do_mass_testing" && ! "$filename_provided" && return 0
+     "$CHILD_MASS_TESTING" && "$filename_provided" && return 0
 
      [[ -z "$fname_prefix" ]] && fname_prefix="${NODE}"_p"${PORT}"
 
-     if "$do_logging"; then
-          if [[ -z "$LOGFILE" ]]; then
-               LOGFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".log)
-          elif [[ -d "$LOGFILE" ]]; then
-               # actually we were instructed to place all files in a DIR instead of the current working dir
-               LOGFILE=$LOGFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".log)
-          else
-               : # just for clarity: a log file was specified, no need to do anything else
-          fi
-
-          if ! "$APPEND"; then
-               [[ -e $LOGFILE ]] && fatal "\"$LOGFILE\" exists. Either use \"--append\" or (re)move it" 1
-          else
-               >$LOGFILE
-          fi
-          tmln_out "## Scan started as: \"$PROG_NAME $CMDLINE\"" >>${LOGFILE}
-          tmln_out "## at $HNAME:$OPENSSL_LOCATION" >>${LOGFILE}
-          tmln_out "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE" >>${LOGFILE}
-          tmln_out "## version openssl: \"$OSSL_VER\" from \"$OSSL_BUILD_DATE\")\n" >>${LOGFILE}
-          exec > >(tee -a ${LOGFILE})
+     if [[ -z "$LOGFILE" ]]; then
+          LOGFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".log)
+     elif [[ -d "$LOGFILE" ]]; then
+          # actually we were instructed to place all files in a DIR instead of the current working dir
+          LOGFILE=$LOGFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".log)
+     else
+          : # just for clarity: a log file was specified, no need to do anything else
      fi
+
+     if ! "$APPEND"; then
+          [[ -e $LOGFILE ]] && fatal "\"$LOGFILE\" exists. Either use \"--append\" or (re)move it" 1
+     fi
+     tmln_out "## Scan started as: \"$PROG_NAME $CMDLINE\"" >>${LOGFILE}
+     tmln_out "## at $HNAME:$OPENSSL_LOCATION" >>${LOGFILE}
+     tmln_out "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE" >>${LOGFILE}
+     tmln_out "## version openssl: \"$OSSL_VER\" from \"$OSSL_BUILD_DATE\")\n" >>${LOGFILE}
+     exec > >(tee -a ${LOGFILE})
 }
 
 
@@ -11777,11 +11826,11 @@ run_mass_testing_parallel() {
           cmdline=$(filter_input "$cmdline")
           [[ -z "$cmdline" ]] && continue
           [[ "$cmdline" == "EOF" ]] && break
-          cmdline="$0 $global_cmdline --warnings=batch -q $cmdline"
+          cmdline="$0 $global_cmdline --warnings=batch $cmdline"
           draw_line "=" $((TERM_WIDTH / 2)); outln;
           determine_logfile
           outln "$cmdline"
-          $cmdline >$LOGFILE &
+          CHILD_MASS_TESTING=true $cmdline >$LOGFILE &
           sleep $PARALLEL_SLEEP
      done < "$FNAME"
      return $?
@@ -11790,6 +11839,7 @@ run_mass_testing_parallel() {
 
 run_mass_testing() {
      local cmdline=""
+     local first=true
      local global_cmdline=${CMDLINE%%--file*}
 
      if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then
@@ -11797,15 +11847,16 @@ run_mass_testing() {
      fi
 
      pr_reverse "====== Running in file batch mode with file=\"$FNAME\" ======"; outln "\n"
-     APPEND=false # Make sure we close out our files
      while read cmdline; do
           cmdline=$(filter_input "$cmdline")
           [[ -z "$cmdline" ]] && continue
           [[ "$cmdline" == "EOF" ]] && break
-          cmdline="$0 $global_cmdline --warnings=batch -q --append $cmdline"
+          cmdline="$0 $global_cmdline --warnings=batch $cmdline"
           draw_line "=" $((TERM_WIDTH / 2)); outln;
           outln "$cmdline"
-          $cmdline
+          "$first" || fileout_separator
+          CHILD_MASS_TESTING=true $cmdline
+          first=false
      done < "${FNAME}"
      return $?
 }
@@ -12453,19 +12504,20 @@ check_proxy
 check4openssl_oldfarts
 check_bsd_mount
 
-if $do_display_only; then
+if "$do_display_only"; then
      prettyprint_local "$PATTERN2SHOW"
      exit $?
 fi
 
-if $do_mass_testing; then
+fileout_banner
+
+if "$do_mass_testing"; then
      prepare_logging
      run_mass_testing
      exit $?
 fi
 
 html_banner
-fileout_banner
 
 #TODO: there shouldn't be the need for a special case for --mx, only the ip adresses we would need upfront and the do-parser
 if $do_mx_all_ips; then