일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- stream_size
- snort
- flowbits
- SSL
- 암호화
- dsize
- stream buffer
- TLS
- header dos
- isdataat
- Reassembly
- Suricata
- chrome 탐지
- 탐지
- idps
- slowloris
- 수리카타
- 오탐
- 시그니처
- 재조합
- 크롬 탐지
- chromium 탐지
- rule
- 미탐
- signatrue
- HTTP2
- IDS
- IPS
- sinature
- DDOS
- Today
- Total
linefilt
Suricata 스트림 재조합 (stream raw reassembly) 본문
악성행위에 사용되는 페이로드 단일 패킷 사이즈에 존재 할 수도 있고 다양한 상황에 따라 패킷의 경계부분에 위치하여, 분할되는 경우가 있을 수 있다. 그렇기 때문에 IDS, IPS에서 특정 패턴을 검사하기 위해서 스트림 재 조합 기능은 반드시 필요한 기능이다. 물론 사용되는 모든 룰들이 재 조합 기능을 필요로 하는 것은 아니나, 특정 상황에서는 오로지 재 조합되지 않은 단일 패킷만 검출해서 확인해야 하는 경우도 있다. raw 기반 (Transport Layer) 재조합은 시퀀스 번호와 같이 4계층에서 순서를 정렬할 수 있는 TCP만 지원한다.
룰 제작 측면에서 suricata reassembly 설정에 대하여 확인하며, 본 글에서는 크게 3가지 항목으로 분류로 진행한다.
- TCP 스트림 재 조합 깊이 (depth)
- Inspection 범위 (chunk size)
- 재 조합에 따른 룰 작성
reassembly
TCP 스트림 재 조합 깊이 (depth)
각 패킷들은 재 조합 하여 스트림으로 재 생성해내는 것은 시스템에 있어서 매우 많은 자원을 소모한다. 탐지하고자 하는 대부분의 패턴들은 특정 세션이 끝날 무렵에 위치하기 보다는 초반에 이루어지는 경우가 많으며, 이러한 이유로 인해서 특정 세션에서 얼마나 많은 수의 세그먼트까지 재 조합을 수행할지 지정할 수 있습니다. (stream_reassemble 키워드를 사용하여 상황에 따라 특정 세션에서 재 조합은 ON/OFF 하는것이 가능)
그림 1. stream reassembly와 chunk-size
Inspection 범위
엔진은 분할되어 있을 수 있는 패턴을 탐색하기 위해서 재 조합을 수행합니다. chunk-size는 재 조합되어야 하는 세션의 패킷이 엔진을 통과할 때 최소한으로 탐색 되어야 스트림의 사이즈를 지정하는데 사용된다. 기본 값 보다 긴 페이로드를 식별해야 하는 경우에는 chunk-size를 조정할 수 있지만 권장하지 않는다. 긴 페이로드의 경우 flowbint나 기타 옵션 등 다른 방안을 통해 처리하는 것이 적절하다.
그림 2. suricata.yaml 에서의 chunk-size 설정
chunk-size 설정은 엔진에서 운영 모드에 따라 동일하게 적용되지만, TAP(IDS)/IPS 모드에 따라서 재 조합을 하는 방식에서 차이가 발생한다.
TAP 모드는 실시간 탐지가 필요 없기 때문에 응답으로 돌아오는 ACK를 기준으로 최소 한 개로 재 조합되어야 하는 메시지라고 판단한다. 엔진 설정에 적용된 chunk-size 만큼의 바이트를 client가 전송했는지 확인한다.
chunk-size가 충족되기 이전에 들어온 ACK는 무시하며, 설정된 chunk-size를 초과한 이후에 들어오는 첫 번째 ACK를 기준으로 판단한다. 재 조합이 완료되면 버퍼를 초기화 하고 새로운 패킷부터 다시 재 조합을 동일하게 시작한다.
그림 3. TAP 모드에서의 재 조합
IPS 모드는 페이로드를 실 시간으로 탐지 해야 되기 때문에 TAP 모드와 같이 응답으로 오는 ACK를 기다리지 않고 chunk-size 범위 내에서 즉시 식별하는 sliding window 방식으로 탐지 한다. 새로운 패킷이 들어왔을 때 chunk-size 잔여 공간이 부족하게 되면, 새로 들어오는 페이로드 길이 만큼 frontend를 cut-off 한다.
그림 4. IPS 모드에서의 재 조합
그림 4에서 첫 번째 문자열이 엔진으로 들어왔을때 먼저 탐색하고 두 번째 문자열이 들어오면 첫 번째 문자열과 합쳐서 탐색을 한다. 그리고 세 번째 문자열이 들어왔을때는 첫 번째 문자열 덩어리를 잘라내고 두 번째 문자열부터 탐색을 수행한다.
- 긴 길이를 가지는 페이로드의 탐색
엔진의 부하를 줄이기 위해서 각 세션에서 재 조합 할 수 있는 범위를 지정할 수 있지만, 그 범위 내에서 최소한 하나로 식별되어야 하는 chunk-size를 크게 잡을 수록 부하가 늘어날 수 밖에 없기에 chunk-size를 지정할 수 있는 범위가 제한된다. HTTP 요청 메시지와 같은 짧은 길이의 스트림은 문제 없이 탐색할 수 있지만, 파일이 포함된 응답과 같은 스트림에 대해서는 탐지가 어려운 일이 발생할 수 있다.
긴 길이를 가지는 스트림의 식별은 주로 flowbits를 사용해서 처리된다.
flowbits는 동일한 세션 내에서 추가적인 행위를 탐지하는데 사용할 수 있으며, 탐지해야 하는 특정 패턴이 chunk-size에 의해서 한번의 식별이 어려운 경우에 사용된다.
- IPS 재 조합시 문자열 위치 미 지정에 따른 오탐
아래 그림은 PNG파일이 포함된 HTTP 응답 메시지(1509bytes)에 대한 정보이다.
그림 5. 두 개의 segment로 구성된 2.segment.png 응답 메시지
이 페이로드를 탐지하기 위해 다음과 같은 룰을 사용한다.
alert tcp 192.168.11.20 8080 -> any any (msg:"PNG Stream"; flow:to_client,established; content:"Server"; content"IEND"; sid:1; rev:1;) |
offset 등의 위치를 정의하는 옵션은 엔진이 더욱 더 정확하고 빠른 탐지를 할 수 있게끔 도움을 준다. 이러한 옵션을 사용해야 하는 이유는 성능적인 측면도 있지만, 오탐을 회피해야 하는 목적도 있다.
그림 7의 패킷을 식별하기 위해서 sid:1 룰을 사용하여 탐지했으며, 사용된 content 값은 "Server"와 "IEND" 입니다. 그림 7은 sid:1이 적용된 상태에서 사이트에서 접속하고 해당 패킷을 다운받은 일련의 과정을 보여준다.
그림 6. 2segment.png 다운로드 절차
그림 7. 위치(location) 키워드 미 사용에 의한 오탐
1. 클라이언트는 처음으로 사이트에 접속하였고 서버는 200 OK로 자신의 디렉터리에 있는 목록을 포함한 응답을 전송한다.
sid:1의 조건 중 하나인 "Server" 문자열이 식별되지만, "IEND"가 없어 alert 되지 않는다.
2. 클라이언트는 2segment.png라는 파일을 클릭하여 다운로드 받는다.
서버의 응답 메시지에는 sid:1에 조건 "Server"와 "IEND"가 모두 확인되었고 alert 된다.(Wireshark No.3566)
3. 클라이언트는 다시 한번 페이지를 새로고침을 수행한다.
처음 페이지를 접속했을 때와 동일한 메시지(Wireshark No.3579)이지만, alert 되는 오탐이 발생한다.
오탐의 원인은 content가 나올 수 있는 정확한 위치를 지정하지 않았기 때문입니다. 일반적으로 snort 문법은 기본적으로 왼쪽에서 오른쪽으로 식별한다. 그렇다고 하여, content:"Server"; content:"IEND"; 가 "Server"가 먼저나오고 "IEND"가 뒤에 나와야 한다고 식별하지 않는다. 별도의 위치 지정이 없을 경우 식별하는 페이로드는 단순한 "AND" 조건으로 적용된다.
- IPS 재 조합 시 chunk-size에 따른 미탐
snort 문법은 보통의 pcap 파일만 있다면, 문자열과 문자열 간의 거리를 확인하여서 간단하게 제작할 수 있다. 더구나 문자열들이 정적인 위치라면, 관리자는 제작한 룰이 매번 정상적으로 탐지할 것이라고 기대한다.
아래 그림은 서비스에 대한 포트와 프로토콜을 정의해둔 리눅스상에서 /etc/service 파일에서 22번 포트까지 잘라낸 파일이다.
1932 bytes를 가지는 "services_22" 파일은 테스트 서버에서 GET 요청에 의해 client로 전송할 때 430 bytes의 HTTP 응답 헤더가 붙으면서 총 2363 bytes가 전송된다.
관리자는 "services_22" 파일을 사용해서 탐지가 된다면, 원본 파일도 문제없이 탐지가 될거라고 기대할 것이다.
"services_22" 파일을 탐지하기 위해서 다음과 같은 룰을 사용한다.
alert tcp 192.168.11.20 -> any any (msg:"cut-off test"; content:"HTTP/1.1"; depth:8; content:"|20|22/udp"; distance:2200; within:155; sid:2; rev:1;) |
그림 8. /etc/services 파일
sid:2 룰을 통해 "services_22" 파일이 정상적으로 탐지가 되었고 692,241 bytes 사이즈를 가지는 원본 "services "파일에 적용한다.
원본 파일은 잘라낸 파일과 앞 부분은 완전히 동일한 파일임에도 불구하고 탐지가 되지 않는다.
이유는 계속해서 들어오는 재 조합 스트림을 확인하기 위해서 chunk-size window에 의해 확인된 front-end가 cut-off 되었기 때문이다.
반면 1932 bytes로 잘라낸 파일의 경우 더 이상 들어오는 페이로드가 없었고 window size에서 해당 페이로드들은 모두 확인이 가능했기 때문에 탐지된다.
cut-off는 아래 검증에서 자세히 확인이 가능합니다.
그림 9. stream-tcp-reassemble.c 파일에서의 chunk-size 처리
실제 패킷에 의한 cut-off 탐지 (IPS mode/raw)
총 3개의 segment로 구성된 5840 bytes에 대하여 5개의 룰을 검증. chunk-size에 대한 정확한 검증을 위해서 offset과 depth를 활용하여 범위를 1 bytes로 지정
suricata.yaml / randomize-chunk-size: no
alert tcp any any -> 10.0.0.10 any (msg:"chunk 1&/"; content:"1"; depth:1; content:"/"; offset:1459; depth:1460; sid:1; rev:1;) alert tcp any any -> 10.0.0.10 any (msg:"chunk 1&9"; content:"1"; depth:1; content:"9"; offset:2559; depth:2560; sid:2; rev:1;) alert tcp any any -> 10.0.0.10 any (msg:"chunk 2&8"; content:"2"; depth:1; content:"8"; offset:2559; depth:2560; sid:3; rev:1;) alert tcp any any -> 10.0.0.10 any (msg:"chunk a&b"; content:"a"; depth:1; content:"b"; offset:2559; depth:2560; sid:4; rev:1;) alert tcp any any -> 10.0.0.10 any (msg:"chunk <&>"; content:"<"; depth:1; content:">"; offset:2559; depth:2560; sid:5; rev:1;) |
그림 10. 5840 bytes를 가지는 테스트 문자열
그림 11. suricata fastlog
5개의 룰 중에서 sid:2를 제외하고 모두 탐지 된다.
- 첫 번째 패킷을 버퍼로부터 가져 온다. 페이로드의 사이즈는 1460bytes로 chunk-size는 1100bytes의 여유가 있습니다. 탐색에 문제가 없다.
- 두 번째 패킷을 버퍼로부터 가져 온다. chunk-size는 두 페이로드의 사이즈 2920을 확인하기에 360이라는 사이즈가 부족하다. 첫 번째 패킷은 확인 되었기 때문에 chunk-size가 부족한 크기인 360만큼 탐색에서 제외하고 361부터 2920 bytes까지 탐색한다. 360만큼 탐색에서 제외되었기 때문에 sid:2룰은 탐지되지 않는다.
- 세 번째 패킷을 버퍼로부터 가져 온다 chunk-size는 여전히 두 번째, 세 번째 패킷 페이로드 2920 bytes (361 부터 3281 bytes) 를 확인하기에는 부족하다. 첫 번째 패킷은 이전에 이미 확인이 되었기 때문에 chunk-size가 부족한 크기인 1460만큼 front-end를 탐색에서 제외하고 1821부터 4380bytes까지 탐색한다.
- 네 번째 패킷을 버퍼로부터 가져 온다. 세 번째와 네 번째 패킷 페이로드 2920 bytes 를 확인하기에 부족하며, 두 번째 패킷은 이전에 이미 확인이 되었기 때문에 chunk-size가 부족한 크기인 1460만큼 front-end를 탐색에서 제외하고 3281 부터 5840 bytes까지 탐색한다.
요약하면 다음과 같습니다.
그림 12. front-end cutoff
1 - 1460
1 - 1460 + 1460 = 2920 > chunk-size(2560) -> cutoff front end excess 360
361 - 2920
361 - 2920 + 1460 = 4020 > chunk-size(2560) -> cutoff front end excess 1460
1821 - 4380
1821 - 4380 + 1460 = 4020 > chunk-size(2560) -> cutoff front end excess 1460
3281 - 5840
그렇다면, chunk-size를 최대치로 조정하여 탐색 범위를 더 늘리면 되는 것이 아닌가라고 할 수 있지만, 다음과 같은 사항들이 발생할 수 있다.
chunk-size를 늘리는 것은 시스템에 더 많은 부하를 발생시킨다.
지금 이 페이로드에 대해서는 효과를 볼 수도 있지만 사이즈가 다른 다른 페이로드들에 대해서는 위와 같은 상황이 언젠가는 발생할 수 있다.
default가 아닌 chunk-size를 조정한 값으로 생성한 룰은 기본 값을 가지고 있는 다른 시스템에서 제대로 적용되지 않을 수 있다.
기본 설정을 가지고 약 두 개 정도의 패킷 페이로드를 어느정도 커버할 수 있기에 분할되는 페이로드에 대한 탐지는 문제가 되지 않는다.
결론
룰 제작에 있어서 가장 좋은 방법은 단순한 정적 분석을 통한 제작이 아닌 동적 분석으로 통해서 검증까지 이루어져야 한다는 것이다. 동일한 룰이더라도 엔진이 TAP모드인지 IPS 모드인지에 따라서 탐지가 상이하게 발생할 수 있다. 또한 룰 제작에 있어서 단순한 content 룰이 아닌 여러가지 키워드를 상황에 따라서 복합적으로 이용해야 한다. 다양한 키워드를 무조건 사용 해야한다는 의미는 아닙니다. 키워드가 많아짐에 따라 룰이 시스템에 영향을 끼치는 부하도 증가할 수밖에 없다.
궁극적인 의미는 오탐/미탐을 줄이면서 시스템에 대한 영향을 최소화 하는 룰을 제작해야 한다는 것이다.
문서 기준은 suricata 4.1.0이 적용된다..
그림 5, 6의 출처는 https://suricata.readthedocs.io/en/suricata-4.1.0/ 이다.
그림 3와 4는 https://suricata.readthedocs.io/en/suricata-4.1.0/에서의 그림을 본 글에 맞게 재구성 한 것이다.
'Engine' 카테고리의 다른 글
HTTP2에서의 flowbits 한계 (0) | 2021.02.27 |
---|---|
suricata performance according to CPU affinity (0) | 2019.08.25 |
Suricata 18,000 Rules Performance (0) | 2018.10.19 |