[SK shieldus Rookies 16기][클라우드기반 시스템 운영 및 구축] Splunk 설치 및 사용방법, DNS 로그 분석, HTTP 로그 분석, EndPoint 로그 분석

Jina·2024년 1월 12일
0

SK shieldus Rookies 16기

목록 보기
55/59
post-custom-banner

1. Splunk

1.1. 정의

SIEM(Security Information & Event Management) 솔루션 중 하나로 기업 정보에 대한 종합 관제 솔루션


Splunk Forwarder는 Agent 프로그램으로 실시간으로 Splunk Server에 로그를 보내준다.

1.2. Splunk 평가판 및 튜토리얼 데이터 파일 다운로드

Splunk 소프트웨어 평가판 다운로드 🔗

  • Splunk Cloud : 평가판 15일 동안 이용 가능 / 매일 최대 5GB의 데이터를 인덱싱 가능
  • Splunk Enterprise : 평가판 60일 동안 이용 가능 / 매일 최대 500MB의 데이터를 인덱싱 가능

Tutorial 데이터 파일 다운로드 🔗

  • tutorialdata.zip 파일을 성공적으로 업로드 하려면 압축해야한다.
    • tutorialdata.zip 파일 다운로드 후 압축해제 X
    • Prices.csv.zip 파일 다운로드 후 압축해제 X

1.3. Splunk 설치

[설치환경]

  • Win10
  • Splunk Enterprise 9.0.1 Ver.


Username : splunk / Password : 12345678

1.4. Splunk 접속

http://localhost:8000 또는 http://127.0.0.1:8000 접속
설치할 때 설정했던 Username 과 Password 입력 후 로그인

1.5. Splunk 살펴보기

분석할 데이터 추가하는 방법 : 설정 > 데이터 추가 > 업로드

업로드 하는 파일은 반드시 압축파일 그대로 올리기


파일이 성공적으로 업로드되면 [검색 시작] 클릭

> 를 클릭하면 상세하게 볼 수 있다.

검색을 통해서도 로그를 분석할 수 있지만 Splunk에서 자동으로 분석해서 필드에 대한 통계를 내주기 때문에 필드를 참고할 수 있다.

2. 로그 분석

사내망의 로그들에 대해

  1. HTTP 로그 분석 후 이상징후 탐색
  2. DNS 로그 분석 후 이상징후 탐색
  3. EndPoint 로그 분석 후 이상징후 탐색

3. 검색명령어

  • 키워드 검색 : category 로 시작하는 모든 키워드 찾으려면 와일드 카드 사용 가능
  • 필드 검색
    • 필드명 = 필드값
    • 검색 후 사이드 바에서 보려고 하는 필드만 선택해서 볼 수 있음
    • 검색 시 필드 사이에 공백은 AND 연산자를 의미
  • 문자열 검색
    • 문자열 검색 시 대소문자 구분 X error 를 검색결과와 Error 검색결과는 동일
    • 연속된 문자열의 경우 "" 사용
  • 연산자 사용 : 연산자 (AND, OR, NOT) 사용 시 대문자로 입력

✅ 검색 시 체크포인트

  • 괄호를 이용하여 우선순위 지정하기
  • 시간 점검하기
  • 자동완성기능 이용하기

3.1. 데이터 나열, 변환

1) table

index=main sourcetype="access_combined_wcookie"
index=main sourcetype=access_combined_wcookie
| table clientip, method, productId, status

2) rename

index=main sourcetype=access_combined_wcookie
| table clientip, action, productId, status
| rename action AS "Customer Action", productId AS ProductID, status AS "HTTP Status"

3) dedup

검색 결과에서 중복 제거

index=main sourcetype=access_combined_wcookie status=404

index=main sourcetype=access_combined_wcookie status=404 | dedup host

4) sort

IP주소를 정렬 할 경우 함수 ip() 또는 num( ) 사용

index=main sourcetype=access_combined_wcookie
| table clientip, action, productId, status
| sort action, -productId

index=main sourcetype=access_combined_wcookie
| table clientip, action, productId, status
| sort -ip(clientip)

3.2. 통계 계산

1) stats

index=main sourcetype="access_combined_wcookie"
| stats sum(bytes), avg(bytes), max(bytes), median(bytes), min(bytes) by clientip

2) top

빈도 수가 많은 값의 순서를 추출

* | top useother=T clientip by method

3) rare

top과 반대의 결과로 빈도 수가 적은 값의 순서를 추출

* | rare useother=T clientip by method

3.3. 시각화

테이블로 표시된 결과를 다양한 차트로도 만들 수 있다.

3.4. 비교분석

1) eval

검사 결과 값의 반환, 검증을 수행하며 함수 실행 결과 값을 반환

# 변수명을 선언
| eval [반환값_저장변수] = 함수(인자1, 인자2..)
sourcetype=access_*
| eval status_code=if(status==200, "OK", "Error")
| table clientip status_code

2) case(X,”Y”,…)

  • 여러 개의 조건을 검증할 때 사용
  • 두 개의 인자가 한 그룹으로 동작
  • 첫 번째 인자가 참인 경우 두 번째 인자의 내용이 반환, 세 번째 인자가 참이면 네 번째 인자가 수행
