Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
Sentinel-Queries — 위협 헌팅, Azure AD 로그인 로그 분석, 이상 징후 탐지, 그리고 효율적인 탐지 패턴 구축을 위한 Microsoft Sentinel KQL 쿼리와 튜토리얼의 엄선된 컬렉션입니다. | Kitploit
도구/GitHubGitHub/reprise99/sentinel-queries
Defensive ToolsThreat IntelligenceLearning & EducationCurated ResourcesAnomaly DetectionLog Analysis
GitHubreprise99/sentinel-queries

Sentinel-Queries

위협 헌팅, Azure AD 로그인 로그 분석, 이상 징후 탐지, 그리고 효율적인 탐지 패턴 구축을 위한 Microsoft Sentinel KQL 쿼리와 튜토리얼의 엄선된 컬렉션입니다.

저장소 보기
1.6k3826개월 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

Microsoft Sentinel용 KQL

Microsoft Sentinel에서 KQL을 사용하기 위한 몇 가지 팁, 요령 및 예제입니다.

  1. 소개
  2. KQL 쿼리의 구조
  3. 기본 사항
    1. 시간 기본 사항
    2. Where 기본 사항
    3. Project 기본 사항
    4. Summarize 기본 사항
    5. Render 기본 사항
    6. Parse 및 Split 기본 사항

소개

Kusto Query Language는 Azure Monitor, Azure Data Explorer 및 Azure Log Analytics(그 내부에서 Microsoft Sentinel이 사용하는 것) 전반에 걸쳐 사용되는 언어입니다. 저는 항상 KQL에 관한 이 시각화가 유용하다고 생각했습니다 -

KQL 시각화

우리는 더 큰 데이터 세트에서 위협, 탐지, 패턴 및 이상 징후를 찾기 위해 정확하고 효율적인 쿼리를 만드는 데 KQL을 사용하려고 합니다.

KQL 쿼리의 구조

아래 쿼리를 예로 들어 보겠습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent

root@kitploit:~
이와 같은 쿼리를 실행하면 첫 번째 줄은 Microsoft Sentinel에 데이터를 조회할 테이블을 알려줍니다. 따라서 이 경우에는 Azure AD 로그인 데이터가 전송되는 SigninLogs 테이블을 검색하려고 합니다. 테이블 목록은 [여기](https://docs.microsoft.com/en-us/azure/sentinel/data-source-schema-reference)에서 확인할 수 있습니다.

그런 다음 Microsoft Sentinel은 쿼리를 순차적으로 실행하므로 오류가 발생하거나 끝에 도달할 때까지 각 줄을 하나씩 실행합니다. 이제 쿼리를 줄별로 분석해 보겠습니다.```kql
SigninLogs

그래서 먼저 SigninLogs 테이블을 선택했습니다.```kql SigninLogs | where TimeGenerated > ago(14d)

root@kitploit:~
다음으로 우리는 Sentinel에게 이 테이블의 지난 14일 분량의 데이터를 다시 살펴보도록 지시합니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"

다음으로, Sentinel이 UserPrincipalName이 "[email protected]"인 로그만 찾도록 요청합니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0"

root@kitploit:~
그런 다음 ResultType == 0인 로그만 찾습니다. 이는 Azure AD에 대한 성공적인 로그온입니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| where AppDisplayName == "Microsoft Teams"

다음으로 Microsoft Teams에 대한 로그인만 찾아보겠습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent

root@kitploit:~
마지막 줄은 project 연산자를 사용하여 로그에서 4개의 필드만 반환하므로, SigninLogs 데이터에서 TimeGenerated, Location, IPAddress 및 UserAgent만 볼 수 있습니다.

이것이 쿼리를 작성하는 방법입니다. 이제 기본 사항입니다.

## 기본 사항

### 시간 기본 사항

Microsoft Sentinel과 KQL은 시간 필터에 매우 최적화되어 있으므로, 검색하려는 데이터의 기간을 알고 있다면 시간 범위를 바로 필터링해야 합니다. 지난 14일의 로그를 검색한 다음, 아래 쿼리처럼 사용자 이름을 검색하는 경우 -```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"

사용자 이름을 먼저 검색한 다음 기간을 검색하는 것보다 훨씬 더 효율적입니다. 다음과 같이 -```kql SigninLogs | where UserPrincipalName == "[email protected]" | where TimeGenerated > ago(14d)

root@kitploit:~
KQL에는 특정 기간을 쿼리하기 위한 다양한 옵션이 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)

