> For the complete documentation index, see [llms.txt](https://docs.panther.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.panther.com/ko/search/scheduled-searches/examples.md).

# 예약 검색 예시

이 페이지에는 로그에서 의심스러운 활동을 조사할 때 사용할 수 있는 일반적인 사용 사례와 예제 검색이 포함되어 있습니다.

아래 예제는 효과적으로 사용하려면 로컬 환경에 맞게 일부 사용자 지정이 필요합니다. 모든 쿼리는 결과 크기를 제어해야 합니다. 이는 다음을 사용해 할 수 있습니다. `LIMIT` 또는 `GROUP BY` 절.

{% hint style="warning" %}
예약 검색이 실행될 때마다 귀사의 데이터 플랫폼에 비용이 발생합니다. 쿼리가 지정된 타임아웃 시간 내에 완료될 수 있는지 확인하세요.
{% endhint %}

### 스트리밍 룰

Panther를 사용하면 다음을 실행할 수 있습니다: [예약 검색](/ko/search/scheduled-searches.md) ([저장된 검색 ](/ko/search/scheduled-searches.md)주기적으로 실행되고, Scheduled 룰과 함께 작동하여 긴 시간 범위에 걸쳐 동작하고 여러 소스의 데이터를 집계하는 디택션을 만들 수 있습니다.

가능하면 스트리밍 룰을 사용해 디택션을 구현해 보세요. 스트리밍 룰은 지연 시간이 짧고 예약 검색보다 비용이 적게 듭니다. 하지만 디택션에 더 많은 컨텍스트가 필요한 경우에는 예약 검색이 적절한 해결책입니다.

### 데이터 지연 시간 및 타이밍 고려 사항

효과적인 Scheduled 룰을 작성하려면 이벤트가 기록된 시점부터 Panther에 도달할 때까지의 데이터 지연 시간을 이해해야 합니다. 이 정보를 사용해 예약 시간과 사용되는 시간 창을 적절히 조정하세요.

예를 들어, AWS CloudTrail 데이터는 약 10분의 지연 시간이 있습니다. 이는 Panther 기능 때문이 아니라 AWS의 제한 때문입니다. 마지막 1시간의 데이터를 분석하려는 경우, 모범 사례로는 검색을 매시 15분에 실행하도록 예약하는 것을 권장합니다.

데이터가 시스템에 들어와 처음 처리될 때 적용되므로 Panther 스트리밍 룰에는 이 고려 사항이 적용되지 않습니다. 예약 검색은 축적된 데이터를 주기적으로 되돌아보기 때문에 타이밍 고려 사항이 중요합니다.

## 예시

### 특정 IP 주소로 AWS 콘솔 액세스를 제한

예약 검색과 예약 룰을 사용해 디택션을 만드는 과정을 더 잘 이해하기 위해, 아주 간단한 엔드 투 엔드 예제부터 시작해 보겠습니다. 회사 IP 대역에서만 AWS 콘솔에 접근하도록 매우 엄격하게 액세스를 제한한다고 가정해 봅시다. 이 제어를 검증하려면 모든 AWS 콘솔 로그인에 다음이 있는지 확인하고 싶습니다: `sourceIPAddress` 이 IP 대역 내에서 발생했는지 확인해야 합니다. 이 IP 블록들의 테이블을 data lake에 보관하고 있습니다. 참고: 이 예제는 단순한 동등 조인 연산을 사용하며 IP 블록 테이블의 모든 항목이 /32 주소라고 가정합니다. 실제 구현에서는 IP 블록 테이블에 CIDR 블록이 있고, 검사는 CIDR 블록 내부에 없는 항목을 찾는 방식이 됩니다. 이는 독자를 위한 연습 문제로 남겨 둡니다.

룰을 15분마다 실행하도록 예약하고, 이전 30분을 확인한다고 가정해 봅시다(CloudTrail과 연관된 데이터의 내재된 지연을 처리하기 위해 긴 윈도우를 사용합니다).

솔직히 말하면: 당신은 *할 수 있습니다* IP 허용 목록 테이블을 DynamoDB나 S3에서 관리한다면 이 디택션을 스트리밍 룰로 구현할 수 있습니다. 로그 소스의 볼륨이 매우 높다면 아래와 같이 주기적인 배치 조인을 수행하는 것이 더 효율적입니다.

이 이벤트를 탐지하는 쿼리는 다음과 같습니다:

{% tabs %}
{% tab title="SQL" %}

```sql
SELECT
    ct.*
FROM
    panther_logs.public.aws_cloudtrail ct LEFT OUTER JOIN infrastructure.networking.egress_blocks egress
        ON (ct.sourceIPAddress = egress.ip)
WHERE
    p_occurs_since('30 minutes') 
    AND
    ct.eventtype = 'AwsConsoleSignIn'
    AND 
    egress.ip IS NULL -- 일치하지 않음!
LIMIT 1000 -- 이런 항목이 많을 것으로 예상하지는 않지만, 안전을 위해 여기에 넣어 두었습니다!
```

다음에 주목하세요: `p_occurs_since()` Panther의 [SQL 매크로](/ko/search/data-explorer.md) 예약 쿼리 생성이 더 쉬워집니다.
{% endtab %}

{% tab title="PantherFlow" %}
{% hint style="warning" %}
현재는 예약 검색을 생성할 수 없습니다 [예약 검색](/ko/search/scheduled-searches.md) 내 [PantherFlow](/ko/pantherflow.md). 다음을 참조하세요: [PantherFlow의 제한 사항](/ko/pantherflow.md#limitations-of-pantherflow) 에서 자세히 알아보세요.
{% endhint %}

```kusto
panther_logs.public.aws_cloudtrail
| where p_event_time > time.ago(30m) 
    and eventtype == 'AwsConsoleSignIn' 
| join kind=leftouter egress=(infrastructure.networking.egress_blocks) 
     on $left.sourceIPAddress == $right.ip
| where egress.ip == null
| limit 1000
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
예약 검색의 출력이 예약 룰(Python)을 거치므로, 반환되는 행 수를 신중하게 제어하는 것이 중요합니다. 다음을 권장합니다: *항상* 다음을 제공하세요: `LIMIT` 절 또는 다음을 사용하세요: `GROUP BY` 제한된 수의 행만 반환하는 집계를 사용하세요(최대 수천 개 미만).
{% endhint %}

이를 구현하려면:

1. 예약된 검색 만들기 [다음 지침을 따르세요](/ko/search/scheduled-searches.md#how-to-create-a-scheduled-search).
   * 아래 예시 스크린샷에서는 30분마다 실행되는 주기가 선택되어 있습니다.\
     ![The image shows the query creation form. It contains fields for Query Name, Tags, and Description. The name is set to "Sketchy AWS Console Logins."The option "Period" is selected, and a dropdown labeled "Period (min)" is set to 30.](/files/ac8cda6cd05fd3432510426ebbde9b1ba8240c1b)
2. 예약 검색이 활성화로 설정되어 있는지 확인하세요.
3. 다음을 만드세요: [예약된 룰](/ko/detections/rules.md) 예약 검색의 출력에 맞게 대상이 지정된.\
   ![The image shows the Scheduled Rule creation page. The "Scheduled Queries" dropdown is set to "Sketchy AWS Console Logins."](/files/b88f70c604cf8aa765ce7b9a4d31d5b206491b2e)![An example Python rule is written under "Rule Function" in the Scheduled Rule creation page.](/files/0e6bbab26fa421099c6a055aa2813904781e8b21)

예약 룰은 스트리밍 룰의 모든 기능을 제공하여 알러트를 사용자 지정하고 대상에 직접 전송할 수 있게 합니다. Panther의 중복 제거는 알러트 폭주를 방지하며, 위의 룰에서는 다음을 사용합니다: `sourceIPAddress` 30분마다 알러트 1개만 생성하는 dedupe를 사용합니다.

목록과 조인하는 이 패턴은 IOC 디택션에도 사용할 수 있습니다(TOR 종료 노드, 악성코드 해시 등 IOC 테이블을 유지).

### Command and Control (C2) 비콘 디택션

이 예제에서는 집계를 사용해 C2 비콘을 찾는 매우 단순하지만 효과적인 행동 기반 디택션을 만듭니다.

{% hint style="info" %}
이는 설명 목적을 위한 지나치게 단순화된 디택션입니다. 허용 목록 지정 및 임계값 조정과 같은 개선 없이 사용하면 과도한 오탐이 발생할 수 있습니다("beacon"하는 비악성 프로세스가 많습니다). 그렇지만 네트워크를 잘 이해하고 적절한 허용 목록을 사용하면 이 기법은 매우 효과적일 수 있습니다.
{% endhint %}

C2 비콘은 다음과 같이 발생하는 모든 IP 활동으로 정의합니다 *최대* 하루에 5회 이하이며 3일 이상 반복되는 경우입니다. 이를 구현하려면:

{% tabs %}
{% tab title="SQL" %}

```sql
WITH low_and_slow_daily_ip_activity AS (
SELECT
    date(p_event_time) as day,
    srcAddr as beacon_ip
FROM
    panther_logs.public.aws_vpcflow
WHERE
    p_occurs_since('1 week')
GROUP BY 1,2
HAVING count(1) <= 5 -- 활동이 적은 것만, 이 부분은 조정이 필요합니다
)
SELECT
 beacon_ip,
 count(1) as days
FROM
 low_and_slow_daily_ip_activity
GROUP BY 
 1
HAVING days >= 3 -- 최소 이 일수 이상 활동한 것만
LIMIT 20 -- 알러트 폭주 방지!
```

{% endtab %}

{% tab title="PantherFlow" %}
{% hint style="warning" %}
현재는 예약 검색을 생성할 수 없습니다 [예약 검색](/ko/search/scheduled-searches.md) 내 [PantherFlow](/ko/pantherflow.md). 다음을 참조하세요: [PantherFlow의 제한 사항](/ko/pantherflow.md#limitations-of-pantherflow) 에서 자세히 알아보세요.
{% endhint %}

```kusto
panther_logs.public.aws_vpcflow
| where p_event_time > time.ago(7d)
| summarize count=agg.count() by day=time.trunc('d', p_event_time), beacon_ip=srcAddr
| where count <= 5  // 활동이 적은 것만, 이 부분은 조정이 필요합니다
| summarize days=agg.count() by beacon_ip
| where days >= 3   // 최소 이 일수 이상 활동한 것만
| limit 20          // 알러트 폭주 방지!
```

{% endtab %}
{% endtabs %}

이를 구현하려면:

1. 예약된 검색 만들기 [다음 지침을 따르세요](/ko/search/scheduled-searches.md#how-to-create-a-scheduled-search).
   * 아래 예시 스크린샷에서는 Cron 표현식을 사용해 매일 자정 1분 후에 실행되도록 설정합니다.\
     ![The query creation screen is open, and the Query Name is set to "C2 Beacons." The option "Cron Expression" is selected. Minutes is set to 1, and Timeout is set to 1.](/files/12983f4131cd9c4ed933e5cf794bb50b9299b72b)
2. 예약 검색이 활성화로 설정되어 있는지 확인하세요.
3. 다음을 만드세요: [예약된 룰](/ko/detections/rules.md) 예약 검색의 출력에 맞게 대상이 지정된:\
   ![The Scheduled Rule creation screen is displayed. The "scheduled queries" dropdown is set to "C2 Beacons."](/files/fdb8e509161056e1eb41bddf0bf4215cc85bf48a)![An example Python rule is written in the "Rule Function" text box on the Scheduled Rule creation page.](/files/45fdecb10760c7d127f325952cebad1a1dfc98ab)

### 엔드포인트 모니터링이 얼마나 잘 작동하고 있나요?

이 가상의 예제에서는 CrowdStrike를 엔드포인트 모니터링 소프트웨어로 사용한다고 가정합니다. Panther는 로그를 수집하도록 설정되어 있고, 당신은 다음을 가지고 있습니다: [CMDB](https://en.wikipedia.org/wiki/Configuration_management_database) 배포된 에이전트를 내부의 관련 사용자에 매핑한 데이터가 채워져 있습니다.

다음과 같은 *많은* 이 데이터로 물어볼 수 있는 흥미로운 질문이 많지만, 이 예제에서는 특히 "지난 24시간 동안 ANY 데이터를 보고하지 않은 엔드포인트는 무엇인가?"라는 질문을 하겠습니다.

CrowdStrike 로그에서 배포된 에이전트의 고유 ID를 다음이라고 합니다: `aid` . CMDB에는 다음의 매핑이 있습니다: `aid` 참조 데이터로의 매핑입니다. 이 예제에서는 다음 속성이 있다고 가정합니다: `employee_name`, `employee_group` 그리고 `last_seen`. 직원 관련 속성은 현재 엔드포인트를 사용하는 사람과 다음을 식별하는 데 도움이 됩니다: `last_seen` 이는 네트워크 활동(VPN 액세스, DHCP 임대, 인증, 생존 디택션 등)을 추적하는 백엔드 프로세스에 의해 업데이트된다고 가정하는 타임스탬프입니다.

이 질문에 답하려면, CMDB에서 다음 조건을 만족하는 에이전트가 무엇인지 알아야 합니다: *실제로* 지난 24시간 내에 네트워크 활동이 있지만, 다음은 *이 아니라* CrowdStrike 활동은 없는 경우입니다. 이는 에이전트가 실행되지 않거나 비활성화되었음을 의미할 수 있습니다(커버리지 공백을 나타냄). 아래 쿼리는 의심되는 특정 엔드포인트를 포함한 직원 그룹별 보고서를 계산합니다:

{% tabs %}
{% tab title="SQL" %}

```sql
WITH active_aids AS (
SELECT
     DISTINCT aid -- 고유한 에이전트 ID만 매칭
FROM panther_logs.public.crowdstrike_aidmaster
WHERE p_occurs_since('1 day') -- 로그 데이터의 최근 24시간
)
SELECT
    cmdb.employee_group,
    count(1) AS num_inactive_endpoints,
    ARRAY_AGG(
        DISTINCT cmdb.aid || ' ' || cmdb.employee_name || ' ' || cmdb.employee_group
    ) as endpoints -- 여기서 특정 엔드포인트를 수집합니다
FROM 
  infrastructure.inventory.cmdb AS cmdb LEFT OUTER JOIN active_aids cs USING(aid)
WHERE
  cs.aid IS NULL -- 어떤 로그 데이터와도 일치하지 않음
AND 
  cmdb.last_seen BETWEEN current_date - 1 AND current_date -- 활성 상태일 가능성이 높음
GROUP BY 1
ORDER BY 2 desc
```

{% endtab %}

{% tab title="PantherFlow" %}
{% hint style="warning" %}
현재는 예약 검색을 생성할 수 없습니다 [예약 검색](/ko/search/scheduled-searches.md) 내 [PantherFlow](/ko/pantherflow.md). 다음을 참조하세요: [PantherFlow의 제한 사항](/ko/pantherflow.md#limitations-of-pantherflow) 에서 자세히 알아보세요.
{% endhint %}

```kusto
let active_aids = panther_logs.public.crowdstrike_aidmaster
| where p_event_time > time.ago(1d)
| summarize by aid;

infrastructure.inventory.cmdb
| where last_seen > time.ago(1d)
| join kind=leftouter aa=(active_aids) on $left.aid == $right.aid
| where aid == null
| extend endpoints=strings.cat(aid, ' ', employee_name, ' ', employee_group)
| summarize num_inactive_endpoints=agg.count(),
            endpoints=agg.make_set(endpoints) by employee_group 
| sort num_inactive_endpoints
```

{% endtab %}
{% endtabs %}

이를 구현하려면:

1. 예약된 검색 만들기 [다음 지침을 따르세요](/ko/search/scheduled-searches.md#how-to-create-a-scheduled-search).
   * 아래 예시 스크린샷에서는 Cron 표현식을 사용해 매일 자정 1분 후에 실행되도록 설정합니다.\
     ![The query creation page is displayed. The query name is "Inactive Crowdstrike Endpoints." The option "Cron expression" is selected. Minutes is set to 1 and Timeout is set to 1.](/files/218646fb5d41325bac1b791f14f20a0a2afa3f3e)
2. 예약 검색이 활성화로 설정되어 있는지 확인하세요.
3. 다음을 만드세요: [예약된 룰](/ko/detections/rules.md) 예약 검색의 출력에 맞게 대상이 지정된:

<figure><img src="/files/28ac11baa4efa5b96fe0ec4be8b619ba7b8377bc" alt="The image shows an example function written in Python. The Test box is open and has an example test written in it." width="563"><figcaption><p>CrowdStrike룰</p></figcaption></figure>

알러트와 관련된 이벤트는 분석가가 검토할 수 있으며, 직원 그룹당 최대 하나입니다. "히트"는 다음에 누적됩니다: `endpoints` 직원 정보를 사용해 쉽게 검토할 수 있습니다. 모든 Panther 룰과 마찬가지로 알러트 대상 위치를 사용자 지정할 수 있는 유연성이 있습니다. 예를 들어, 만약 다음과 같다면: `employee_group` 있거나 `C-Suite` 그 경우 온콜에 즉시 페이지를 보낼 수 있고, 기본 알러트는 단순히 다음 날 검토할 작업 큐로 보내집니다.

### 비정상적인 Okta 로그인

해당 [Okta 로그](/ko/data-onboarding/supported-logs/okta.md) 이벤트와 관련된 "누가", "어떤 장치로", "어디서"라는 질문에 대한 답을 제공합니다. 이 정보는 예를 들어 탈취한 자격 증명을 사용하는 공격자와 같은 의심스러운 행동을 식별하는 데 사용할 수 있습니다.

"의심스러운"을 정의하는 것이 과제입니다. 의심스러움을 정의하는 한 가지 방법은 정상에서의 벗어남입니다. 각 사용자에 대한 기준선을 만들 수 있다면, 의미 있는 변화가 있을 때 알러트할 수 있습니다.

좋아 보이지만, 이제 유용한 보안 결과를 생성하는 방식으로 "의미 있는 변화"를 정의해야 합니다(그리고 많은 오탐은 피해야 합니다). 이 예제에서는 다음에 대한 의미 있는 변화를 대상으로 합니다: `클라이언트` 탈취된 자격 증명을 나타낼 수 있는 정보입니다. 참고: Okta 데이터는 매우 풍부한 컨텍스트를 제공하며, 이는 이 데이터를 활용하는 한 가지 단순한 예일 뿐입니다.

VPN과 프록시 때문에 특정 IP 주소나 관련 지리 정보만으로 의심 활동을 식별하는 것은 종종 실용적이지 않습니다. 마찬가지로 사용자는 새 장치를 사용하게 되어 장치를 바꿀 수 있고, 여러 장치를 사용할 수도 있습니다. 정상 사용자 간에는 상당한 차이가 있을 것으로 예상합니다. 그러나 특정 사용자에 대해서는 시간이 지날수록 더 큰 일관성이 있을 것으로 예상합니다.

이 예제에서는 각 항목에 대해 계산하여 "정상"을 정의합니다: `행위자`, 과거 최대 30일 동안:

* 사용된 고유 auth 클라이언트
* 사용된 고유 OS 버전
* 사용된 고유 장치
* 사용된 고유 위치(정의: 국가, 주, 도시)

네 가지 차원 중 어느 것과도 ANY 일치하지 않는 이벤트를 "의심스러운" 것으로 정의합니다. 이는 다음을 의미합니다:

* 새 장치를 사용하게 되더라도 알러트를 받지 않습니다.
* 위치를 변경해도 알러트를 받지 않습니다.
* 우리는 *도* 모든 속성이 한꺼번에 변경될 때 알러트를 받습니다,

  그리고 이것이 보안 관점에서 모두 이상하고 흥미롭다고 가정합니다.

또한 새 직원으로 인한 오탐을 피하기 위해 최소 5일 이상의 기록이 없는 actor는 고려하지 않습니다.

이것을 이전 날에 대해 하루 한 번 실행하도록 예약한다고 가정합니다.

이는 단지 예제일 뿐이며 다른 휴리스틱과 마찬가지로 조정이 필요하지만, actor별로 자동 보정된다는 장점이 있습니다.

위 내용을 계산하는 SQL은 다음과 같습니다:

{% tabs %}
{% tab title="SQL" %}

```sql
WITH actor_baseline AS (
  SELECT
    actor:id as actor_id,
    ARRAY_AGG (DISTINCT client:id) as client_id_list,
    ARRAY_AGG (DISTINCT client:userAgent.os)  as client_os_list,
    ARRAY_AGG (DISTINCT client:device) as client_devices_list,
    ARRAY_AGG (DISTINCT 
       client:geographicalContext:country || client:geographicalContext:state || client:geographicalContext:city)
      as client_locations_list
  FROM
    panther_logs.public.okta_systemlog
  WHERE
    p_occurs_since('30 days') 
  GROUP BY 1
  HAVING
    COUNT(DISTINCT date(p_event_time)) > 5 -- 최소 5일 이상의 기록
)
-- 현재 일자와 기준선을 비교하여 기준선 속성 중 어느 것과도 일치하지 않는 이벤트를 반환
SELECT
  logs.*
FROM
  panther_logs.public.okta_systemlog logs JOIN actor_baseline bl ON (actor:id = bl.actor_id)
WHERE
  p_occurs_since('1 day') 
  AND
  NOT ARRAY_CONTAINS(logs.client:id::variant, bl.client_id_list)
  AND
  NOT ARRAY_CONTAINS(logs.client:userAgent:os::variant, bl.client_os_list)
  AND
  NOT ARRAY_CONTAINS(logs.client:device::variant, bl.client_devices_list)
  AND
  NOT ARRAY_CONTAINS(
          (client:geographicalContext:country || 
           client:geographicalContext:state || 
           client:geographicalContext:city)::variant, bl.client_locations_list)
```

이러한 기준선을 검색이 실행될 때마다 다시 계산하는 것은 매우 비효율적입니다. 향후 Panther는 요약 테이블을 생성할 수 있는 기능을 지원하여 위와 같이 설명한 방법을 더 효율적으로 만들 수 있게 될 것입니다.
{% endtab %}

{% tab title="PantherFlow" %}
{% hint style="warning" %}
현재는 예약 검색을 생성할 수 없습니다 [예약 검색](/ko/search/scheduled-searches.md) 내 [PantherFlow](/ko/pantherflow.md). 다음을 참조하세요: [PantherFlow의 제한 사항](/ko/pantherflow.md#limitations-of-pantherflow) 에서 자세히 알아보세요.
{% endhint %}

```kusto
let actor_baseline = panther_logs.public.okta_systemlog
| where p_event_time > time.ago(30d)
| summarize unique_days=agg.count_distinct(time.trunc('d', p_event_time)),
            client_id_list=agg.make_set(client.id),
            cliend_os_list=agg.make_set(client.userAgent.os),
            client_devices_list=agg.make_set(client.device),
            client_locations_list=agg.make_set(strings.cat(client.geographicalContext.country, 
                                                           client.geographicalContext.state, 
                                                           client.geographicalContext.city)) by actor_id=actor.id
| where unique_days > 5;

panther_logs.public.okta_systemlog
| where p_event_time > time.ago(1d)
| join kind=inner ab=(actor_baseline) on $left.actor.id == $right.actor_id
| where client.id not in ab.client_id_list
    and client.userAgent.os not in ab.client_os_list
    and client.device not in ab.client_devices_list
    and strings.cat(client.geographicalContext.country, 
                    client.geographicalContext.state, 
                    client.geographicalContext.city) not in ab.client_locations_list
```

{% endtab %}
{% endtabs %}

### Password spraying 디택션

Password spraying은 몇 가지 흔히 사용되는 비밀번호로 많은 계정(사용자 이름)에 접근을 시도하는 공격입니다. 전통적인 무차별 대입 공격은 비밀번호를 추측하여 단일 계정에 무단으로 접근하려고 합니다. 이 경우 일반적인 계정 잠금 정책이 정해진 기간 동안 제한된 횟수(보통 3\~5회)의 실패한 시도만 허용하므로, 대상 계정이 빠르게 잠길 수 있습니다. password-spray 공격("low-and-slow" 방식이라고도 함)에서는 악의적 행위자가 하나의 흔히 사용되는 비밀번호(예: ‘password123’ 또는 ‘winter2017’)를 여러 계정에 시도한 뒤 두 번째 비밀번호를 시도하는 식으로 진행합니다. 이 기법을 통해 행위자는 빠르거나 잦은 계정 잠금을 피함으로써 탐지를 피할 수 있습니다.

이 행위를 디택션하는 핵심은 시간에 따라 집계하고 로그인 실패가 발생한 사용자 이름의 다양성을 살펴보는 것입니다. 아래 예제는 CloudTrail을 사용하지만, 유사한 기법은 어떤 인증 로그에도 사용할 수 있습니다. 선택한 임계값은 대상 네트워크에 맞게 조정해야 합니다.

{% tabs %}
{% tab title="SQL" %}

```sql
SELECT
  -- 이 정보는 알러트 이벤트에 포함됩니다
  awsRegion as region,
  recipientAccountId as accountid,
  COUNT(DISTINCT useridentity:userName) as distinctUserNames,
  COUNT(1) as failures,
  MIN(p_event_time) as first_attempt,
  MAX(p_event_time) as last_attempt
FROM
  panther_logs.public.aws_cloudtrail
WHERE
  -- 이것은 3600초(1시간)를 되돌아보는 Panther 매크로입니다
  p_occurs_since('1 hour') 
  AND
  eventtype = 'AwsConsoleSignIn'
  AND
  responseElements:ConsoleLogin = 'Failure'
GROUP BY
  region, accountid
HAVING
  distinctUserNames > 5
   AND
  failures > 10
```

{% endtab %}

{% tab title="PantherFlow" %}
{% hint style="warning" %}
현재는 예약 검색을 생성할 수 없습니다 [예약 검색](/ko/search/scheduled-searches.md) 내 [PantherFlow](/ko/pantherflow.md). 다음을 참조하세요: [PantherFlow의 제한 사항](/ko/pantherflow.md#limitations-of-pantherflow) 에서 자세히 알아보세요.
{% endhint %}

```kusto
panther_logs.public.aws_cloudtrail
| where p_event_time > time.ago(1h)
    and eventType == 'AwsConsoleSignIn'
    and responseElements.ConsoleLogin == 'Failure'
| summarize distinctUserNames=agg.count_distinct(useridentity.userName),
            failures=agg.count(),
            first_attempt=agg.min(p_event_time),
            last_attempt=agg.max(p_event_time) by region=awsRegion, accountid=recipientAccountId
| where distinctUserNames > 5 and failures > 10
```

{% endtab %}
{% endtabs %}

### DNS 터널 디택션

대부분의 네트워크에서는 DNS를 일반적으로 차단할 수 없기 때문에, DNS 기반 데이터 유출 및 C2는 매우 효과적일 수 있습니다. DNS 기반 터널을 만드는 도구도 많이 있습니다. 모든 DNS 터널이 악성인 것은 아니며, 아이러니하게도 많은 백신 도구가 DNS 터널을 사용해 원격으로 텔레메트리를 보냅니다. 보안에 관심 있는 사람들 대부분은 DNS 터널을 불안하게 느끼므로, 네트워크에서 이를 디택션하는 것이 유용합니다. 간단한 트래픽 분석만으로도 이러한 터널을 쉽게 찾을 수 있지만, 합법적인 터널도 있기 때문에 아래 예제는 임계값과 허용 목록 모두에 대해 로컬 환경에 맞는 조정이 필요합니다.

잠재적인 DNS 터널은 1시간 동안 몇 개의 UNIQUE 도메인에만 충분히 많은 데이터를 전송하는 DNS 서버(포트 53)로 정의합니다.

이 검색을 매시간 실행하고, 1시간을 되돌아보며 이러한 터널을 식별한다고 가정합니다:

{% tabs %}
{% tab title="SQL" %}

```sql
SELECT
  account_id,
  region,
  vpc_id,
  srcAddr, -- 외부
  srcIds:instance, -- 내부

  COUNT(1) as message_count,
  ARRAY_AGG(DISTINCT query_name) as query_names
FROM
  panther_logs.public.aws_vpcdns
WHERE
  p_occurs_since('1 hour')
  AND
  -- 단순 허용 목록 지정
  query_name NOT LIKE '%amazonaws.com'
GROUP BY
  1,2,3,4,5
HAVING
  message_count >= 1000   -- 1시간 내에 상당한 양의 활동
   AND
  ARRAY_SIZE(query_names) <= 2 -- 구별되는 도메인이 아주 적음(실제 DNS 서버일 가능성 낮음!)
```

{% endtab %}

{% tab title="PantherFlow" %}
{% hint style="warning" %}
현재는 예약 검색을 생성할 수 없습니다 [예약 검색](/ko/search/scheduled-searches.md) 내 [PantherFlow](/ko/pantherflow.md). 다음을 참조하세요: [PantherFlow의 제한 사항](/ko/pantherflow.md#limitations-of-pantherflow) 에서 자세히 알아보세요.
{% endhint %}

```kusto
panther_logs.public.aws_vpcdns
| where p_event_time > time.ago(1h) and not strings.like(query_name, '%amazonaws.com')
| summarize message_count=agg.count(), 
            query_names=agg.make_set(query_name) by account_id,
                                                    region,
                                                    vpc_id,
                                                    srcAddr,         // 외부
                                                    srcIds.instance, // 내부
| where message_count >= 1000        // 1시간 내에 상당한 양의 활동
    and arrays.len(query_names) <=2  // 구별되는 도메인이 아주 적음(실제 DNS 서버일 가능성 낮음!)
```

{% endtab %}
{% endtabs %}

### 클라우드 인프라의 월간 보고

Panther [클라우드 보안](/ko/cloud-scanning.md) 가 AWS 인프라를 보고할 수 있으므로, 다음을 사용할 수 있습니다: `resource_history` 테이블을 사용해 운영과 보안 모두에 유용할 수 있는 활동 통계를 계산할 수 있습니다.

간단한 예로, 모니터링된 계정의 활동을 보여주기 위해 이전 달에 대해 매월 1일에 실행되도록 예약할 수 있는 아래 보고서가 있습니다.

{% tabs %}
{% tab title="SQL" %}

```sql
WITH monthly_report AS (
  SELECT
    accountId,
    changeType,
    ARRAY_AGG(DISTINCT resourceType) as resourceTypes,
    count(1) as objects
  FROM
    panther_cloudsecurity.public.resource_history
  WHERE
      DATE_TRUNC('MONTH', p_event_time) = DATE_TRUNC('MONTH', current_date - 1)  -- 1일에 실행하므로 지난달 전체!
    AND
    changeType <> 'SYNC' -- 이것들은 변경이 아니라 전체 레코드입니다
  GROUP BY 1,2
)
-- Python 룰에서 전달할 수 있도록 전체 보고서를 JSON 객체의 단일 행으로 만들고 싶습니다
SELECT
  ARRAY_AGG(OBJECT_CONSTRUCT(*)) as monthly_report
FROM monthly_report
```

{% endtab %}

{% tab title="PantherFlow" %}
{% hint style="warning" %}
현재는 예약 검색을 생성할 수 없습니다 [예약 검색](/ko/search/scheduled-searches.md) 내 [PantherFlow](/ko/pantherflow.md). 다음을 참조하세요: [PantherFlow의 제한 사항](/ko/pantherflow.md#limitations-of-pantherflow) 에서 자세히 알아보세요.
{% endhint %}

```kusto
let monthly_report = panther_cloudsecurity.public.resource_history
// TODO: 날짜에서 빼기
| where time.trunc('month', p_event_time) == time.trunc('month', time.now()-1d)
    and changeType != 'SYNC' // 이것들은 변경이 아니라 전체 레코드입니다
| summarize resourceTypes=agg.make_set(resourceType),
            objects=agg.count() by accountId, changeType;
            
// Python 룰에서 전달할 수 있도록 전체 보고서를 JSON 객체의 단일 행으로 만들고 싶습니다
monthly_report
| project report=object('accountId', accountId, 
                        'changeType', changeType, 
                        'resourceTypes', resourceTypes,
                        'objects', objects)
| summarize monthly_report=agg.make_set(report)
```

{% endtab %}
{% endtabs %}

예시 출력:

```javascript
{
    "monthly_report": [
        {
            "ACCOUNTID": "34924069XXXX",
            "CHANGETYPE": "DELETED",
            "OBJECTS": 8,
            "RESOURCETYPES": [
                "AWS.IAM.Role"
            ]
        },
        {
            "ACCOUNTID": "34924069XXXX",
            "CHANGETYPE": "MODIFIED",
            "OBJECTS": 2388,
            "RESOURCETYPES": [
                "AWS.CloudTrail.Meta",
                "AWS.S3.Bucket",
                "AWS.CloudFormation.Stack",
                "AWS.CloudTrail",
                "AWS.IAM.Role"
            ]
        },
        {
            "ACCOUNTID": "34924069XXXX",
            "CHANGETYPE": "CREATED",
            "OBJECTS": 11,
            "RESOURCETYPES": [
                "AWS.IAM.Role",
                "AWS.CloudFormation.Stack",
                "AWS.KMS.Key"
            ]
        }
    ]
}
```

해당 `resource_history` 이 테이블은 특정 리소스 수준까지 세부 정보를 제공하므로, 원한다면 위 검색의 더 자세한 변형을 만들 수 있습니다.

### 데이터베이스(Snowflake) 모니터링

{% hint style="info" %}
다음을 사용해 Snowflake 활동을 모니터링하는 것도 가능합니다: [Snowflake 감사 로그](/ko/data-onboarding/supported-logs/snowflake.md) 통합.
{% endhint %}

민감한 데이터를 보유한 데이터베이스는 공격 대상이 되는 경우가 많으므로 광범위한 보안 모니터링이 필요합니다.

이 쿼리들은 Panther의 읽기 전용 역할이 다음에 접근할 수 있어야 합니다: `snowflake.account_usage` 감사 데이터베이스(이 작업은 Snowflake 관리자에 의해 수행되어야 할 수 있습니다).

```sql
 USE ROLE accountadmin;
 GRANT IMPORTED PRIVILEGES ON DATABASE snowflake TO ROLE panther_readonly_role;
```

이 쿼리는 사용자 이름별 로그인 실패 패턴을 찾으며 정기적으로 실행해야 합니다:

{% tabs %}
{% tab title="SQL" %}

```sql
 -- 이전 24시간 동안 로그인 실패가 2회 초과인 사용자를 반환
  --이것은 SnowAlert 쿼리에서 수정된 것입니다
  SELECT 
    user_name,
    reported_client_type,
    ARRAY_AGG(DISTINCT error_code),
    ARRAY_AGG(DISTINCT error_message),
    COUNT(event_id) AS counts
  FROM snowflake.account_usage.login_history
  WHERE 1=1
    AND DATEDIFF(HOUR, event_timestamp, CURRENT_TIMESTAMP) < 24
    AND error_code IS NOT NULL
  GROUP BY reported_client_type, user_name
  HAVING counts >=3;
```

{% endtab %}

{% tab title="PantherFlow" %}
{% hint style="warning" %}
현재는 예약 검색을 생성할 수 없습니다 [예약 검색](/ko/search/scheduled-searches.md) 내 [PantherFlow](/ko/pantherflow.md). 다음을 참조하세요: [PantherFlow의 제한 사항](/ko/pantherflow.md#limitations-of-pantherflow) 에서 자세히 알아보세요.
{% endhint %}

```kusto
snowflake.account_usage.login_history
| where time.diff('h', p_event_time, time.now()) < 24 and error_code != null
| summarize error_codes=agg.make_set(error_code),
            error_msgs=agg.make_set(error_message),
            counts=agg.count() by reported_client_type, user_name
| where counts >= 3
```

{% endtab %}
{% endtabs %}

단일 IP별 Snowflake 실패 로그인은 IP별 로그인 시도를 24시간 동안 살펴보고 실패한 로그인 횟수가 2회 초과인 IP를 반환합니다. 이는 잠재적으로 의심스러운 활동을 강조하기 위해 24시간마다 실행되도록 예약할 수 있습니다. 이 접근 방식의 효과는 회사 내부 IP 주소를 기업이 어떻게 처리하는지에 따라 달라질 수 있습니다.

{% tabs %}
{% tab title="SQL" %}

```sql
 --이전 24시간 동안 실패한 로그인이 2회 초과인 IP 반환
  --이것은 SnowAlert 쿼리에서 수정된 것입니다
  SELECT 
    client_ip,
    MIN(event_timestamp) event_start,
    MAX(event_timestamp) event_end,
    timediff(second, event_start, event_end) as event_duration,
    reported_client_type,
    ARRAY_AGG(DISTINCT error_code),
    ARRAY_AGG(DISTINCT error_message),
    COUNT(event_id) AS counts
  FROM snowflake.account_usage.login_history
  WHERE 1=1
    AND DATEDIFF(HOUR,  event_timestamp, CURRENT_TIMESTAMP) < 24
    AND error_code IS NOT NULL
  GROUP BY client_ip, reported_client_type
  HAVING counts >= 3;
```

{% endtab %}

{% tab title="PantherFlow" %}
{% hint style="warning" %}
현재는 예약 검색을 생성할 수 없습니다 [예약 검색](/ko/search/scheduled-searches.md) 내 [PantherFlow](/ko/pantherflow.md). 다음을 참조하세요: [PantherFlow의 제한 사항](/ko/pantherflow.md#limitations-of-pantherflow) 에서 자세히 알아보세요.
{% endhint %}

```kusto
snowflake.account_usage.login_history
| where time.diff('h', event_timestamp, time.now()) < 24 and error_code != null
| summarize error_codes=agg.make_set(error_code),
            error_msgs=agg.make_set(error_message),
            event_start=agg.min(p_event_time),
            event_end=agg.max(p_event_time),
            counts=agg.count() by client_ip, reported_client_type
| extend event_duration=time.trunc('s', event_end) - time.trunc('s', event_start)
| where counts >= 3
```

{% endtab %}
{% endtabs %}

Snowflake에서 관리자 권한 부여, 지난 7일을 되돌아봄. 이는 반드시 의심스러운 것은 아니지만, Snowflake 관리자들이 추적해 두면 좋을 수도 있습니다.

{% tabs %}
{% tab title="SQL" %}

```sql
SELECT
    current_account() AS environment
     , REGEXP_SUBSTR(query_text, '\\s([^\\s]+)\\s+to\\s',1,1,'ie') AS role_granted
     , start_time AS event_time
     , query_text AS event_data
     , user_name AS user_name
     , role_name
     , query_type
     , query_id AS query_id
FROM snowflake.account_usage.query_history
WHERE 1=1
  AND DATEDIFF(DAY,  start_time, CURRENT_TIMESTAMP) <= 7
  AND query_type='GRANT'
  AND execution_status='SUCCESS'
  AND (role_granted ILIKE '%securityadmin%'  OR role_granted ILIKE '%accountadmin%' OR role_granted ILIKE '%admin%')
```

{% endtab %}

{% tab title="PantherFlow" %}
{% hint style="warning" %}
현재는 예약 검색을 생성할 수 없습니다 [예약 검색](/ko/search/scheduled-searches.md) 내 [PantherFlow](/ko/pantherflow.md). 다음을 참조하세요: [PantherFlow의 제한 사항](/ko/pantherflow.md#limitations-of-pantherflow) 에서 자세히 알아보세요.
{% endhint %}

```kusto
snowflake.account_usage.query_history
| where time.diff('d', event_timestamp, time.now()) < 7
    and query_type == 'GRANT'
    and execution_status == 'SUCCESS'
    and (strings.ilike(role_granted, '%securityadmin%') OR 
         strings.ilike(role_granted, '%accountadmin%') OR 
         strings.ilike(role_granted, '%admin%'))
| project event_time=start_time,
          event_data=query_text,
          user_name,
          role_name,
          query_type,
          query_id,
          role_granted=re.substr(query_text, '\\s([^\\s]+)\\s+to\\s',1,1,'ie')
```

{% endtab %}
{% endtabs %}

#### 계정 사용 뷰 조회하기

추가적인 [계정 사용 쿼리 예시를 보려면 Snowflake의 문서를 참조하세요](https://docs.snowflake.com/en/sql-reference/account-usage#querying-the-account-usage-views).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.panther.com/ko/search/scheduled-searches/examples.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