index=httplog sourcetype=httplog
| eval description=case(error==404, "Not found", error==500, "internal Server Error")
| table clientip description
| eval quarter=case(date_month==“January”, “1Q”, date_month==“April”, “2Q”)

3) cidmatch(“X”,Y)

  • IP주소 Y가 네트워크 범위 X에 존재하는지 확인
  • 반환 값은 참 또는 거짓이며 두 개의 인자가 사용
  • 첫 번째 CIDR 형식의 네트워크 주소 범위, 두 번째는 검사를 위한 IP 주소가 입력
  • 검색 필터로 사용 가능
| eval local=cidrmatch(“10.0.0.0/8”, “10.10.0.100”)

IP 주소 10.10.0.100이 10.0.0.0/8 대역에 포함되면 true 아니면 false 반환

| where (cidrmatch(“10.0.0.0/8”, ip) OR
(cidrmatch(“172.16.0.0/12”, ip) OR
(cidrmatch(“192.16.0.0/16”, ip)

4) if(X,Y,Z)

  • X가 참이면 Y를 실행하고 X가 거짓이면 Z실행
* | eval ip1="10.10.0.100", ip2="100.10.0.100"
| eval network1=if(cidrmatch("10.10.0.0/24", ip1),"local", "external"), 
network2=if(cidrmatch("10.10.0.0/24", ip2),"local", "external") # Y
| table ip1, network1, ip2, network2

IP 필드 값이 10.10.0.100이라면 10.10.0.0/24 네트워크에 포함되므로 network1 필드에 “local” 문자열이 저장
IP 필드 값이 100.10.0.100이라면 network2 필드에는 “external” 이 할당

5) like(X,“Y”)

  • Like 함수의 X필드에서 일부 문자열인 Y를 검색
  • 첫 번째 인자는 대상 필드, 두 번째 인자는 정규 표현식의 탐색 패턴
  • X에서 Y를 찾을 수 있으면 참을 반환
  • like 함수의 와일드 카드 문자열은 ‘%’
  • field 변수가 addr로 시작하는지 검사
| where like(field, “add%”)

6) match(X,“Y”)

index=httplog sourcetype=httplog
| where NOT match(method, "(GET|POST|-)")
| stats count(src) as src_count by method
| sort - src_count

where절이 True인 경우에만 그 다음 stats나 sort 절을 수행한다.

3.5. 다중 문자열과 시간

1) split(X,”Y”)

  • X라는 문자열을Y 라는 구분자를 이용해 분할
    /data/utility/tool/GoogleToolbar.exe 를 split(uri,"/") 하면 총 4개로 분할된다. 인덱스는 0번부터 시작한다. data utility tool GoogleToolbar.exe

2) mvindex(X,Y,Z)

  • 필드 X에 있는 Y번째 값은 반환 (Z는 생략가능)
  • 여기서 Y는 인덱스 번호 (0번부터 시작)
  • Z값 지정 시 함수는 Y부터 Z까지의 값을 반환
    /data/utility/tool/GoogleToolbar.exe 를 mvindex(split(uri,”/”), -1) 하면 /를 구분자로 총 4개로 분할 data utility tool GoogleToolbar.exeGoogleToolbar.exe 를 반환한다.
*| eval passwd_str="lightdm:x:107:117:Light Display M a n a g e r :/var/lib/lightdm:/bin/false"
| eval uid=mvindex(split(passwd_str,":"),0)
| eval subuid1=substr(uid,2)
| eval subuid2=substr(uid,2,4)
| table uid, subuid1, subuid2

3) round(X,Y)

  • X를 Y 자리 수 기준으로 반올림
  • 나누기 계산을 할 경우 소수점 자리가 급격히 늘어나는 것을 방지

4) strftime(X,Y)

  • 유닉스 타임 X를 지정한 Y형식으로 출력
  • 주로 사용자가 읽기 편한 형식으로 변환 할 때 사용
  • 유닉스 타임(에포크 타임) 계산법은 1970년 1월 1일 0시를 기준으로 초를 계산

5. DNS 로그 분석 실습환경 구성

5.1. Index 만들기

리눅스 로그와 유사하게 /var/lib/splunk 위치에 로그가 있다.

5.2. Source Type 지정


쉼표로 구분된 필드 이름
ts,uid,src,spt,dst,dpt,proto,trans_id,rtt,domain,qclass,qclass_name,qtype,qtype_name,rcode,rcode_name,AA,TC,RD,RA,Z,answers,TTLs,rejected

5.3. 데이터 추가

내가 설정할 필드보다 많은 이유? Splunk에서 자동 인식해서 만들어낸 필드가 포함되어 있기 때문에

5.4. URL Toolbox 설치

표시여부가 No 로 되어 있으면 [속성 편집] 에서 예로 바꿔주기

앱이 정상적으로 설치되면 Splunk 메인화면에서 확인할 수 있다.

6. DNS 로그 분석