첫 번째 예에서와 같이, 이는 지난 14일을 검색할 것입니다.```kql SigninLogs | where TimeGenerated > ago(14h)

root@kitploit:~
시간 단위로도 설정할 수 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14m)

그리고 분.

KQL은 또한 시간 범위 간의 조회를 지원합니다 -```kql SigninLogs | where TimeGenerated between (ago(14d) .. ago(7d))

root@kitploit:~
이것은 14일 전에서 7일 전 사이의 SigninLogs 데이터를 찾습니다.```kql
SigninLogs
| where TimeGenerated between (ago(14h) .. ago(7h))

14시간 전과 7시간 전 사이.```kql SigninLogs | where TimeGenerated between (ago(14m) .. ago(7m))

root@kitploit:~
그리고 14분 전에서 7분 전 사이에.

### Where 기초

Where는 작성하는 거의 모든 쿼리에서 사용하게 될 연산자입니다. 이것이 Microsoft Sentinel에 특정 데이터를 탐색하라고 지시하는 방법입니다. where 연산자에서는 구문이 매우 중요합니다. 동일한 예제를 사용한다면.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"

This will search our SigninLogs table, over the last 14 days, for exact matches where our UserPrincipalName equals [email protected]. In KQL == is case sensitive, so if you search for [email protected] and the username is actually [email protected], you won't get any results. The non case sensitive equivalent is =~

이것은 지난 14일 동안 SigninLogs 테이블에서 UserPrincipalName이 [email protected]과 정확히 일치하는 항목을 검색합니다. KQL에서 ==는 대소문자를 구분하므로 [email protected]으로 검색하고 실제 사용자 이름이 [email protected]인 경우 결과를 얻을 수 없습니다. 대소문자를 구분하지 않는 동등 연산자는 =입니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName = "[email protected]"

root@kitploit:~
이것은 대소문자 구분 없이 [email protected]과 일치하는 모든 항목을 찾습니다.

같음 대신에 포함을 사용할 수도 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName contains "reprise_99"

This will find any log entries where the UserPrincipalName contains reprise_99, if you had [email protected] and [email protected] data, it would find both. The contains operator is not case sensitive, but you can use contains_cs to make it case sensitive.

You can use either startswith or endswith if you are searching for particular patterns.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName startswith "reprise_99"

SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName endswith "testdomain.com"

root@kitploit:~
startswith와 endswith는 모두 대소문자를 구분하지 않지만, startswith_cs 또는 endswith_cs를 사용하여 대소문자를 구분하도록 만들 수 있습니다.

네 글자를 초과하는 전체 단어를 검색하는 경우 KQL에서 has 연산자를 사용할 수 있습니다. 데이터가 인덱싱되어 있으므로 'has'를 사용하는 것이 'contains'보다 더 효율적입니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName has "Teams"

이렇게 하면 애플리케이션 표시 이름에 Teams라는 단어가 포함된 모든 SigninLogs를 찾을 수 있습니다. 여기에는 "Microsoft Teams"와 "Microsoft Teams Web Client"가 포함될 수 있으며, 둘 다 쿼리를 충족합니다.

여러 단어를 검색하는 경우 has_any 또는 has_all을 사용할 수 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where AppDisplayName has_any ("Teams","Outlook")

root@kitploit:~
이것은 애플리케이션 표시 이름에 "Teams" 또는 "Outlook"이 포함된 결과를 반환합니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName has_all ("Teams","Outlook")

이렇게 하면 애플리케이션 표시 이름에 "Teams"와 "Outlook"이 포함된 결과가 반환됩니다.

검색할 필드를 모를 경우 와일드카드를 사용할 수도 있습니다. 비효율적일 수 있지만 원하는 항목을 찾는 데 올바른 방향을 제시해 줄 수 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where * contains "reprise_99"

root@kitploit:~
이것은 reprise_99를 포함하는 모든 필드에 대해 SigninLogs 테이블을 검색합니다.

이들 옵션 중 상당수는 !를 사용하여 쿼리를 반전시켜 조건이 참이 아닌 결과를 찾는 것도 지원합니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName != "[email protected]"

이 쿼리는 UserPrincipalName이 [email protected]과 같지 않은 모든 SigninLogs를 찾습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName !contains "reprise_99"

root@kitploit:~
이 쿼리는 UserPrincipalName에 reprise_99가 포함되어 있지 않은 모든 SigninLogs를 찾습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName !has "Teams"

