SIEM(Security Information & Event Management) 솔루션 중 하나로 기업 정보에 대한 종합 관제 솔루션
Splunk Forwarder는 Agent 프로그램으로 실시간으로 Splunk Server에 로그를 보내준다.
[설치환경]
- Win10
- Splunk Enterprise 9.0.1 Ver.
Username : splunk / Password : 12345678
http://localhost:8000 또는 http://127.0.0.1:8000 접속
설치할 때 설정했던 Username 과 Password 입력 후 로그인
분석할 데이터 추가하는 방법 : 설정 > 데이터 추가 > 업로드
업로드 하는 파일은 반드시 압축파일 그대로 올리기
파일이 성공적으로 업로드되면 [검색 시작] 클릭
>
를 클릭하면 상세하게 볼 수 있다.
검색을 통해서도 로그를 분석할 수 있지만 Splunk에서 자동으로 분석해서 필드에 대한 통계를 내주기 때문에 필드를 참고할 수 있다.
사내망의 로그들에 대해
""
사용✅ 검색 시 체크포인트
- 괄호를 이용하여 우선순위 지정하기
- 시간 점검하기
- 자동완성기능 이용하기
index=main sourcetype="access_combined_wcookie"
index=main sourcetype=access_combined_wcookie
| table clientip, method, productId, status
index=main sourcetype=access_combined_wcookie
| table clientip, action, productId, status
| rename action AS "Customer Action", productId AS ProductID, status AS "HTTP Status"
검색 결과에서 중복 제거
index=main sourcetype=access_combined_wcookie status=404
index=main sourcetype=access_combined_wcookie status=404 | dedup host
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)
index=main sourcetype="access_combined_wcookie"
| stats sum(bytes), avg(bytes), max(bytes), median(bytes), min(bytes) by clientip
빈도 수가 많은 값의 순서를 추출
* | top useother=T clientip by method
top과 반대의 결과로 빈도 수가 적은 값의 순서를 추출
* | rare useother=T clientip by method
테이블로 표시된 결과를 다양한 차트로도 만들 수 있다.
검사 결과 값의 반환, 검증을 수행하며 함수 실행 결과 값을 반환
# 변수명을 선언
| eval [반환값_저장변수] = 함수(인자1, 인자2..)
sourcetype=access_*
| eval status_code=if(status==200, "OK", "Error")
| table clientip status_code
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”)
| 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)
* | 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” 이 할당
…| where like(field, “add%”)
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 절을 수행한다.
/data/utility/tool/GoogleToolbar.exe
를 split(uri,"/") 하면 총 4개로 분할된다. 인덱스는 0번부터 시작한다. data
utility
tool
GoogleToolbar.exe
/data/utility/tool/GoogleToolbar.exe
를 mvindex(split(uri,”/”), -1) 하면 /를 구분자로 총 4개로 분할 data
utility
tool
GoogleToolbar.exe
후 GoogleToolbar.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
리눅스 로그와 유사하게 /var/lib/splunk 위치에 로그가 있다.
쉼표로 구분된 필드 이름
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
내가 설정할 필드보다 많은 이유? Splunk에서 자동 인식해서 만들어낸 필드가 포함되어 있기 때문에
표시여부가 No 로 되어 있으면 [속성 편집] 에서 예로 바꿔주기
앱이 정상적으로 설치되면 Splunk 메인화면에서 확인할 수 있다.
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 : 질의한 도메인의 응답코드
rcode rcode_name 서명 0 NoError 오류 없음 1 FromErr Qury 형식 오류 2 SevFail DNS 서버 자체의 문제로 실패 3 NXDomain DNS 클라이언트가 존재하지 않는 도메인으로 질의 4 Notlmp DNS 서버가 해당 질의를 지원 X 5 Refused 정책적인 이유로 질의를 거절
- AA : 인증서버답변여부
- TC : 전체질의가 잘렸는지 여부
- RD : 재귀질의 요청여부
- RA : 재귀질의 가능여부
- Z : 예약된 필드로 사용하지 않음
- Answers : DNS서버에서 반환한 답변내역
- TTLs : 도메인의 TTL값, 질의 답변의 캐시보관기간 (3600초(1시간 기본))
- Reject : 질의가 거부됐는지 판단
전반적인 통계치를 파악하기 위함이다.
현황분석 시 숫자에서 이상 징후를 유추해야 한다.
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 필드 값은 제거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
: 수신지 포트 53domain!="*.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 가 가장 많이 접속했다.
특정 몇몇 사람만 많이 접속했는지 여러 명이 접속했는지 파악할 필요가 있다.
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주소172.16.146.107 와 172.16.138.48가 teredo.ipv6.microsoft.com 에 비정상적으로 많은 접속 횟수로 보아 매크로를 돌렸을 가능성이 높다.
index=dnslog sourcetype=dnslog domain!="-" rcode_name= "NXDomain"
| top showperc=f src, domain
도메인 검색 시 오타가 날 수도 있기 때문에 NXDomain 이 1~2건 정도는 있을 수 있으나 지속적인 NXDomain이 발생하면 점검이 필요하다. 왜? 감염된 PC가 사라진 도메인에 접속을 시도하는걸수도 있기 때문에
💡 NXDomain이란?
존재하지 않는 도메인에 접속을 시도하는 경우 발생하는 에러
💡 서브 도메인(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자를 기준으로 한다. 공격자가 접속사이트를 숨기기 위해서 길게 만든다.
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 필드 값을 기준으로 검색 결과를 내림차순으로 정렬
쉼표로 구분된 필드 이름 : 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
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
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번 접속했다면 점검해볼 필요가 있다.
보통 직원들이 접속하는 사이트의 국가는 정해져 있는데 희안한 국가 사이트가 있다면 프록시를 사용중일 수 있기 때문에 모니터링할 필요가 있다.
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개만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)가 발생한 로그만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)가 발생한 로그만index=httplog sourcetype=httplog domain!="(empty)" status_code!="-"
| top limit=10 showperc=f status_code
HTTP 응답코드별로 카운팅해서 상위 10개까지만 표시
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 기준으로 내림차순으로 정렬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를 기준으로 내림차순으로 정렬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로 구성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
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 공격을 위한 실행 파일이 있는지 확인하기 위함
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에 프로토콜부터 전체 주소가 보여지면 프록시를 사용했음을 알 수 있다.
기본 윈도우 이벤트 뷰어에서는 프로세스 생성, 네트워크 연결, 파일 생성 시간 변경 등의 정보를 추출한 후 윈도우 이벤트 저장소에 저장할 수 없기 때문에 ⇒ 이벤트 기반 정보가 아닌 행동 기반 정보를 수집해서 이벤트 저장소에 저장
일반적으로 클라이언트 PC를 지칭하는 말
EndPoint로그 == 윈도우 로그를 기반 ⇒ 악성코드에 감염된 PC들의 이상징후를 탐지
이렇게 하는 이유? 원래 Forworder 에서 실시간으로 로그를 받아와야하지만 그게 현재 불가능하기 때문에
설정 > 시스템 > 서버 컨트롤
검색창에 index=sysmon
을 검색했을 때 로그가 뜨면 성공!
이벤트 뷰어 > 응용 프로그램 및 서비스 로그 > Microsoft > Sysmon > Operational
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차 검색, 하위 검색 ⇒ 검색 범위를 줄이기 위함💡 프로그램과 프로세스의 차이
프로그램 : 하드디스크에 저장된 소스코드 (정적인 상태)
프로세스 : 실행 중인 프로그램
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 *"
: 하드디스크에서 실행된 파일을 검색 후 삭제된 파일이 있다면 해당 파일의 원본 이름을 알려줘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회 이상 접속되면 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$")
윈도우 탐색기를 통해서