DNS 로그 필드

  • ts : 유닉스 시간
  • uid : 로그 id
    ------------ ▼ TCP/IP헤더에서 알 수 있는 정보 ----------
  • id.orig_h : 송신지IP주소
  • id.orig_p : 송신지 port번호
  • id.resp_h : 수신지IP주소
  • id.resp_p : 수신지 port번호
  • Protocol : 사용 프로토콜
    -------------- ▼ 여기서부터 DNS 로그 필드 ------------
  • trans_id : 질의와 응답을 연결하는 ID(DNS작업번호)
  • rtt : 응답시간 (-으로 표시되면 정상적으로 진행 X)
  • query : 도메인질의내용
  • DNS : 서버에 질의한 도메인
    ----------- ▼ Query 메세지를 보고 구성한 필드 ---------
  • qclassb : 질의클래스
  • qclass_name : 질의클래스 이름
  • qtype : 질의형식
  • qtype_name : 질의형식이름
    --------- ▼ Responce 메세지를 보고 구성한 필드 --------
  • rcode : 답변코드
  • rcode_name : 질의한 도메인의 응답코드
rcodercode_name서명
0NoError오류 없음
1FromErrQury 형식 오류
2SevFailDNS 서버 자체의 문제로 실패
3NXDomainDNS 클라이언트가 존재하지 않는 도메인으로 질의
4NotlmpDNS 서버가 해당 질의를 지원 X
5Refused정책적인 이유로 질의를 거절
  • AA : 인증서버답변여부
  • TC : 전체질의가 잘렸는지 여부
  • RD : 재귀질의 요청여부
  • RA : 재귀질의 가능여부
  • Z : 예약된 필드로 사용하지 않음
  • Answers : DNS서버에서 반환한 답변내역
  • TTLs : 도메인의 TTL값, 질의 답변의 캐시보관기간 (3600초(1시간 기본))
  • Reject : 질의가 거부됐는지 판단

6.1. DNS 네트워크 현황 분석 항목

전반적인 통계치를 파악하기 위함이다.
현황분석 시 숫자에서 이상 징후를 유추해야 한다.

1) TOP 10 도메인 현황 분석

  • 사용자가 가장 많이 접속한 도메인 10개
  • 많이 접속한다는 것은 많은 사용자가 접속한다는 뜻이지만 소수의 클라이언트가 많이 접속할 수도 있다는 뜻이기도 함 (ex. 동일 도메인이 10분 동안 60번씩 요청할 경우, 매크로가 동작한다고 추측 가능)
  • 네트워크 접속 행위를 분석 시 해당 트래픽이 사용자가 발생시킨 것인지, 자동 프로그램이 일으킨 행위인지를 분석의 기준으로 삼을 수 있음
index=dnslog sourcetype=dnslog domain!="-"
| eval list = "mozila"
| `ut_parse(domain,list)`
| table ut_netloc, ut_domain, ut_subdomain, ut_domain_without_tld, ut_tld
| dedup ut_netloc
  • index=dnslog : dnslog 저장소에서
  • sourcetype=dnslog : dnslog 필드를 갖고 있는 로그들만 분석
  • domain!="-" : domain 필드에 - 표시 즉, 값이 설정되어 있지 않은 로그는 제외
  • | eval list = "mozila" : list 라는 변수명에 "mozila" 라는 값을 선언
  • | ut_parse(domain,list) : URL의 domain을 mozila로 파싱 (URL Toolbox가 설치되었기 때문에 가능)
  • | table ut_netloc, ut_domain, ut_subdomain, ut_domain_without_tld, ut_tld : 테이블 형태로 필드 구성을 ut_netloc, ut_domain, ut_subdomain, ut_domain_without_tld, ut_tld 로 하기 (URL Toolbox에서 지원하는 필드)
  • | dedup ut_netloc : 중복된 ut_netloc 필드 값은 제거

  • ut_netloc(전체주소) : ztp.polycom.com
  • domain : polycom.com
  • subdomain : ztp
  • tld(Top Level Domain) : com

2) 사용자들이 가장 많이 접속한 도메인 10개를 검색

index=dnslog sourcetype=dnslog dpt=53 
domain!="*.arpa" 
domain!="-"
| eval list="mozilla" 
| `ut_parse(domain, list)`
| top showperc=f limit=10 ut_netloc
  • index=dnslog : dnslog 저장소에서
  • sourcetype=dnslog : dnslog 필드를 갖고 있는 로그들만 분석
  • dpt=53 : 수신지 포트 53
  • domain!="*.arpa" : 정방향 조회 A레코드만 (역방향 조회인 ptr 레코드는 제외)
  • domain!="-" : Query name 부분(도메인명)이 없으면 제외
  • | eval list="mozilla" : list 라는 변수에 "mozilla" 라는 값 부여
  • | ut_parse(domain, list) : 도메인을 mozilla 형식으로 분할
  • | top showperc=f limit=10 ut_netloc : 퍼센테이지 제외 결과값을 내림차순으로 10개만 보여주되 전체 주소를 보여줘

teredo.ipv6.microsoft.com 가 가장 많이 접속했다.

  • ut_netloc : teredo.ipv6.microsoft.com
  • domain : microsoft.com
  • subdomain : teredo.ipv6
  • tld : com