이 쿼리는 애플리케이션 표시 이름에 "Teams"가 포함되지 않은 SigninLogs를 찾습니다.

Project 기본

Project를 사용하면 쿼리에서 반환되는 열과 그 순서를 선택할 수 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent

root@kitploit:~
이 쿼리는 최근 14일 동안의 SigninLogs 데이터를 검색하며, UserPrincipalname이 [email protected]이고, ResultType이 0이며, 애플리케이션 표시 이름이 "Microsoft Teams"인 항목을 찾습니다. 그런 다음 해당 쿼리의 각 일치 항목에 대해 TimeGenerated, Location, IPAddress 및 UserAgent를 반환합니다.

동일한 함수의 일부로 열 이름을 바꿀 수 있습니다.```kql
| project LogTime=TimeGenerated, SigninLocation=Location, IP=IPAddress, Agent=UserAgent

이것은 동일한 데이터를 반환하지만, 열 이름을 LogTime, SigninLocation, IP 및 Agent로 바꿉니다.

또한 project 연산자를 사용하여 출력을 인라인으로 조작할 수 있습니다.```kql | project LocalTime=TimeGenerated+5h, Location, IPAddress, UserAgent

root@kitploit:~
This returns the same data, but changes the TimeGenerated name to LocalTime and converts to a +5h time zone if you work in that time zone.

project-away is the opposite of project and will remove columns from your query.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| project-away UserAgent
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| where AppDisplayName == "Microsoft Teams"

이 쿼리에서는 UserAgent를 제거합니다. 열을 제거하면 쿼리에서 나중에 해당 열에 접근할 수 없다는 점을 기억하세요.

Summarize 기초

Summarize는 쿼리 콘텐츠를 집계하는 테이블을 생성합니다. Summarize에는 여러 기본 집계 함수가 있습니다. 예제 쿼리를 다시 사용하면, summarize를 사용하여 결과를 다양한 방식으로 조작할 수 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize count() by AppDisplayName

root@kitploit:~
이 쿼리는 지난 14일 동안의 SigninLogs 테이블에서 [email protected]에 대한 일치 항목을 찾아 결과가 성공(ResultType == 0)인 이벤트를 조회한 다음 해당 이벤트를 애플리케이션 표시 이름별로 요약합니다.

결과 열의 이름을 선택적으로 지정할 수 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize AppCount=count() by AppDisplayName

동일한 데이터를 반환하지만 반환된 열의 이름을 AppCount로 업데이트합니다.

전체 개수 대신 고유 개수(distinct count)를 요약할 수 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize DistinctAppCount=dcount(AppDisplayName) by AppDisplayName

root@kitploit:~
이것은 [email protected]이 로그인한 각각의 고유한 애플리케이션에 대해 단일 레코드를 반환합니다.

arg_max 및 arg_min 함수를 사용하여 쿼리와 일치하는 가장 최신 또는 가장 오래된 레코드를 반환할 수 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize arg_max(TimeGenerated, *) by UserPrincipalName

이 쿼리는 지난 14일 동안의 모든 로그인 로그 중 UserPrincipalName이 [email protected]이고 성공한 로그를 검색한 다음 최신 레코드를 반환합니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize arg_min(TimeGenerated, *) by UserPrincipalName

root@kitploit:~
이것은 동일하지만 가장 오래된 레코드를 반환합니다.

countif를 사용하여 합계에 논리를 제공할 수 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize TeamsLogons=countif(AppDisplayName has "Teams"), SharePointLogons=countif(AppDisplayName has "SharePoint")

이는 데이터를 두 개의 새 열, 즉 애플리케이션 표시 이름에 "Teams"가 포함된 TeamsLogons와 애플리케이션 표시 이름에 "SharePoint"가 포함된 SharePointLogons로 요약합니다.

KQL에 데이터를 시간 'bins'로 배치하도록 지시하여 데이터를 추가로 조작할 수 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppCount=count() by AppDisplayName, bin(TimeGenerated, 1d)

root@kitploit:~
이것은 첫 번째 summarize 예제와 동일한 데이터를 반환한 다음 해당 데이터를 1d bins로 그룹화합니다.

유용한 경우 이러한 함수들을 함께 결합할 수 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize TeamsLogons=countif(AppDisplayName has "Teams"), SharePointLogons=countif(AppDisplayName has "SharePoint") by bin(TimeGenerated, 1d)

