module EhcHardenRunner
EhcHardenRunner — argv-only toolkit subprocess scans (nmap, nikto, openssl). Public: run_active, parse_nmap, parse_nikto, parse_openssl, build_nmap_argv, build_nikto_argv, build_openssl_argv Depends: EhcHardenTarget, EhcToolkit, TextUtil Tests: test/lib/ehc_harden_runner_test.rb
Constants
- MAX_LINES
- NIKTO_BIN
- NMAP_BIN
- OPENSSL_BIN
- TIMEOUT_SEC
Public Instance Methods
Source
# File lib/ehc_harden_runner.rb, line 68 def build_nikto_argv(host, url:) target = url.to_s.strip.empty? ? "http://#{host}/" : url [NIKTO_BIN, "-host", target, "-maxtime", TIMEOUT_SEC.to_s] end
Source
# File lib/ehc_harden_runner.rb, line 64 def build_nmap_argv(host) [NMAP_BIN, "-Pn", "--top-ports", "20", host.to_s] end
Source
# File lib/ehc_harden_runner.rb, line 73 def build_openssl_argv(host) [OPENSSL_BIN, "s_client", "-connect", "#{host}:443", "-servername", host.to_s, "-brief"] end
Source
# File lib/ehc_harden_runner.rb, line 159 def default_runner lambda do |argv| require "open3" Open3.capture3(*argv) end end
Source
# File lib/ehc_harden_runner.rb, line 166 def failure_status(message = nil) Struct.new(:success?, :message).new(false, message) end
Source
# File lib/ehc_harden_runner.rb, line 45 def nikto_issues(host, url:, pacman: nil, runner:) return toolkit_missing_issue("nikto", "web server scanner") unless EhcToolkit.installed?("nikto", pacman: pacman) output, status = run_command(build_nikto_argv(host, url: url), runner: runner) return [] unless status.success? parse_nikto(output) end
Source
# File lib/ehc_harden_runner.rb, line 36 def nmap_issues(host, pacman: nil, runner:) return toolkit_missing_issue("nmap", "port and service scan") unless EhcToolkit.installed?("nmap", pacman: pacman) output, status = run_command(build_nmap_argv(host), runner: runner) return [] unless status.success? parse_nmap(output, host: host) end
Source
# File lib/ehc_harden_runner.rb, line 54 def openssl_issues(host, https:, pacman: nil, runner:) return [] unless https return toolkit_missing_issue("openssl", "TLS cert inspection") unless EhcToolkit.installed?("openssl", pacman: pacman) output, status = run_command(build_openssl_argv(host), runner: runner) return [] unless status.success? parse_openssl(output, host: host) end
Source
# File lib/ehc_harden_runner.rb, line 93 def parse_nikto(output) findings = output.to_s.lines.map(&:strip).grep(/\A\+ /).reject { |line| line.match?(/Target |Nikto|Start Time/i) } return [] if findings.empty? findings.first(4).map.with_index do |line, index| text = line.sub(/\A\+ /, "").strip EhcHardenProbe.issue( code: "nikto_#{index + 1}", title: "Nikto finding", summary: TextUtil.truncate(text, 90), severity: "medium", detail_lines: [text, "Review path exposure and server hardening."] ) end end
Source
# File lib/ehc_harden_runner.rb, line 77 def parse_nmap(output, host:) lines = output.to_s.lines.map(&:strip).grep(%r{\A\d+/tcp\s+open}) return [] if lines.empty? ports = lines.map { |line| line.split(/\s+/).first }.first(6) [ EhcHardenProbe.issue( code: "open_ports", title: "Open TCP ports detected", summary: "nmap found #{ports.length} open ports on #{host}.", severity: "info", detail_lines: ports.map { |port| "Open #{port}" } ) ] end
Source
# File lib/ehc_harden_runner.rb, line 109 def parse_openssl(output, host:) text = output.to_s match = text.match(/notAfter=(?<value>.+)/i) return [] unless match expires = Time.parse(match[:value].strip) days = ((expires - Time.now.utc) / 86_400).ceil severity = days <= 30 ? "high" : "info" summary = days.positive? ? "TLS cert expires in #{days} days." : "TLS certificate has expired." [ EhcHardenProbe.issue( code: "tls_expiry", title: "TLS certificate expiry", summary: summary, severity: severity, detail_lines: [ "Host: #{host}", "notAfter=#{match[:value].strip}", days <= 30 ? "Renew or replace the certificate before expiry." : "Track renewal in your cert monitor." ] ) ] rescue ArgumentError [] end
Source
# File lib/ehc_harden_runner.rb, line 25 def run_active(host:, url:, https: false, pacman: nil, runner: nil) return [] unless EhcHardenTarget.shell_safe_host?(host.to_s.sub(/:.*\z/, "")) tool_runner = runner || default_runner issues = [] issues.concat(nmap_issues(host, pacman: pacman, runner: tool_runner)) issues.concat(nikto_issues(host, url: url, pacman: pacman, runner: tool_runner)) issues.concat(openssl_issues(host, https: https, pacman: pacman, runner: tool_runner)) issues end
Source
# File lib/ehc_harden_runner.rb, line 151 def run_command(argv, runner:) stdout, stderr, status = runner.call(argv) output = [stdout, stderr].map(&:to_s).join("\n").strip [TextUtil.truncate(output, 8000), status] rescue StandardError => e ["", failure_status(e.message)] end
Source
# File lib/ehc_harden_runner.rb, line 136 def toolkit_missing_issue(package, role) [ EhcHardenProbe.issue( code: "toolkit_missing_#{package}", title: "#{package} not installed", summary: "Active #{role} skipped — install #{package} from the EHC toolkit.", severity: "info", detail_lines: [ "Package #{package} is not installed on the bot host.", "Run !ehc toolkit then: pacman -S #{package}" ] ) ] end