특정 몇몇 사람만 많이 접속했는지 여러 명이 접속했는지 파악할 필요가 있다.

3) 도메인과 해당 도메인을 접속한 송신지 IP주소를 동시에 검색

index=dnslog sourcetype=dnslog dpt=53 domain!="*.arpa" domain!="-" 
| eval list="mozilla" 
| `ut_parse(domain,list)` 
| top showperc=f src, ut_netloc
  • index=dnslog sourcetype=dnslog dpt=53 domain!="*.arpa" domain!="-" : dnslog 저장소에서 소스타입이 dnslog인 것 중 수신지 포트가 53이고 도메인이 역방향 조회인 것과 공란인 경우는 제외
  • src : 송신지 IP주소
    src ---ut_netloc---> DNS

172.16.146.107 와 172.16.138.48가 teredo.ipv6.microsoft.com 에 비정상적으로 많은 접속 횟수로 보아 매크로를 돌렸을 가능성이 높다.

4) NXDomain이 빈번하게 발생하는 도메인과 해당 질의를 발생하는 송신지 모니터링

index=dnslog sourcetype=dnslog domain!="-" rcode_name= "NXDomain"
| top showperc=f src, domain

도메인 검색 시 오타가 날 수도 있기 때문에 NXDomain 이 1~2건 정도는 있을 수 있으나 지속적인 NXDomain이 발생하면 점검이 필요하다. 왜? 감염된 PC가 사라진 도메인에 접속을 시도하는걸수도 있기 때문에

💡 NXDomain이란?
존재하지 않는 도메인에 접속을 시도하는 경우 발생하는 에러

5) 비정상적인 서브 도메인 길이

💡 서브 도메인(Subdomain)

  • 도메인 소유자가 생성하는 도메인명
    (ex. aaa.bbb.naver.com 에서 서브 도메인은 aaa.bbb에 해당)
  • 한국인터넷진흥원에서 도메인은 2~63자로 규정해두었다.
  • 비정상적인 도메인 길이는 주로 서브 도메인을 의미
index=dnslog sourcetype=dnslog domain!="-" 
| where NOT cidrmatch(domain, "0.0.0.0/0") 
| eval list="Mozilla"
| `ut_parse(domain, list)`
| where NOT match(domain,"(microsoft.com|akamaized.net| amazonaws.com)$") 
| eval sub_len=len(ut_subdomain) 
| search sub_len > 20 
| table ut_domain, ut_subdomain, sub_len, sut_netloc
  • | where NOT cidrmatch(domain, "0.0.0.0/0") : 도메인명이 ip 형태로 나오면 ptr 레코드를 의미(역방향 조회). 도메인명에 IP주소 형태가 있으면 True ⇒ ptr 레코드 제외
  • | where NOT match(domain,"(microsoft.com|akamaized.net| amazonaws.com)$") : 정규표현식에서 $는 도메인 끝에서부터 봤을 때 microsoft.com|akamaized.net| amazonaws.com 인 건 제외 ⇒ 검색 대상에서 가상화를 지원해주는 사이트는 제외
  • | eval sub_len=len(ut_subdomain) : sub_len 변수에 서브 도메인의 길이를 부여
  • | search sub_len > 20 : sub_len이 20자 초과인 것만 찾기

💡 왜 서브 도메인 길이를 20자를 기준으로 하는지?
서브 도메인이 20자를 초과하면 난독화 현상을 진행되기 때문에 20자를 기준으로 한다. 공격자가 접속사이트를 숨기기 위해서 길게 만든다.

6) 비허가 DNS 사용

index=dnslog sourcetype=dnslog (dst!="172.16.142.11" AND dst!="172.16.142.12") (src!="172.16.142.11" AND src!="172.16.142.12") 
| stats count by dst 
| sort - count

위조 DNS 서버를 쓰고 있는지 찾아내는 작업

  • index=dnslog sourcetype=dnslog (dst!="172.16.142.11" AND dst!="172.16.142.12") (src!="172.16.142.11" AND src!="172.16.142.12") : DNS 서버주소는 질의할 때는 dst지만 응답할 때는 src가 된다. ⇒ 수신지 IP 주소나 송신지 IP주소가 172.16.142.11 나 172.16.142.12 가 아닌 로그만
  • | stats count by dst : dst 필드 값에 따라 이벤트 수를 계산
  • | sort - count : count 필드 값을 기준으로 검색 결과를 내림차순으로 정렬

7. HTTP 로그 분석 실습환경 구성

7.1. Index 생성

7.2. Source Type 지정


쉼표로 구분된 필드 이름 : ts,uid,src,spt,dst,dpt,trans_depth,method,domain,uri,referrer,version,user_agent,request_body_len,response_body_len,status_code,status_msg,info_code,info_msg,tags,username,password,proxied,orig_fuids,orig_filenames,orig_mime_types,resp_fuids,resp_filenames,resp_mime_types

7.3. 데이터 추가

8. HTTP 로그 분석