이것은 countif 및 bin 함수의 조합으로, 애플리케이션 표시 이름을 기준으로 요약하고 결과를 1d 빈(bin)에 배치합니다.

쿼리 내에서 항목 집합을 만들 수 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppList=make_set(AppDisplayName) by UserPrincipalName

root@kitploit:~
이렇게 하면 [email protected]이 로그인한 애플리케이션 목록이 AppList라는 목록으로 출력됩니다.

이것을 우리의 시간 구간과 결합할 수 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize AppList=make_set(AppDisplayName) by UserPrincipalName, bin(TimeGenerated, 1d)

이렇게 하면 [email protected]이 로그인한 애플리케이션 목록이 날짜별로 하나의 목록으로 분리되어 생성됩니다.

Render 기본 사항

render 연산자를 사용하면 KQL에서 데이터를 원형 차트, 시간 또는 영역 차트, 세로 막대 및 가로 막대 차트 등 다양한 형식으로 시각화할 수 있습니다.

Signinlogs 테이블에서 사용한 동일한 예제를 사용하면 데이터를 다양한 방식으로 시각화할 수 있는 방법을 확인할 수 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppCount=count()by AppDisplayName | render piechart

root@kitploit:~
이 쿼리는 지난 14일 동안 [email protected]이 로그인한 모든 애플리케이션을 요약한 다음 출력을 파이차트로 렌더링합니다.

