web-dev-qa-db-ja.com

awkでの後方参照に問題があります

最近、私はセキュリティログに興味があり、bash-Shellでそれをより良い方法にしたいと思っています。 awkで、後方参照は9でしか保存されないことがわかりました。しかし、10個の後方参照を使用する必要があります。

試した

awk '{print gensub(/^([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}).+?\sID\s(\[[0-9]{4}\]).+?\sTargetUserName\s=\s(.+?)\sTargetDomainName\s=\s(.+?)\sTargetLogonId\s=\s(.+?)\sLogonType\s=\s([0-9]{1,2})\s(.+?\sWorkstationName\s=\s(.+?)\sLogonGuid\s=\s.+?TransmittedServices\s=\s.+?\sLmPackageName\s=\s.+?KeyLength\s=\s.+?\sProcessId\s=\s.+?\sProcessName\s=\s.+?\sIpAddress\s=\s(.+?)\sIpPort\s\=\s([0-9]{1,}))?.+?$/,"\\5,\\4,\\3,\\2\\6,\\1,\\8,\\9,","g") }'

ターゲット文字列(実際には数千の文字列があります)

2017-03-21T02:00:00 kornawesome Security/Microsoft-Windows-Security-Auditing ID [4624] :EventData/Data -> SubjectUserSid = S-1-5-18 SubjectUserName = PRETENDERS$ SubjectDomainName = WORKGROUP SubjectLogonId = 0x00000000000004j7 TargetUserSid = X-12-54-181 TargetUserName = SYSTEMS TargetDomainName = NT AUTHORITY TargetLogonId = 0x00000000000003e7 LogonType = 8 LogonProcessName = Lxxoi   AuthenticationPackageName = Negotiate WorkstationName = - LogonGuid = {00344000-0000-0000-0000-0000000003440} TransmittedServices = - LmPackageName = Stainless KeyLength = 0 ProcessId = 0x0000000000000244 ProcessName = C:/Windows/System32/services.exe IpAddress = 10.0.0.0 IpPort = 10.5.3.2 ImpersonationLevel = %%1122

Awkやを使って実行する別の方法がある場合は、基本的なbashと連想配列を使用したいと思います。また、親切な説明をお願いします...私のために(noob)。

1
KeiTheNoop

セキュリティログの問題は、テキストの一部がおそらくユーザーの制御下にあることです。そのため、正規表現を使用して物事を分解することには問題があります。ただし、複数の式を使用して物事を分解できる可能性があり、これにより、9つの後方参照の制限を回避できます。たとえば、すべてのログエントリがタイムスタンプで始まる場合は、それをはがすことができます。

_awk '{t=$1 ;$1=""; 
print gensub(/^.+?\sID\s(\[[0-9]{4}\]).+?\sTargetUserName\s=\s(.+?)\sTargetDomainName\s=\s(.+?)\sTargetLogonId\s=\s(.+?)\sLogonType\s=\s([0-9]{1,2})\s(.+?\sWorkstationName\s=\s(.+?)\sLogonGuid\s=\s.+?TransmittedServices\s=\s.+?\sLmPackageName\s=\s.+?KeyLength\s=\s.+?\sProcessId\s=\s.+?\sProcessName\s=\s.+?\sIpAddress\s=\s(.+?)\sIpPort\s\=\s([0-9]{1,}))?.+?$/,"\\4,\\3,\\2,\\1\\5" t ",\\7,\\8,","g") }'
_

選択できるので、パターンの一部としてWorkstationName\s=\s(.+?)\sLogonGuidがあり、次を使用できます。

_awk {t=$1; $1="" ; printf("%s", gensub(/^.+?WorkstationName\s=\s(.+?)\sLogonGuid.*$/,"\\1,")); printf("%s,", t)}
_

フィールドを引き出すために、これを繰り返すことができます。

@casはコメントの中で、データは_EventData/Data ->_の前と後の2つの部分で表示でき、後のデータは_=_(スペース等スペース)で分割できると述べています。 )。さらに進んで、それをキーと値のペアとして表示し、_/\s\S+\s=\s/_で分割し、オプションの4番目の引数をsplitに使用してキーを取得します。これには、ユーザーが等号を行に入れられないこと、および各データに1つのWordキーがあるという大きな前提がいくつかあります。キーと値のインデックスは1だけ異なり、行の最初の部分は_v[1]_で終わることに注意してください。

_/usr/bin/awk '{
    n=split($0,v,/\s\S+\s=\s/,k)
    printf("There are %d fields\n",n)
    for(i=0;i<n;i++) { printf("%d key \"%s\" value \"%s\"\n",i,k[i],v[i+1]) }
}'
_