HTTP 로그 필드

  • ts : 유닉스 시간
  • uid : 로그 id
    ------------▼ IP헤더에서 알 수 있는 정보----------
  • id.orig_h : 송신지IP주소
  • id.orig_p : 송신지 port번호
  • id.resp_h : 수신지IP주소
  • id.resp_p : 수신지 port번호
    ------------------▼ HTTP 로그 필드---------------
  • trans_depth : 질의와 응답을 연결하는 ID
  • method : 요청방식
  • host : 접근 도메인
  • uri : 도메인 제외 상세주소
  • referrer : Host 접속시 경유주소
  • version : HTTP 버전
  • user_agent : 웹브라우저 정보
  • request_body_len : 서버에게 전송하는 정보길이
  • response_body_len : 사용자에게 전송하는 정보길이
  • status_code : 상태코드
  • status_msg : 상태 메세지
  • tags
  • Proxied : 프록시 접속 여부
  • resp_fuids : 연결된 파일 ID
  • resp_mime_types : 전송파일 mime type


8.1. HTTP 네트워크 현황 분석 항목

1) 사용자들이 가장 많이 접속하는 도메인을 추출해서 접속현황 분석

index=httplog sourcetype=httplog domain!="(empty)" 
| iplocation dst 
| where NOT cidrmatch("0.0.0.0/0", domain) 
| stats sum(request_body_len) as "Outbound", sum(response_body_len) as "Inbound" by domain, Country 
| eval Outbound=round(Outbound/(1024*1024),2) 
| eval Inbound=round(Inbound/(1024*1024),2) 
| sort Outbound desc 
| head 10
  • index=httplog sourcetype=httplog domain!="(empty)" : httplog 저장소에서 httplog 필드가 있고, 도메인이 빈 칸인 로그(host명 명시X)를 제외한 로그
  • | iplocation dst : 수신지 IP를 보고 지역정보를 알려줘
  • | where NOT cidrmatch("0.0.0.0/0", domain) : 도메인에 IP주소가 포함되지 않는 로그만
  • | stats sum(request_body_len) as "Outbound", sum(response_body_len) as "Inbound" by domain, Country : 도메인과 국가의 request_body_len의 합계를 Outbound라고 Rename, response_body_len의 합계를 Inbound 라고 Rename ⇒ Request는 나가는거고, Response는 들어오는거라서
  • | eval Outbound=round(Outbound/(1024*1024),2) : 메가바이트로 처리하고 소숫점 2자리까지 반올림하고 Outbound 변수에 값 할당
  • | eval Inbound=round(Inbound/(1024*1024),2) : 메가바이트로 처리하고 소숫점 2자리까지 반올림하고 Inbound 변수에 값 할당
  • | sort Outbound desc : Outbound 필드를 내림차순으로 정렬
  • | head 10 : 검색 결과 중 상위 10개만

💡 대부분 Request Body Length < Response Body Length 지만
Request Body Length > Response Body Length 경우는 PUT 메소드를 사용할 경우에는 가능

송신지 IP가 1개인데 특정 사이트에 2627번 접속했다면 점검해볼 필요가 있다.

보통 직원들이 접속하는 사이트의 국가는 정해져 있는데 희안한 국가 사이트가 있다면 프록시를 사용중일 수 있기 때문에 모니터링할 필요가 있다.

2) 클라이언트가 서버에 자원을 요청하는 방식 분식

index=httplog sourcetype=httplog uri!="-" 
| top method limit=10 showperc=f
  • index=httplog sourcetype=httplog uri!="-" : httplog 저장소에서 소스타입이 httplog면서 uri가 없는 건 제외한 로그만
  • | top method limit=10 showperc=f : 메소드별로 카운트해서 퍼센테이지 없이 상위 10개만

3) Top 10 클라이언트 오류

index=httplog sourcetype=httplog uri!="-" uri!="/" (status_code >=400 AND status_code < 500) 
| top domain, status_code limit=10 showperc=f
  • index=httplog sourcetype=httplog uri!="-" uri!="/" (status_code >=400 AND status_code < 500) : httplog 저장소의 httplog 필드에서 uri가 없거나 메인페이지인 경우는 제외한 클라이언트 오류(4xx)가 발생한 로그만
    • 4xx : 클라이언트측 에러
      • 400 : 문법적 오류
      • 401 : 인증 실패
      • 403 : 접근 권한이 없는 리소스를 요청
      • 404 : 존재하지 않는 리소스를 요청

4) Top 10 서버 오류

index=httplog sourcetype=httplog uri!="-" status_code >= 500
| top domain, status_code limit=10 showperc=f
  • index=httplog sourcetype=httplog uri!="-" status_code >= 500 : httplog 저장소의 httplog 필드에서 uri가 없는 건 제외하고 서버 오류(5xx)가 발생한 로그만
    • 5xx : 서버측 에러
      • 500 : 소프트웨어상(애플리케이션) 오류
      • 501 : 웹에서 요청한 메소드가 서버에서 허용하는 것이 아닐 경우
      • 502 : 서버와 클라이언트를 중계해주는 프록시에 문제가 발생할 경우
        • 웹에서 게이트웨이는 프록시를 의미
      • 503 : 하드웨어상(RAM,CPU) 오류

5) HTTP 상태 코드

index=httplog sourcetype=httplog domain!="(empty)" status_code!="-" 
| top limit=10 showperc=f status_code