![KQL Piechart](https://raw.githubusercontent.com/reprise99/Sentinel-Queries/main/Diagrams/render-piechart.png?raw=true)

또한 세로 막대형 차트로 렌더링할 수도 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize AppCount=count()by AppDisplayName
| render columnchart

KQL 세로 막대형 차트

또는 가로 막대형 차트입니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppCount=count()by AppDisplayName | render barchart

root@kitploit:~
![KQL 막대 차트](https://raw.githubusercontent.com/reprise99/Sentinel-Queries/main/Diagrams/render-barchart.png?raw=true)

시간 데이터의 경우, 먼저 summarize 섹션에 설명된 대로 데이터를 시간 'bins'로 요약한 다음, 특정 기간에 걸쳐 데이터를 시각화할 수 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by bin(TimeGenerated, 1d)
| render timechart

이것은 지난 14일 동안 [email protected]의 모든 로그인을 일별로 시각화하여 시간 차트로 표시합니다.

KQL Timechart

또한 render를 areachart로 사용할 수도 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize SigninCount=count() by bin(TimeGenerated, 1d) | render areachart

root@kitploit:~
![KQL Areachart](https://raw.githubusercontent.com/reprise99/Sentinel-Queries/main/Diagrams/render-areachart.png?raw=true)

세로 막대형 차트와 가로 막대형 차트도 시간 데이터와 함께 사용할 수 있습니다. 더 큰 시간 범위에 걸쳐 시간 'bin'마다 열 또는 막대가 표시됩니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by bin(TimeGenerated, 1d)
| render columnchart

이는 타임차트와 동일한 쿼리이지만, 일별로 하나의 열을 가지는 세로 막대형 차트(column chart)로 렌더링한 것입니다.

KQL Time Column Chart```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize SigninCount=count() by bin(TimeGenerated, 1d) | render barchart

root@kitploit:~
그리고 막대 차트입니다.

![KQL Time Bar Chart](https://raw.githubusercontent.com/reprise99/Sentinel-Queries/main/Diagrams/render-timebarchart.png?raw=true)

세로 막대 차트나 막대 차트는 서로 쌓이도록(누적) 표시할 수 있습니다(기본값입니다).```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by AppDisplayName, bin(TimeGenerated, 1d)
| render columnchart

이 쿼리는 우리 계정의 모든 로그인을 찾아 애플리케이션별 로그인 수를 집계한 다음, 각 날짜에 대해 단일 열을 생성합니다.

KQL Time Column Chart Stacked

각 애플리케이션이 자체 열을 가지도록 하려면 스택 해제(unstacked)로 설정할 수 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize SigninCount=count() by bin(TimeGenerated, 1d) | render columnchart with (kind=unstacked)

root@kitploit:~
![KQL 시간 열 차트 비누적](https://raw.githubusercontent.com/reprise99/Sentinel-Queries/main/Diagrams/render-timecolumnchartunstacked.png?raw=true)

또한 차트의 축과 제목 이름을 KQL에 맞춰 변경할 수 있습니다.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by AppDisplayName, bin(TimeGenerated, 1d)
| render columnchart with (kind=unstacked, ytitle="Total Sign Ins", xtitle="Day", title="Application Signins Per Day")

KQL 시간 열 차트(비적층, 이름 변경됨)

summarize 작업에서 논리를 결합하여 render 연산자를 위한 동적 콘텐츠를 만들 수 있습니다.```kql SigninLogs | where TimeGenerated > ago(14d) | where ResultType == "0" | summarize TeamsCount=countif(AppDisplayName has "Teams"), OneDrive=countif(AppDisplayName has "OneDrive"), SharePointCount=countif(AppDisplayName has "SharePoint") by bin(TimeGenerated, 1d) | render columnchart with (kind=unstacked, ytitle="Sign In Count", xtitle="Day", title="Teams vs OneDrive vs SharePoint Sign Ins Per Day")

root@kitploit:~
이 쿼리는 테넌트에 대한 모든 로그인을 검색한 다음, 애플리케이션 표시 이름에 "Teams"가 있는 그룹, 애플리케이션 표시 이름에 "OneDrive"가 있는 그룹, 애플리케이션 표시 이름에 "SharePoint"가 있는 그룹의 세 그룹을 지난 14일 동안 날짜별로 집계한 후, 누적되지 않은 세로 막대형 차트로 렌더링합니다.

![KQL 시간 열 차트 Outlook, OneDrive, SharePoint](https://raw.githubusercontent.com/reprise99/Sentinel-Queries/main/Diagrams/render-timecolumn-outlookonedrivesharepoint.png?raw=true)

### Parse 및 Split 기초

Parse와 split은 일치 항목을 기준으로 문자열 데이터를 여러 열로 확장하는 두 가지 서로 다른 방식입니다. Microsoft Sentinel로 수집되는 많은 로그(예: sysmon)는 단일 긴 문자열로 들어올 수 있으며, parse와 split을 사용하면 이를 읽을 수 있는 데이터로 가공할 수 있습니다.

이 예제에서는 다음 테스트 데이터를 사용합니다.```kql
let ExampleText = datatable(TestData:string)
[
'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account'
];

이 데이터는 다음과 같이 생긴 단일 문자열일 뿐입니다.

Parse

다음과 같이 특정 데이터 일치 항목을 파싱할 수 있습니다.```kql let ExampleText = datatable(TestData:string) [ 'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account' ]; ExampleText | parse TestData with * 'Name=' DisplayName ',' * | project DisplayName

root@kitploit:~
이 명령은 Name=과 , 사이의 모든 데이터를 'DisplayName'이라는 새 열로 구문 분석합니다.

![Parse 1](https://raw.githubusercontent.com/reprise99/Sentinel-Queries/main/Diagrams/parse1.png?raw=true)

동일한 명령 내에서 문자열을 따라 매칭하여 여러 열을 구문 분석할 수 있습니다.```kql
let ExampleText = datatable(TestData:string)
[
'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account'
];
ExampleText
| parse TestData with * 'Name=' DisplayName ',UPNSuffix=' DomainSuffix ',AadTenantId=' AzureADTenantId ',' *
| project DisplayName, DomainSuffix, AzureADTenantId

이것은 DisplayName, DomainSuffix 및 AzureADTenantId라는 세 개의 새 열을 파싱합니다.

Parse 2

KQL은 작업을 순차적으로 실행한다는 점을 기억하면, 일단 파싱한 후에는 새로 생성된 열에서 다시 파싱할 수 있습니다.```kql let ExampleText = datatable(TestData:string) [ 'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account', ]; ExampleText | parse TestData with * 'Name=' DisplayName ',UPNSuffix=' DomainSuffix ',AadTenantId=' AzureADTenantId ',' * | project DisplayName, DomainSuffix, AzureADTenantId | parse DomainSuffix with * '.' TopLevelDomain | project DisplayName, DomainSuffix, TopLevelDomain, AzureADTenantId

root@kitploit:~
이는 도메인을 추가로 파싱하여 최상위 도메인을 찾습니다. 이 경우에는 .com입니다.

![Parse 3](https://raw.githubusercontent.com/reprise99/Sentinel-Queries/main/Diagrams/parse3.png?raw=true)

parse 연산자를 사용하면 KQL은 모든 데이터 행을 검토하고 일치하는 항목이 없는 결과도 반환합니다. 따라서 데이터 구조에 따라 빈 데이터가 많은 행이 생길 수 있습니다. 예제 데이터를 확장하여 다른 이름을 가진 행을 하나 더 포함하고 동일한 쿼리를 실행하면 빈 결과가 표시됩니다.```kql
let ExampleText = datatable(TestData:string)
[
'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account',
'Display=Reprise99,UPN=testdomain.com,AadDirectoryId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadObjectId=cf6f2df6-b754-48dc-b7bc-c8339caf211,Name=Test User,AccountType=account'
]
;
ExampleText
| parse TestData with * 'Name=' DisplayName ',UPNSuffix=' DomainSuffix ',AadTenantId=' AzureADTenantId ',' *
| project DisplayName, DomainSuffix, AzureADTenantId
| parse DomainSuffix with * '.' TopLevelDomain
| project DisplayName, DomainSuffix, TopLevelDomain, AzureADTenantId

Parse 4

이를 해결하려면 'parse-where' 연산자를 사용할 수 있습니다. 이 연산자는 쿼리와 일치하는 결과만 반환합니다.```kql let ExampleText = datatable(TestData:string) [ 'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account', 'Display=Reprise99,UPN=testdomain.com,AadDirectoryId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadObjectId=cf6f2df6-b754-48dc-b7bc-c8339caf211,Name=Test User,AccountType=account' ] ; ExampleText | parse-where TestData with * 'Name=' DisplayName ',UPNSuffix=' DomainSuffix ',AadTenantId=' AzureADTenantId ',' * | project DisplayName, DomainSuffix, AzureADTenantId | parse DomainSuffix with * '.' TopLevelDomain | project DisplayName, DomainSuffix, TopLevelDomain, AzureADTenantId

root@kitploit:~
파싱에서 일치 항목이 있었던 단일 결과로 돌아온 것을 볼 수 있습니다.

![Parse 5](https://raw.githubusercontent.com/reprise99/Sentinel-Queries/main/Diagrams/parse5.png?raw=true)

Split은 구분자를 기준으로 텍스트 문자열을 배열로 분리합니다. 원래 테스트 데이터로 돌아가면, 쉼표 기호를 기준으로 split할 수 있습니다.```kql
let ExampleText = datatable(TestData:string)
[
'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account',
]
;
ExampleText
| extend SplitData = split(TestData,',')
| project SplitData

문자열이 분할된 배열이 반환됩니다.

Split 1

Split은 인덱스를 인식하므로 데이터가 동일한 순서로 있으면 새 열로 직접 분할할 수 있습니다.```kql let ExampleText = datatable(TestData:string) [ 'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account', ] ; ExampleText | extend Name = split(TestData,',')[0] | extend DomainSuffix = split(TestData,',')[1] | extend AzureADTenantId = split(TestData,',')[2] | extend AzureADUserId = split(TestData,',')[3] | extend DisplayName = split(TestData,',')[4] | extend AccountType = split(TestData,',')[5] | project Name, DomainSuffix, AzureADTenantId, AzureADUserId, DisplayName, AccountType

root@kitploit:~
문자열 내에서 데이터 위치를 알고 있다면 데이터를 명명된 열로 직접 분할할 수 있습니다.

![Split 2](https://raw.githubusercontent.com/reprise99/Sentinel-Queries/main/Diagrams/split2.png?raw=true)

데이터를 분할한 후에는 처음부터 구조화된 것처럼 쿼리할 수 있습니다. 따라서 데이터에 두 번째 레코드를 추가한 다음 특정 일치 항목을 쿼리하면 원하는 결과를 찾을 수 있습니다.```kql
let ExampleText = datatable(TestData:string)
[
'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account',
'Name=Reprise103,UPNSuffix=testdomain.com,AadTenantId=331c1234-a841-43e5-1d31-12220a5bcee1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User 2,Type=account'
]
;
ExampleText
| extend Name = split(TestData,',')[0]
| extend DomainSuffix = split(TestData,',')[1]
| extend AzureADTenantId = split(TestData,',')[2]
| extend AzureADUserId = split(TestData,',')[3]
| extend DisplayName = split(TestData,',')[4]
| extend AccountType = split(TestData,',')[5]
| project Name, DomainSuffix, AzureADTenantId, AzureADUserId, DisplayName, AccountType
| where Name contains "Reprise99"

"Reprise99"가 Name에 포함된 경우 하나의 적중 결과만 얻을 수 있고, "Reprise103"이 Name에 포함된 두 번째 레코드는 찾을 수 없습니다.

Split 3

도구 다운로드