あなたのサンプルデータで

_There are 22 fields
0 key "" value "2017-03-21T02:00:00 kornawesome Security/Microsoft-Windows-Security-Auditing ID [4624] :EventData/Data ->"
1 key " SubjectUserSid = " value "S-1-5-18"
2 key " SubjectUserName = " value "PRETENDERS$"
3 key " SubjectDomainName = " value "WORKGROUP"
4 key " SubjectLogonId = " value "0x00000000000004j7"
5 key " TargetUserSid = " value "X-12-54-181"
6 key " TargetUserName = " value "SYSTEMS"
7 key " TargetDomainName = " value "NT AUTHORITY"
8 key " TargetLogonId = " value "0x00000000000003e7"
9 key " LogonType = " value "8"
10 key " LogonProcessName = " value "Lxxoi  "
11 key " AuthenticationPackageName = " value "Negotiate"
12 key " WorkstationName = " value "-"
13 key " LogonGuid = " value "{00344000-0000-0000-0000-0000000003440}"
14 key " TransmittedServices = " value "-"
15 key " LmPackageName = " value "Stainless"
16 key " KeyLength = " value "0"
17 key " ProcessId = " value "0x0000000000000244"
18 key " ProcessName = " value "C:/Windows/System32/services.exe"
19 key " IpAddress = " value "10.0.0.0"
20 key " IpPort = " value "10.5.3.2"
21 key " ImpersonationLevel = " value "%%1122"
_

ここからさらに進んで、saydataという連想配列を作成できます

_for(i=1;i<n;i++) {gsub(/[ =]/,"",k[i]);data[k[i]]=v[i+1]}
_

そうすれば、これがフィールド20か21かを心配することなく、_data["IpPort"]_のようなものを印刷できます。

3
icarus

icarusはすでにawkでこれに回答しているので、Perlを使用して日付とIDを変数に抽出し、イベントデータをハッシュ(連想配列)に抽出する方法を次に示します。

_#!/usr/bin/Perl -l

use strict;

while(<>) {
  if (m/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}).+?\sID\s(\[\d{4}\]).*?Data -> (.*)$/) {
    my ($date,$id,$eventdata) = ($1,$2,$3);

    print $date;
    print $id;

    # decorate the key names with a tab (i.e. add a tab before each) 
    $eventdata =~ s/([^[:blank:]]+) *= */\t$1=/g;
    # remove tab from beginning of $eventdata
    $eventdata =~ s/^\t//;       #/

    # split $eventdata on tabs, and split again into key=value pairs
    # and store in %data hash.    
    my %data = map { my($k,$v) = split("=",$_,2); $k => $v } split(/ *\t/,$eventdata);

    foreach my $key (sort keys %data) { printf "%s=%s\n", $key, $data{$key} };
  };
};
_

(_#/_コメントは、U&Lの壊れたPerl構文の強調表示を修正するためだけにあります)

split("=",$_,2)操作の最後の_,2_は、各key = valueペアを最大twoフィールドに分割することに注意してください:最初の)までのすべて _=_シンボル、およびそれ以降のすべて。これは、値に_=_記号が含まれているかどうかは関係ないことを意味します。このようなことは、awkよりもPerlの方がはるかに簡単です。 while(<>)ループの最初の2行に示されているように、正規表現とキャプチャグループの操作も簡単です。

として保存します。例: _kei.pl_、_chmod +x kei.pl_で実行可能にし、次のように実行します。

_$ ./kei.pl input 
2017-03-21T02:00:00
[4624]
AuthenticationPackageName=Negotiate
ImpersonationLevel=%%1122
IpAddress=10.0.0.0
IpPort=10.5.3.2
KeyLength=0
LmPackageName=Stainless
LogonGuid={00344000-0000-0000-0000-0000000003440}
LogonProcessName=Lxxoi
LogonType=8
ProcessId=0x0000000000000244
ProcessName=C:/Windows/System32/services.exe
SubjectDomainName=WORKGROUP
SubjectLogonId=0x00000000000004j7
SubjectUserName=PRETENDERS$
SubjectUserSid=S-1-5-18
TargetDomainName=NT AUTHORITY
TargetLogonId=0x00000000000003e7
TargetUserName=SYSTEMS
TargetUserSid=X-12-54-181
TransmittedServices=-
WorkstationName=-
_

ところで、ハッシュに日付とIDも必要な場合は、_%data = map ..._行の後に次を追加します(そして_print $date;_と_print $id;_行を削除します。

_$data{'DATE'} = $date;
$data{'ID'} = $id;
_
1
cas