HTTP 응답코드별로 카운팅해서 상위 10개까지만 표시

9. HTTP 이상징후

9.1. 비정상 메소드 사용

1) 메소드가 OPTIONS 인 경우

index=httplog sourcetype=httplog 
| stats count(eval(method="OPTIONS")) AS option_count by src 
| where option_count > 10 
| sort option_count desc
  • index=httplog sourcetype=httplog : httplog 저장소에서 소스타입이 httplog인 로그
  • | stats count(eval(method="OPTIONS")) AS option_count by src : 송신지IP(src)에서 메소드가 OPTIONS인 걸 카운팅해서 option_count로 명명
  • | where option_count > 10 : option_count가 10개 이상인 것만
  • | sort option_count desc : option_count 기준으로 내림차순으로 정렬

2) 메소드가 GET, POST, - 인 경우 제외한 나머지

index=httplog sourcetype=httplog 
| where NOT match(method, "(GET|POST|-)")
| stats count(src) as src_count by method 
| sort - src_count
  • index=httplog sourcetype=httplog : httplog 저장소에서 소스타입이 httplog인 로그
  • | where NOT match(method, "(GET|POST|-)") : 메소드가 GET, POST, - (비정상 요청) 제외한 것만
  • | stats count(src) as src_count by method : 메소드에서 송신지IP(src)를 카운트해서 src_count로 명명
  • | sort - src_count : src_count를 기준으로 내림차순으로 정렬

9.2. 외부행 데이터 전송

index=httplog sourcetype=httplog (request_body_len!=0 OR response_body_len!="0") domain!="-" 
| stats sum(request_body_len) as outTotal sum(response_body_len) as inTotal by src, dst 
| eval oMB=round(outTotal/(1024*1024),2) 
| eval iMB=round(inTotal/(1024*1024),2) 
| search oMB!=0 AND iMB!=0 
| iplocation dst 
| eval isUp=if((oMB/iMB)>1, "Yes","No") 
| where isUp="Yes" 
| table src,dst, iMB, oMB, Country, City
  • index=httplog sourcetype=httplog (request_body_len!=0 OR response_body_len!="0") domain!="-" : httplog 저장소에서 소스타입이 httplog면서 request_body_len나 response_body_len가 0이 아니고 도메인이 공란이 아닌 로그
  • | stats sum(request_body_len) as outTotal sum(response_body_len) as inTotal by src, dst : 송신지 IP주소와 수신지 IP주소에서 request_body_len의 합계를 구해서 outTotal로 명명하고, response_body_len의 합계를 구해서 inTotal로 명명
  • | eval oMB=round(outTotal/(1024*1024),2) : outTotal을 메가 바이트로 처리하고 소숫점 2자리까지 반올림한 후 oMB 변수에 할당
  • | eval iMB=round(inTotal/(1024*1024),2) : inTotal을 메가 바이트로 처리하고 소숫점 2자리까지 반올림한 후 iMB 변수에 할당
  • | search oMB!=0 AND iMB!=0 : oMB(request)와 iMB(response) 값이 0이 아닌 것만 검색
  • | iplocation dst : 수신지 IP주소에서 국가코드 추출
  • | eval isUp=if((oMB/iMB)>1, "Yes","No") : oMB(request)나 iMB(response)가 1 이상이면 Yes 아니면 No 값을 isUP 변수에 할당
  • | where isUp="Yes" : isUP이 Yes인 것만
  • | table src,dst, iMB, oMB, Country, City : 테이블로 보여주되 필드는 src, dst, iMB, oMB, Country, City로 구성

9.3. Mime-type과 파일 확장자 불일치

index=httplog sourcetype=httplog resp_mime_types="application/x-dosexec" uri!="-" 
| eval filename1=mvindex(split(uri,"/"),-1) 
| eval filename=if(like(filename1,"%?%"), mvindex(split(filename1,"?"),0),filename1) 
| eval filetype=if(match(filename,"(.exe|.bat|.ps1|.dll|.ocx)$"), "PE", "Not_PE") 
| table domain, uri, filename, filetype, resp_mime_types 
| where filetype=="Not_PE" 
| dedup filename
  • index=httplog sourcetype=httplog resp_mime_types="application/x-dosexec" uri!="-" : httplog 저장소에서 소스타입이 httplog이고, 미디어 타입이 DOS 실행 파일이면서 uri가 없는 것은 제외한 나머지 로그
  • | eval filename1=mvindex(split(uri,"/"),-1) : uri를 /를 기준으로 분할 후 인덱스 번호가 -1번인 것을 filename1 변수에 할당
  • | eval filename=if(like(filename1,"%?%"), mvindex(split(filename1,"?"),0),filename1) : 만약 filename1에 ?가 있으면 ?를 기준으로 filename1을 분할 후 인덱스 번호가 0번인 것을 filename 변수에 할당하고, ?가 없으면 그냥 filename1을 filename 변수에 할당 ⇒ 파일명을 검출하기 위함
  • | eval filetype=if(match(filename,"(.exe|.bat|.ps1|.dll|.ocx)$"), "PE", "Not_PE") : 만약 filename에 끝부분이 .exe, .bat, .ps1, .dll, .ocx 인 경우 "PE" 값을 filetype 변수에 할당하고, 아닌 경우 "Not_PE" 를 filetype 변수에 할당 ⇒ 파일 확장자를 검출하기 위함
  • | table domain, uri, filename, filetype, resp_mime_types : 테이블을 domain, uri, filename, filetype, resp_mime_types 필드로 구성
  • | where filetype=="Not_PE" : filetype이 "Not_PE"인 것만
  • | dedup filename : filename 중복 제거

