一覧SP-04-1 · IDS・IPS:通信内容の検知

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

1
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'')
2
print([(seq,b'/lesson' in body) for t,seq,flags,body in rows if body])
[(101, False), (110, False)]
3
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

4
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')]
5
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')]
[]
6
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')]
[]
[]
7
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

演習完了