IDS・IPSのTCP再構成とHTTP検査
パケット、解析したHTTP、通知、配送の証拠をそれぞれ照合します。
説明
IDSは通信を観測して通知し、IPSは経路上で通信への措置も行います。HTTPの内容を検査するには、パケットのバイト列をTCPの順序に沿って再構成し、HTTPとして解析する必要があります。
公開文字列/lessonを含む固定pcapと、Suricata 7.0.10で解析した記録を開きます。分割、欠落、逆方向の3試料で同じルールへの一致を比較します。
パケット、解析したHTTP、通知、配送の証拠をそれぞれ照合します。
1パケットごとの番号と内容を開く
各行は時刻、シーケンス番号、TCP flagsの整数値、ペイロードです。
from sp04 import *
rows = packetsパケットごとの番号と内容を開く('split')
for row in rows: print(row)2はSYN、18はSYN+ACK、16はACK、24はPSH+ACKです。要求はseq 101と110の2個に分かれています。
2文字列がパケットの境界をまたぐことを確認する
各ペイロードだけに/lessonがあるかを調べます。
print([(seq,b'/lesson' in body文字列がパケットの境界をまたぐことを確認する) for t,seq,flags,body in rows if body])どちらもFalseです。最初がGET /less、次がonで始まるため、一枚ずつの文字列検索では見つかりません。
3シーケンス番号順につなげる
この試料には再送や重複がないため、2個のデータを順に結合できます。
piecesシーケンス番号順につなげる = sorted((seq,body) for t,seq,flags,body in rows if body)
print(pieces[0][0]+len(pieces[0][1])==pieces[1][0])
request = b''.join(body for seq,body in pieces)
print(request.decode(),end='')101に最初の9バイトを加えると110です。欠落のない範囲でHTTP要求全体を得られます。一般のTCPでは再送、重複、番号の巡回も処理します。
4HTTPのURIに限定したルールを読む
ルールの検査先はhttp.uriです。
print((ROOT/'alert.rules').read_text(),end='')
print(httpHTTPのURIに限定したルールを読む('split-alert'))
print(alerts('split-alert'))URIは/lesson、sid 1000001が一致しました。単に全バイトのどこかに文字列があるという条件ではありません。
5後半が欠けた試料を比較する
2個目のデータパケットを欠いたpcapです。
print([(seq,body) for t,seq,flags,body in packets後半が欠けた試料を比較する('gap') if body])
print(http('gap-alert'))
print(alerts('gap-alert'))この版の解析記録では途中の/lessが残りますが、/lessonへの通知はありません。欠落した記録から完全な要求を復元することはできません。
6同じバイト列を逆方向へ置く
サーバーからクライアントへ同じGET文字列を送った試料です。
print([(seq,body) for t,seq,flags,body in packets同じバイト列を逆方向へ置く('reverse') if body])
print(http('reverse-alert'))
print(alerts('reverse-alert'))要求URIとしての解析と通知はありません。flowのto_serverという方向条件も検査の一部です。
7一つのフロー内の記録を照合する
HTTP記録と通知が同じflow_idを参照するか確認します。
events = RESULTS['split-alert']
print([e['event_type'] for e in events])
print(len({e['flow_id一つのフロー内の記録を照合する'] for e in events}))alert、http、flowが一つのIDを共有しています。内容、通知、接続統計を同じ通信に結び付けて調べられます。
まとめ
分割と欠落をシーケンス番号で確認します。
HTTPのフィールドと通信方向が検査条件になります。
通知がない場合は取得範囲と解析結果も調べます。
操作履歴と解説を振り返れます。
Suricata 7.0.10のpcap解析結果 / Python 3.15
from sp04 import *
rows = packets('split')
for row in rows: print(row)(1, 100, 2, b'') (2, 500, 18, b'') (3, 101, 16, b'') (4, 101, 24, b'GET /less') (5, 110, 24, b'on HTTP/1.1\r\nHost: demo.example.test\r\n\r\n') (6, 501, 16, b'')
print([(seq,b'/lesson' in body) for t,seq,flags,body in rows if body])[(101, False), (110, False)]
pieces = sorted((seq,body) for t,seq,flags,body in rows if body)
print(pieces[0][0]+len(pieces[0][1])==pieces[1][0])
request = b''.join(body for seq,body in pieces)
print(request.decode(),end='')True GET /lesson HTTP/1.1 Host: demo.example.test
print((ROOT/'alert.rules').read_text(),end='')
print(http('split-alert'))
print(alerts('split-alert'))alert http any any -> any any (msg:"Lbox lesson URI"; flow:established,to_server; http.uri; content:"/lesson"; sid:1000001; rev:1;)
[('GET', 'demo.example.test', '/lesson')]
[(1000001, 'allowed')]print([(seq,body) for t,seq,flags,body in packets('gap') if body])
print(http('gap-alert'))
print(alerts('gap-alert'))[(101, b'GET /less')]
[('GET', None, '/less')]
[]print([(seq,body) for t,seq,flags,body in packets('reverse') if body])
print(http('reverse-alert'))
print(alerts('reverse-alert'))[(501, b'GET /lesson HTTP/1.1\r\nHost: demo.example.test\r\n\r\n')] [] []
events = RESULTS['split-alert']
print([e['event_type'] for e in events])
print(len({e['flow_id'] for e in events}))['alert', 'http', 'flow'] 1
演習完了