✅ 체크포인트
Request 헤더의 Accept는 Response 헤더의 Content-type
Request 헤더의 Accept은 받아들일 수 있는 데이터 타입을 MIME타입으로 표현하여 알려줌
Response 헤더의 Content-type은 body의 메세지 본문의 데이터 타입

💡 MIME은 그냥 표현 형식
Request 헤더에 있는 accept : text/html 을 예로 들자면 받아들일 수 있는 데이터 타입(accept)이 MIME 타입(text/html)으로 표현되었다고 말한다.

💡 MZ 실행파일이란? DOS에서 .EXE 실행 파일에 사용되는 파일 형식

  • 파일 확장자 : .exe
  • 인터넷 미디어 타입
    • application/x-dosexec
    • application/x-msdos-program
    • application/x-ms-dos-executable

9.4. 사이트 이동 후 실행파일 다운로드

index=httplog sourcetype=httplog referrer!="-" status_code=200 
| eval filename1=mvindex(split(uri,"/"),-1) 
| eval filename=if(like(filename1,"%?%"), mvindex(split(filename1,"?"),0),filename1) 
| where cidrmatch("0.0.0.0/0",domain) 
| where match(resp_mime_types,"application/x-dosexec") OR match(filename,"(exe|dll|com|src)$") 
| eval URL=domain+" :: " + filename 
| stats count by src, URL 
| stats list(URL) as Target list(count) as Source by src
  • index=httplog sourcetype=httplog referrer!="-" status_code=200 : httplog 저장소에서 소스타입이 httplog고, 경유지가 있는 HTTP 응답코드가 200인 로그
  • | eval filename1=mvindex(split(uri,"/"),-1) : uri를 /로 분할하고 인덱스 번호가 -1번인 것을 filename1 변수에 할당 ⇒ 파일명을 검출하기 위함
  • | eval filename=if(like(filename1,"%?%") mvindex(split(filename1,"?"),0),filename1) : 만약 filename1에 ?가 있으면 ?를 기준으로 filename1을 분할 후 인덱스 번호가 0번째인 것을 filename 변수에 할당하고, ?가 없으면 그냥 filename1을 filename 변수에 할당
  • | where cidrmatch("0.0.0.0/0",domain) : 도메인에 IP주소가 들어가는지
  • | where match(resp_mime_types,"application/x-dosexec") OR match(filename,"(exe|dll|com|src)$") : 미디어 타입이 DOS 실행 파일이거나 filename의 끝부분이 exe, dll, com, src인 것
  • | eval URL=domain+" :: " + filename : 도메인과 ::와 filename을 결합한 값을 URL 변수에 할당
  • | stats count by src, URL : 송신지 IP주소(src)와 URL을 카운팅
  • | stats list(URL) as Target list(count) as Source by src : 송신지 IP주소(src)에서 URL을 Target으로 명명하고, 카운팅한 값을 Source로 명명

⇒ DBD 공격을 위한 실행 파일이 있는지 확인하기 위함

9.5. 프록시 서버 접속

index=httplog sourcetype=httplog (uri="http://*" OR method="connect") 
| table src, domain, uri  
  • index=httplog sourcetype=httplog (uri="http://*" OR method="connect") : httplog 저장소에서 소스타입이 httplog고, uri가 http:// 로 시작하거나 메소드가 connect인 로그
  • | table src, domain, uri : 테이블을 src, domain, uri로 구성

✅ 체크포인트
1. Request 메소드Connect면 프록시를 경유해 들어왔음을 알 수 있다.
2. URI에 프로토콜부터 전체 주소가 보여지면 프록시를 사용했음을 알 수 있다.

10. Sysmon

10.1 정의

  • 윈도우 운영체제에 설치되는 시스템 모니터링 툴

10.2 목적

기본 윈도우 이벤트 뷰어에서는 프로세스 생성, 네트워크 연결, 파일 생성 시간 변경 등의 정보를 추출한 후 윈도우 이벤트 저장소에 저장할 수 없기 때문에 ⇒ 이벤트 기반 정보가 아닌 행동 기반 정보를 수집해서 이벤트 저장소에 저장

10.3. 생성 이벤트

11. EndPoint

일반적으로 클라이언트 PC를 지칭하는 말

12. EndPoint 로그 분석 실습환경 구성

EndPoint로그 == 윈도우 로그를 기반 ⇒ 악성코드에 감염된 PC들의 이상징후를 탐지

1) Sysmon 설치

2) sysmon 로그 데이터를 Splunk 저장소에 넣기

