Search

Splunk 위협헌팅 #3 - 로그 예외처리의 중요성

위협헌팅은 로그 최적화가 가장 중요하다

위협헌팅을 할 때 가장 중요한 것은 좋은 쿼리문도 좋은 대시보드도 아닌 “정상 로그 예외처리”를 통한 로그 최적화가 가장 중요하다.
예를 들어 데이터 유출을 탐지하려한다고 가정해보자. 평소에 백업을 위해 A 서버가 모든 단말기들과 하루 10GB 이상씩 통신을 한다면 네트워크 통신 용량 순으로 정렬을 시켜도 우리에게 보이는 로그는 모두 A 서버와의 통신만 보일 것이다.
1GB를 유출하는 데이터는 검색 결과에서 몇 페이지를 넘겨야 보일 것이고 만약 TOP10을 보여주는 대시보드라면 평생 찾지 못하고 놓칠 것이다. 즉 안전한 로그들에 대한 화이트리스팅이 무엇보다 중요한게 위협헌팅이고 안전 로그 예외처리가 수반되지 않은 상태에서의 위협헌팅 효율은 10% 이하로 떨어질 수 밖에 없다.
반대로 보이는 모든 로그가 의심되는 유효성 있는 로그라면 굳이 별도의 대시보드나 검색 쿼리문 없이 기본 로그만 봐도 위협헌팅이 가능해진다.
약 30시간동안 발생한 로그는 약 3500만개로 50개 이하 단말기에서도 이정도 로그가 발생한다.
그러면 기존 로그에 대해 모두 안전한지 검증하면 되느냐?
엄청난 다수의 인원이 존재할 때는 발생하는 모든 의심로그에 대해 정 오탐을 판별하는 것이 가능할지 모르지만 소수의 인원으로 운영할 경우 로그에 대해 모두 검증하는 것은 불가능에 가깝다.
사전에 안티바이러스나 각종 취약점 테스트 도구를 통해 취약점 및 악성파일을 식별해 내부의 위협을 최대한 삭제한다.
이후 현재 상태는 안전하다고 가정, 현재까지 식별된 정보들을 예외처리한다.
이렇게하면 평시에 발생 로그들을 모두 제외할 수 있는만큼 효율적으로 신규 위협을 탐지할 수 있다.
물론 가정이야 했지만 실제로 그렇다는 것은 아니므로 모든 위협헌팅 과정에서 기존 로그를 무시하는 방향은 지양해야한다. 평시에는 안전했던 로그일지라도 악성코드가 자주 사용하는 로그도 존재한다. (ex. Powershell)
즉 기존 로그를 제외할 때 정말로 [안정성이 확인된 화이트리스트]와 [기존 로그 예외]를 구분해 사용해야하며, [기존 로그 화이트리스트]에 대해서는 비판적 사고를 가지고 사용해야한다.

로그 예외처리

Splunk의 Zeek, Suricata(Opensource NIDS) 네트워크 로그와 Sysmon 로그를 기준으로 위협헌팅에 자주 사용되는 필드값들의 화이트리스트를 구성한다.
여기서는 우선 네트워크 로그 예외처리만을 언급한다.

예외처리 목록 예시

Trusted User IP List
whitelist.csv
HTTP User Agent
agent_before_used.csv
WebShell type file (php, jsp. asp …)
webshell.before.csv
DNS Query
query_before_used.csv
random_dns.csv
Suricata Alert (src_ip → dest_ip & alert)
suricata_before.csv

1) 신뢰자산 및 유저 화이트리스트 구성

취약점 검사를 수행하는 인원의 통신을 제외할 경우 공격을 목적으로 취약점 스캔 툴(ex. nessus, nmap 등)을 사용하는 공격자를 탐지할 수 없는 등 위험이 존재하기 때문에 신뢰 목록을 구성해 기존 화이트리스트를 생성할 때 제외해야한다.

2) HTTP User-Agent 예외처리

index=zeek sourcetype="corelight_http" | search NOT [|inputlookup whitelist.csv |fields src_ip] | where isnotnull(http_user_agent) | stats count(http_user_agent) as agent_count by http_user_agent | table http_user_agent, agent_count | outputlookup agent_before_used.csv
SQL
복사
User-Agent값은 조작이 가능하지만 기존에 탐지되지 않던 User-Agent값이거나, 공격자가 미처 변조를 잊었다면 탐지하기에 용이한 값이다. http 로그에서 user_agent 값만 뽑아 출력한다.

3) WebShell 예외처리

index=zeek sourcetype=corelight_http uri!="-" | rex field=uri "(?<filename>[^\/\\&\?]+\.\w{2,4})(?=([\?&].*$|$))" | eval filetype=if(match(filename,"\.(dbm|cfc|cfml|cfm|lib|cgi|pm|pl|jspf|jsv|jsw|inc|php4|php3|phtml|htm|html|asa|aspx|jspx|php5|php|jsp|asp|htaccess)\b"), "shell", "not_shell") | where filetype == "shell" AND filename!="-" | dedup filename,dest_host | table filename, dest_host, dest_ip, uri | outputlookup webshell_before.csv
SQL
복사
새로운 웹 구성 파일이 업로드됐을 때 탐지하기 위해 기존 유저가 각종 사이트에 접근했던 파일 목록을 만들어 구성한다.

4) DNS Query 예외처리

