diff --git a/lib/audit_log_parser.rb b/lib/audit_log_parser.rb index d486ed7..c0aa407 100644 --- a/lib/audit_log_parser.rb +++ b/lib/audit_log_parser.rb @@ -4,13 +4,17 @@ class AuditLogParser class Error < StandardError; end - def self.parse(src, flatten: false) + + # @param unhex_keys [Array] with * meaning all + def self.parse(src, flatten: false, unhex: false, unhex_keys: ['*'], unhex_min_length: 8) + # audit always uses uppercase hex digits. Fortunately addresses are generally lower-case. src.each_line.map do |line| - parse_line(line, flatten: flatten) + parse_line(line, flatten: flatten, unhex: unhex, unhex_keys: unhex_keys, unhex_min_length: unhex_min_length) end end - def self.parse_line(line, flatten: false) + def self.parse_line(line, flatten: false, unhex: false, unhex_keys: ['*'], unhex_min_length: 8) + unhex_re = /^[A-F0-9]{#{unhex_min_length},}$/ line = line.strip if line !~ /type=\w+ msg=audit\([\d.:]+\): */ @@ -22,10 +26,27 @@ def self.parse_line(line, flatten: false) header.sub!(/: *\z/, '') header = parse_header(header) body = parse_body(body.strip) + + if unhex + unhex_keys = unhex_keys.include?('*') ? :all : unhex_keys + unhex_hash!(header, unhex_keys, unhex_re) + unhex_hash!(body, unhex_keys, unhex_re) + end + result = {'header' => header, 'body' => body} flatten ? flatten_hash(result) : result end + def self.unhex_hash!(hash, unhex_keys, unhex_re) + hash.each do |key, value| + if value.kind_of?(Hash) + unhex_hash!(value, unhex_keys, unhex_re) + elsif (unhex_keys == :all || unhex_keys.include?(key)) && (value.length % 2) == 0 && value =~ unhex_re + value[0..-1] = [value].pack("H*") + end + end + end + def self.parse_header(header) result = {} diff --git a/spec/audit_log_parser_spec.rb b/spec/audit_log_parser_spec.rb index 125943e..9d44547 100644 --- a/spec/audit_log_parser_spec.rb +++ b/spec/audit_log_parser_spec.rb @@ -1,7 +1,7 @@ RSpec.describe AuditLogParser do let(:audit_log) do { - %q{type=SYSCALL msg=audit(1364481363.243:24287): arch=c000003e syscall=2 success=no exit=-13 a0=7fffd19c5592 a1=0 a2=7fffd19c4b50 a3=a items=1 ppid=2686 pid=3538 auid=500 uid=500 gid=500 euid=500 suid=500 fsuid=500 egid=500 sgid=500 fsgid=500 tty=pts0 ses=1 comm="cat" exe="/bin/cat" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key="sshd_config"} => + %q{type=SYSCALL msg=audit(1364481363.243:24287): arch=c000003e syscall=2 success=no exit=-13 a0=7fffd19c5592 a1=0 a2=7fffd19c4b50 a3=a items=1 ppid=2686 pid=3538 auid=500 uid=500 gid=500 euid=500 suid=500 fsuid=500 egid=500 sgid=500 fsgid=500 tty=pts0 ses=1 comm="cat" exe="/bin/cat" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key="sshd_config"} => {"header"=>{"type"=>"SYSCALL", "msg"=>"audit(1364481363.243:24287)"}, "body"=> {"arch"=>"c000003e", @@ -95,6 +95,12 @@ expect(AuditLogParser.parse(lines)).to eq audit_log.values end + specify '#parse unhex does not affect unhexable' do + lines = audit_log.keys.join("\n") + expect(AuditLogParser.parse(lines, unhex: true)).to eq audit_log.values + end + + context 'when flatten' do specify '#parse can be parsed flatly' do lines = audit_log.keys.join("\n") @@ -103,6 +109,60 @@ end end + context 'when unhex log' do + let(:unhex_audit_log) do + { + %q{type=PROCTITLE msg=audit(1585655101.154:27786): proctitle=2F62696E2F7368002D6300636F6D6D616E64202D762064656269616E2D736131203E202F6465762F6E756C6C2026262064656269616E2D73613120312031} => + {"header"=>{"type"=>"PROCTITLE", "msg"=>"audit(1585655101.154:27786)"}, + "body"=> + { + "proctitle" => "/bin/sh\u0000-c\u0000command -v debian-sa1 > /dev/null && debian-sa1 1 1", + } + } + } + end + + let(:unhex_specific_audit_log) do + { + %q{type=PROCTITLE msg=audit(1585655101.154:27786): proctitle=2F62696E2F7368002D6300636F6D6D616E64202D762064656269616E2D736131203E202F6465762F6E756C6C2026262064656269616E2D73613120312031 proctitle2=2F62696E2F7368002D6300636F6D6D616E64202D762064656269616E2D736131203E202F6465762F6E756C6C2026262064656269616E2D73613120312031 } => + {"header"=>{"type"=>"PROCTITLE", "msg"=>"audit(1585655101.154:27786)"}, + "body"=> + { + "proctitle" => "2F62696E2F7368002D6300636F6D6D616E64202D762064656269616E2D736131203E202F6465762F6E756C6C2026262064656269616E2D73613120312031", + "proctitle2" => "/bin/sh\u0000-c\u0000command -v debian-sa1 > /dev/null && debian-sa1 1 1", + } + } + } + end + + let(:unhex_length_audit_log) do + { + %q{type=PROCTITLE msg=audit(1585655101.154:27786): proctitle=2F62696E2F7368002D6300636F6D6D616E64202D762064656269616E2D736131203E202F6465762F6E756C6C2026262064656269616E2D73613120312031 } => + {"header"=>{"type"=>"PROCTITLE", "msg"=>"audit(1585655101.154:27786)"}, + "body"=> + { + "proctitle" => "2F62696E2F7368002D6300636F6D6D616E64202D762064656269616E2D736131203E202F6465762F6E756C6C2026262064656269616E2D73613120312031", + } + } + } + end + + specify '#parse correctly unhex proctitle' do + lines = unhex_audit_log.keys.join("\n") + expect(AuditLogParser.parse(lines, unhex: true)).to eq unhex_audit_log.values + end + + specify '#parse correctly unhex specific keys' do + lines = unhex_specific_audit_log.keys.join("\n") + expect(AuditLogParser.parse(lines, unhex: true, unhex_keys: ['proctitle2'])).to eq unhex_specific_audit_log.values + end + + specify '#parse does not unhex short keys' do + lines = unhex_length_audit_log.keys.join("\n") + expect(AuditLogParser.parse(lines, unhex: true, unhex_keys: ['proctitle'], unhex_min_length: 10000)).to eq unhex_length_audit_log.values + end + end + context 'when invalid log' do let(:invalid_log) do {