이렇게 하는 이유? 원래 Forworder 에서 실시간으로 로그를 받아와야하지만 그게 현재 불가능하기 때문에

3) Splunk 서버 재시작

설정 > 시스템 > 서버 컨트롤

4) sysmon 로그 데이터 확인

검색창에 index=sysmon 을 검색했을 때 로그가 뜨면 성공!

이벤트 뷰어 > 응용 프로그램 및 서비스 로그 > Microsoft > Sysmon > Operational

13. PC 이상 징후 분석

13.1. 비정상 폴더에서 exe 파일 실행

  • 윈도우 실행파일(시스템폴더) 위치
    • C:\Program Files
    • C:\Program Files(x86)
    • C:\Windows
    • C:\Windows\system32
  • 일반적으로 공격에 사용되는 악성코드는 단독 실행 파일로 동작
  • 시스템 폴더에 설치되지 않음
    ⇒ 프로그램의 실행 경로를 판단한다면 이상징후를 판별 가능
  • 인터넷으로 다운로드한 악성코드가 처음부터 시스템 폴더에 복사되지는 않기 때문에 최초 실행 폴더를 기반으로 탐지하는 방법은 유효함
  • 백도어 프로그램은 윈도우 정상 파일의 대체로 C:\Windows\System32에 설치되기도 함
index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
(CurrentDirectory!="*Program FIles*" AND CurrentDirectory!="*system32*")
(Image!="*system32*" AND Image!="*Program FIles*" AND Image!="*SysWOW64*")
[search index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
| rare CurrentDirectory limit=10 showperc=f showcount=f]
| table Image
  • EventCode=1 : 프로세스 생성을 의미 ⇒ exe 파일이 정상적으로 실행됨
  • (CurrentDirectory!="*Program FIles*" AND CurrentDirectory!="*system32*")
    (Image!="*system32*" AND Image!="*Program FIles*" AND Image!="*SysWOW64*") : 실행 파일이 들어있는 디렉터리로 Program Files, System32, SysWOW64 등은 제외
  • [search index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 | rare CurrentDirectory limit=10 showperc=f showcount=f] : 2차 검색, 하위 검색 ⇒ 검색 범위를 줄이기 위함

13.2. 파일 실행 후 원본 파일 삭제

💡 프로그램과 프로세스의 차이
프로그램 : 하드디스크에 저장된 소스코드 (정적인 상태)
프로세스 : 실행 중인 프로그램

  • 하드 디스크에 저장된 악성코드는 프로세스 상태가 되어야 PC들을 감염시킬 수 있음
  • 악성코드 파일 실행 후 원본파일을 디스크에서 삭제해서 분석을 회피하기도 함 ⇒ 프로그램을 실행 후 원본 파일을 디스크에서 삭제하는 행위는 정상 행위가 아님
index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 ParentImage="C:\\windows\\explorer.exe"
[search index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational"
| where NOT isnull(Image) AND NOT isnull(ParentImage)
| search CommandLine="* del *"
| table ParentImage
| rename ParentImage AS Image
] | table Image
  • EventCode=1 ParentImage="C:\\windows\\explorer.exe" : 파일탐색기를 이용하여 실행된 파일 검색
  • [search index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" | where NOT isnull(Image) AND NOT isnull(ParentImage) | search CommandLine="* del *" : 하드디스크에서 실행된 파일을 검색 후 삭제된 파일이 있다면 해당 파일의 원본 이름을 알려줘

13.3. 실행 후 네트워크 접속 다수 발생

index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 
(Image!="C:\\Windows*" AND Image!="*Program FIles*")
[search index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational"  EventCode=3
 (DestinationIp!="10.0.0.0/8" AND DestinationIp!="172.16.0.0/12" AND DestinationIp!="192.168.0.0/16")
 | stats count(DestinationIp) AS total_count dc(DestinationIp) AS uniq_count by Image
 | where total_count > 50 OR uniq_count > 20 
 | table Image]
| table Image

⇒ 디도스 공격을 찾아낼 때 사용하는 검색어

  • index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 : 프로세스가 실행된 파일을 찾고
  • (Image!="C:\\Windows*" AND Image!="*Program FIles*") : Windows나 Program 디렉토리 밑에서 실행된 파일은 제외
  • [search index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3 : 네트워크 로그만 검색
  • (DestinationIp!="10.0.0.0/8" AND DestinationIp!="172.16.0.0/12" AND DestinationIp!="192.168.0.0/16") : 수신지 IP주소가 사설주소인 건 제외 ⇒ 수신지가 공인IP인 로그들만 검색
  • | where total_count > 50 OR uniq_count > 20 : 수신지 IP로 50회 이상 접속되면

13.4. 네트워크 Shell 실행

  • 웹쉘 파일명을 기반으로 검색하는 수 밖에 없음
  • 시그니처를 기반으로 찾아내려면 IDS를 사용
index=sysmon sourcetype="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 
| where match(Image, "netsh.exe$")
| where NOT isnull(ParentImage)
| table ParentImage, Image, COmmandLine
  • where match(Image, "netsh.exe$") 윈도우 탐색기를 통해서

profile
공부 기록
post-custom-banner

0개의 댓글