linefilt

TCP Out-Of-Order (or segment loss) 탐지 본문

Rules/behavior

TCP Out-Of-Order (or segment loss) 탐지

mong.goose 2019. 12. 23. 21:58

TCP 스트림은 재조합을 지원하기 때문에 문자열 분할에 대해 크게 문제될 것은 없다. 하지만, 문자열 패턴이 아닌 순수한 스트림 또는 트래픽 양을 확인해야 하는 경우에는 탐지하기 곤란하거나 고려해야 할 요소들이 훨씬 많아질 수 있다. 이런 요소들 중 TCP Out-Of-Order (OOO) 또는 segment loss는 연속된 스트림에서 TCP segement의 양을 측정하는 것을 매우 어렵게 만든다.

아래 내용은 TCP OOO와 segment loss가 정확히 몇번 발생했는지 등의 완전성을 목표하지 않는다. 이런 현상이 발생하는 빈도를 탐지하는 것을 목표(실시간 완전 탐지는 어려움이 존재)로 하며, Suricata IPS(Inline)모드를 사용하여 탐지한다. 

 

그림 1. suricata 탐지윈도 크기에 따른 버퍼 cut-off

suricata는 기본적으로 설정된 chunk-size(탐지 윈도) 크기에 따라 stream buffer(페이로드)의 앞 부분을 cut-off하는 방식으로 동작한다. suricata-5.0.1 기준으로 기본 chunk-size는 2560이며, 최대 값은 4024이다. 악의적인 공격자의 우회를 최대한 제한하기 위해 1/10 의 크기만큼 동적으로 chunk-size가 적용되도록 설정되어있다. suricata.yaml을 수정하여 고정 값 또는 다른 값으로 변경이 가능하다.

stream:
  memcap: 1024mb
  checksum-validation: yes      # reject wrong csums
  inline: yes                  # auto will use inline mode in IPS mode, yes or no set it statically
  prealloc-sessions: 50000       # 2k sessions prealloc'd per stream thread
  bypass: no                  # Bypass packets when stream.reassembly.depth is reached.
  reassembly:
    memcap: 1024mb
    depth: 1mb                  # reassemble 1mb into a stream
    toserver-chunk-size: 2560
    toclient-chunk-size: 2560
    randomize-chunk-size: yes
    randomize-chunk-range: 10
    raw: yes
    segment-prealloc: 50000
    #check-overlap-different-data: true

 

그림 1은 간단한 표현을 위해 chunk-size가 2560으로 고정 값이며, 모든 TCP segment 사이즈가 1460 동일한 상태이다. 그림 1에서 알 수 있듯이, chunk-size를 초과하는 버퍼의 앞 부분은 cut-off가 된다. chunk-size가 2560으로 설정된 상태에는 탐지하려는 최대 페이로드의 길이는 2560이 될 수 있지만, 2561이상은 존재 할 수 없다.

페이로드길이가 2560인 것을 탐지하기 위해서는 다음 룰을 통해 탐지할 수 있다:

alert tcp-stream any any -> any any (msg:"max chunk size detection"; flow:established; isdataat:2559; )

 

TCP OOO 또는 segment loss도 이와 같은 방식을 통해 탐지할 수 있다.

TCP segment가 순차적으로 확인되면, 재조합을 처리하는 stream buffer에서 연속적인 Block이 생성이 된다. 그림 2는 segment 5까지 중에서 segment 4가 TCP OOO 또는 segment loss가 발생한 상황이다. segment 1부터 segment 3까지는 순차적으로 확인되어 연속적인 stream buffer가 생성되었지만, segment 4는 확인되지 않는다. segment 5는 자신을 기준되는 blob(덩어리)을 탐지에 사용한다.

그림 2에서 segment 5의 TCP sequence는 5840으로 segment loss 등이 없었다면, chunk-size 2560을 충분히 채울 수 있는 크기다. 하지만 GAP이 발생하면서, 현재 segment 5 기준으로 segment 5가 속하는 stream buffer는 1460이 된다.

stream buffer의 사이즈가 1460이라면 다음과 같은 룰을 적용하여 탐지할 수 있다:

 

alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,; isdataat:!1460; stream_size:clientr,>,1461; )

그림 2. stream buffer에서의 GAP 발생