index=zeek sourcetype="corelight_dns" | search NOT [|inputlookup whitelist.csv |fields src_ip] | where isnotnull(query) | dedup query | table query
SQL
복사
악성코드는 C&C 서버 통신을 위해 색다른 DNS 서버를 이용하는 경우가 많다. 이를 탐지하기 위해 기존 DNS Query 예외처리 목록을 구성한다.
이렇게 돌려버리면 일반적인 서비스의 경우 너무 많은 결과가 나온다. 비교할 때 상당히 많은 시간이 소요되므로 가능하면 공통되는 도메인은 묶어주는 게 좋다.

4-1) 다양한 주소로 접근하는 공통 DNS Query 리스트 제외

”1abc.google.com”, “2ab2.google.com”, “345.google.com”
Plain Text
복사
이를 우리는 *.google.com 과 같은 식으로 표현할 수 있디. 수백개의 개별 query 비교보다 정규식 비교가 훨씬 빠르므로 다음 과정을 거쳐 random_dns.csv를 제작한다.
query!="*Your Domain*" 내부 자산에 대한 접근은 필터링하지 않도록 한다. (커스텀)
query="*.*" 서비스 질의가 아닌 도메인 접근만을 살펴본다.
"(?:[^.]*\.){0,2}(?<last_two>([^.]*)\.([^.]*)$)" ”.”으로 나눈 뒤 맨 뒤 2 단어만 추출한다.
match(last_two,"(co|com|go|gov|edu|ne|or|org|ac|net)\.") co.kr, co.jp 는 일반적인 도메인이므로 제외하지 않도록 체크한다.
해당 도메인에 대한 다른 종류의 쿼리 질의 회수가 5회보다 많을 경우 출력한다.
index=zeek sourcetype="corelight_dns" query != "*Your Domain*" query="*.*" | search NOT [|inputlookup whitelist.csv |fields src_ip] | dedup query | rex field=query "(?:[^.]*\.){0,2}(?<last_two>([^.]*)\.([^.]*)$)" | stats count(last_two) as domain_count by last_two | eval common_domain = if(match(last_two,"(co|com|go|gov|edu|ne|or|org|ac|net)\."),"YES","NO") | where domain_count > 5 AND common_domain="NO" | sort -domain_count | eval query = "*." + last_two | table query, domain_count
PHP
복사

4-2) 랜덤 문자열이 들어간 DNS Query 리스트 제외

gtm-cn-4590x3tmp04.gtm-a2b4.com gtm-cn-tl32pap9m03.gtm-a2b4.com sun6-20.userapi.com sun6-21.userapi.com
Plain Text
복사
이런식의 랜덤 문자열이 들어가는 형태의 경우 페이지를 접속 할 때마다 값이 바뀌는 경우가 많아 오탐의 주요 원인이 된다.
대부분 dns, api, cdn, vpn, cloud 사이트가 이에 해당한다. 1번 과정을 통해 걸러지지 않은 도메인들을 식별하는 과정이므로 결과를 직접 확인하며 제외(random_dns.csv에 추가)하는 것이 좋다.
regex query=".*[a-zA-Z0-9]*\d{2,}[a-zA-Z0-9]*" 알파벳 대문자, 소문자, 숫자와 함께 2개 이상의 숫자를 포함하는 모든 문자열 (a02, 99z 등)
search index=zeek sourcetype="corelight_dns" query != "*Your Domain*" query="*.*" | search NOT [|inputlookup whitelist.csv |fields src_ip] | search NOT [|inputlookup random_dns.csv |fields query] | regex query=".*[a-zA-Z0-9]*\d{2,}[a-zA-Z0-9]*" | stats count(query) as query_count values(src_ip) as query_sip by query | eval len_query = len(query) | table query, len_query | sort query | sort -len_query
PHP
복사

4-3) 이외 개별 DNS 쿼리 제외

이제 나머지 값들을 직접 하나씩 비교할 수 있는 query_before_used.csv 룩업에 넣어주자
index=zeek sourcetype="corelight_dns" query != "*Your Domain*" query="*.*" | search NOT [|inputlookup whitelist.csv |fields src_ip] | search NOT [|inputlookup random_dns.csv |fields query] | where isnotnull(query) | dedup query | table query | outputlookup query_before_used.csv
SQL
복사

5) Suricata Alert 예외처리

index=suricata src="*" OR dest="*" OR dest_port="*" alert.category="*" NOT alert.category IN ("Generic Protocol Command Decode") | search NOT [|inputlookup whitelist.csv |fields src_ip] | eval epoch_time = _time | stats count as C by alert.signature src_ip dest_ip dest_port | table alert.signature src_ip dest_ip dest_port C | outputlookup suricata_before.csv
SQL
복사
룰 기반 Suricata에는 생각보다 ‘의심로그’(예. 악성코드에 자주 사용되는 DNS 유형)가 상당히 많다. 이에 기존 로그들을 우선 제외한다. 여유가 있다면 비교적 앞선 Agent나 DNS Query와 다르게 종류가 한정적이니 검증 절차를 거치는 것이 좋다.

✓ 다른 [탐구생활] 포스트

Pretendard vs SUIT 최고의 한글 고딕 폰트
Design
Pretendard vs SUIT 최고의 한글 고딕 폰트
Design
︎ 더 많은 게시물을 보려면
︎ 작성자가 궁금하면?
 2023. Absolroot all rights reserved.