suricata는 새로운 segment 마다 탐지를 하기 때문에 GAP이 발생하더라도 위와 같은 방식으로 지속적으로 탐지할 수 있다. 다만 연속적인 segment가 loss 되는 문제 등이 발생하면, 정확히 몇개의 segment가 문제되는지를 확인할 수 없다. 또한 stream buffer의 페이로드를 대상으로 탐지하기 때문에 TCP Length가 0인 segment들의 TCP OOO 또는 segment loss를 확인할 수 없다는 한계를 가진다.

 

그림 3. 두 개 이상의 TCP segment loss 및 OOO

TCP OOO, segment loss를 탐지하는 룰은 모든 환경에서 공통적으로 적용하기는 어렵다. TCP MSS가 1460인 경우가 대부분일지라도 이는 최대 전송 사이즈이고 추가적인 캡슐화가 적용될 수 있기 때문에 상황에 맞추어 특정 TCP Sequence 이후 부터 탐지하는 등의 조정이 필요하다.

아래 10개의 룰은 서버 방향으로의 요청에 대해 초기 1460 까지의 페이로드를 (TCP Sequence 기준 1461) 10개의 구간으로 나누었을 때의 예시이다.

alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,to_server; isdataat:!146; stream_size:client,>,146; stream_size:client,<,294; sid:1;)
alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,to_server; isdataat:!292; stream_size:client,>,292; stream_size:client,<,439; sid:2;)
alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,to_server; isdataat:!438; stream_size:client,>,438; stream_size:client,<,585; sid:3;)
alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,to_server; isdataat:!584; stream_size:client,>,584; stream_size:client,<,731; sid:4;)
alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,to_server; isdataat:!730; stream_size:client,>,732; stream_size:client,<,877; sid:5;)
alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,to_server; isdataat:!876; stream_size:client,>,874; stream_size:client,<,1023; sid:6;)
alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,to_server; isdataat:!1022; stream_size:clienr,>,1022; stream_size:client,<,1169; sid:7;)
alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,to_server; isdataat:!1168; stream_size:client,>,1168; stream_size:client,<,1315; sid:8;)
alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,to_server; isdataat:!1314; stream_size:client,>,1314; stream_size:client,<,1461; sid:9;)
alert tcp-stream any any -> any any (msg:"TCP segment loss or Out-Of-Order"; flow:established,to_server; isdataat:!1460; stream_size:client,>,1460; sid:10;)

stream buffer에서 첫 번째 segment 이후 GAP이 발생해야 되기 때문에 3-Way Handshake 이후 첫 번째 segment를 제외한 (TCP Len이 0인 모든 segment는 제외) 두 번째 segment 부터 식별하도록 구성된다. stream_size는 세션의 ISN을 기준으로 누적된 상대적인 sequence를 확인한다. TCP 3-Way Handshake에서 TCP flags - SYN 또한 sequence 번호가 1이 증가 된다는 것을 염두해 두어야 한다.

sid:1은 TCP sequence가 148이상, 293이하 일때, 누적 페이로드 사이즈가 147미만인 경우 탐지된다. TCP sequence의 범위 제한적으로 지정했기 때문에 페이로드 147을 초과하는 페이로드 사이즈는 탐지되지 않는다. sid:1부터 sid:10 까지 146배수로 순차 증가하는 룰이며, TCP sequence 까지 모두 포함되었을 때 탐지하도록 동작한다. 146이라는 기준은 1460까지를 10개의 구간으로 나누기 위한 임의의 값이다. 만약 segment 1과 segment 2, segment 3의 Length가 300으로 동일할 때, segment 2가 loss되면 sid:6이 탐지된다.

TCP sequence가 1461 이후 부터는 어떤 사이즈를 적용하여도 큰 영향을 끼치지 않는다. TCP MSS가 1300인 환경에서는 두 개의 segment를 더해도 2600이라는 크기가 나온다. 두 세그먼트 중 앞 세그먼트가 loss 된다면, 현재 stream buffer의 크기는 1300으로 "isdataat:!1460;"이 적용되어있어도 동일하게 탐지가 가능하다.

flowint를 사용해서 빈도를 카운팅하는 룰을 적용할 수 있다.

tcp-stream은 flow:only_stream과 동일한 동작을 한다.

snort에서는 적용이 불가능하다.

'Rules > behavior' 카테고리의 다른 글

raw stream based Slowloris (Inline mode)  (0) 2022.04.08
URL Scan과 Crawler  (0) 2019.04.05
Comments