YARA-L 2.0 알려진 문제

다음에서 지원:

이 문서는 규칙 로직을 디버깅하고 YARA-L 2.0 실행을 최적화하려는 감지 엔지니어를 대상으로 합니다. 필드 unnesting, 집계의 카티전 프로덕트 확장, 보강 eventual consistency와 같은 비표준 엔진 동작을 처리하는 방법을 설명합니다. 이러한 방법을 따르면 결과 값이 부풀려지거나 감지가 누락되는 논리 오류를 방지할 수 있습니다.

YARA-L 2.0은 평가 중에 반복되는 입력란을 개별 이벤트 행으로 확장하는 실행 모델을 사용합니다. 이 변환은 엔진 수준에서 발생하므로 여러 반복 필드를 참조하거나 부호 없는 통합 데이터 모델 (UDM) 유형에 대해 산술 연산을 수행하려면 컴파일러 오류, 런타임 오류 또는 잘못된 결과 집합을 방지하기 위해 구문 해결 방법이 필요합니다. 이 문서에서는 이러한 기술적 제약 조건과 이를 해결하는 로직 패턴을 간략하게 설명합니다.

시작하기 전에

YARA-L 2.0 규칙을 테스트하거나 수정하기 전에 계정에 다음 기술 사용 권한이 있는지 확인하세요.

필요한 IAM 역할

  • roles/chronicle.viewer (보안 운영 뷰어): 기존 규칙 및 감지 메타데이터를 볼 수 있습니다.
  • roles/chronicle.editor (보안 운영 편집자): 규칙 로직을 수정하고 변경사항을 저장합니다.

필수 권한

  • chronicle.rules.runTest: 이전 데이터에서 테스트 실행 기능을 실행하는 데 필요합니다.

  • chronicle.detections.get: 감지 대시보드에서 중첩되지 않은 이벤트의 출력을 검사합니다.

주요 용어

  • UDM (통합 데이터 모델): 플랫폼 전반에서 수집된 모든 보안 원격 분석을 구조화하는 데 사용되는 정규화된 스키마입니다.
  • Unnesting: 반복되는 입력란 (배열)이 포함된 단일 UDM 이벤트를 여러 행으로 확장하는 엔진 수준의 작업입니다. 각 행은 배열의 고유한 요소를 나타내며, 이로 인해 규칙 평가 중에 행이 곱해질 수 있습니다.
  • T₀ (초기 실행): 수신 원격 분석에 대한 규칙의 첫 번째 실행입니다. 이는 '스트리밍' 단계에서 발생하며, GeoIP 또는 ASN 조정과 같은 백그라운드 보강 프로세스가 완료되기 전에 발생하는 경우가 많습니다.

반복 필드가 중첩 해제된 결과 집계

규칙이 여러 요소가 있는 이벤트 변수의 반복되는 입력란을 참조하는 경우 각 요소는 개별 이벤트 행으로 분할됩니다.

예를 들어 이벤트 $e의 반복 필드 target.ip에 있는 두 개의 IP 주소는 각각 다른 target.ip 값을 가진 두 개의 $e 인스턴스로 분할됩니다.

rule outbound_ip_per_app {
  meta:

  events:
    $e.principal.application = $app

  match:
    $app over 10m

  outcome:
    $outbound_ip_count = count($e.target.ip) // yields 2.

  condition:
    $e
}

이벤트 레코드: 중첩 해제 전후

이 섹션의 표는 IP 주소 배열이 포함된 단일 이벤트가 두 개의 개별 레코드로 변환되는 방식을 보여줍니다.

unnest 전

다음 테이블에서는 반복되는 입력란을 중첩 해제하기 전의 이벤트 레코드를 보여줍니다.

metadata.id principal.application target.ip
aaaaaaaaa Google SecOps [192.0.2.20, 192.0.2.28]

중첩 해제 후

다음 테이블에서는 반복되는 입력란을 중첩 해제한 후의 이벤트 레코드를 보여줍니다.

metadata.id principal.application target.ip
aaaaaaaaa Google SecOps 192.0.2.20
aaaaaaaaa Google SecOps 192.0.2.28

중첩된 반복되는 입력란 (카티전 프로덕트)

규칙이 security_results.action과 같이 다른 반복 필드 내에 중첩된 반복 필드를 참조하는 경우 중첩 해제는 상위 필드와 하위 필드 수준 모두에서 동시에 발생합니다. 이렇게 하면 모든 요소의 카티전 프로덕트가 생성됩니다.

다음 예시에서 security_results에 2개의 반복되는 값이 있고 security_results.actions에 2개의 반복되는 값이 있는 이벤트 $e는 4개의 인스턴스로 중첩 해제됩니다.

rule security_action_per_app {
  meta:

  events:
    $e.principal.application = $app

  match:
    $app over 10m

  outcome:
    $security_action_count = count($e.security_results.actions) // yields 4.

  condition:
    $e
}

중첩 해제 전의 이벤트 레코드

원래 레코드는 중첩된 배열 구조 내에 작업을 저장합니다.

metadata.id principal.application security_results
aaaaaaaaa Google SecOps [ { actions: [ ALLOW, FAIL ] }, { actions: [ CHALLENGE, BLOCK ] } ]

중첩 해제 후의 이벤트 레코드

확장 후에는 각 고유 작업이 자체 행이 되므로 고유하지 않은 집계에서 예상치 못한 개수가 발생할 수 있습니다.

metadata.id principal.application security_results.actions
aaaaaaaaa Google SecOps 허용
aaaaaaaaa Google SecOps 실패
aaaaaaaaa Google SecOps CHALLENGE
aaaaaaaaa Google SecOps 차단

관련 없는 필드에 미치는 영향

규칙 평가 시 이러한 중첩 해제 동작은 규칙이 또한 반복 필드에 해당하는 상위 필드가 포함된 반복 필드를 하나 이상 참조할 때 예기치 않은 결과 집계를 생성할 수 있습니다. sum(), array(), count()와 같은 고유하지 않은 집계는 중첩 해제 동작으로 생성되는 동일 이벤트의 다른 필드에서 중복 값을 고려할 수 없습니다.

다음 예시에서 이벤트 $e에는 단일 호스트 이름 (google.com)이 있지만 결과 (hostnames)는 동일한 이벤트 $e의 중첩되지 않은 인스턴스 4개에 대해 집계되며 각 인스턴스는 중복 principal.hostname 값을 가집니다. 이 결과는 security_results.actions에서 반복되는 값의 중첩 해제로 인해 1개가 아닌 4개의 호스트 이름을 생성합니다.

rule security_action_per_app {
  meta:

  events:
    $e.principal.application = $app

  match:
    $app over 10m

  outcome:
    $hostnames = array($e.principal.hostname) // yields 4.
    $security_action_count = count($e.security_results.action) // yields 4.

  condition:
    $e
}

관련 없는 필드가 있는 중첩 해제 전의 이벤트 레코드

호스트 이름은 단일 값이지만 반복되는 보안 결과와 함께 표시됩니다.

metadata.id principal.application principal.hostname security_results
aaaaaaaaa Google SecOps google.com [ { action: [ ALLOW, FAIL ] }, { action: [ CHALLENGE, BLOCK ] } ]

관련 없는 필드로 중첩 해제 후의 이벤트 레코드

이제 호스트 이름이 4개 행에 걸쳐 중복되어 array() 함수가 이를 4번 선택합니다.

metadata.id principal.application principal.hostname security_results.action
aaaaaaaaa Google SecOps google.com 허용
aaaaaaaaa Google SecOps google.com 실패
aaaaaaaaa Google SecOps google.com CHALLENGE
aaaaaaaaa Google SecOps google.com 차단

unnest 동작 해결 방법

중첩 해제가 발생할 때 결과 값이 정확한지 확인하려면 선택한 집계의 고유 버전을 사용하세요. 다음 함수는 unnesting으로 생성된 중복 행을 무시합니다.

  • max()
  • min()
  • array_distinct()
  • count_distinct()

여러 이벤트 변수가 포함된 결과 집계

규칙에 여러 이벤트 변수가 포함된 경우 감지에 포함된 각 이벤트 조합에 대한 집계에 개별 항목이 존재합니다. 예를 들어 나열된 이벤트에 대해 다음 예시 규칙이 실행되는 경우

events:
  $e1.field = $e2.field
  $e2.somefield = $ph

match:
  $ph over 1h

outcome:
   $some_outcome = sum(if($e1.otherfield = "value", 1, 0))

condition:
  $e1 and $e2
event1:
  // UDM event 1
  field="a"
  somefield="d"

event2:
  // UDM event 2
  field="b"
  somefield="d"

event3:
  // UDM event 3
  field="c"
  somefield="d"

합계는 모든 이벤트 조합에 대해 계산되어, 결과 값 계산에 두 이벤트 변수를 모두 사용할 수 있습니다. 계산에 다음 요소가 사용됩니다.

1: $e1 = event1, $e2 = event2
2: $e1 = event1, $e2 = event3
3: $e1 = event2, $e2 = event1
4: $e1 = event2, $e2 = event3
5: $e1 = event3, $e2 = event1
5: $e1 = event3, $e2 = event2

이 경우 $e2가 3개의 고유 이벤트에만 해당하더라도 잠재적 최대 합계가 6이 됩니다.

이는 합계, 개수, 배열에 영향을 줍니다. 개수 및 배열의 경우에는 count_distinct 또는 array_distinct를 사용하여 문제를 해결할 수 있지만 합계에 대해서는 해결 방법이 없습니다.

표현식 시작 부분의 괄호

괄호로 표현식을 시작하는 것은 지원되지 않으며 규칙 편집기에서 파싱 오류가 발생합니다.

잘못된 구문

parsing: error with token: ")"
invalid operator in events predicate

다음 예시에서는 이러한 유형의 오류가 발생합니다.

($event.metadata.ingested_timestamp.seconds -
$event.metadata.event_timestamp.seconds) / 3600 > 1

유효한 문법 변형

다음 구문 변형은 동일한 결과를 반환하지만 구문이 유효합니다.

$event.metadata.ingested_timestamp.seconds / 3600 -
$event.metadata.event_timestamp.seconds / 3600 > 1
    1 / 3600 * ($event.metadata.ingested_timestamp.seconds -
$event.metadata.event_timestamp.seconds) > 1
    1 < ($event.metadata.ingested_timestamp.seconds -
$event.metadata.event_timestamp.seconds) / 3600

결과의 색인 배열에는 집계 필요

반복되는 입력란의 outcome 섹션 내에서 배열의 색인을 직접 생성하는 것은 허용되지 않습니다. 임시 자리표시자 변수가 필요합니다.

outcome:
  $principal_user_dept = $suspicious.principal.user.department[0]

해결 방법

events 섹션 내의 자리표시자 변수에 특정 배열 색인을 캡처한 다음 결과에서 해당 자리표시자를 참조합니다.

events:
  $principal_user_dept = $suspicious.principal.user.department[0]

outcome:
  $principal_user_department = $principal_user_dept

존재하지 않는 OR 조건

두 개의 개별 이벤트 변수 사이에 OR 조건을 적용하고, 규칙이 존재하지 않음에 일치하는 경우 규칙은 성공적으로 컴파일되지만 거짓양성 감지를 생성할 수 있습니다.

예를 들어 다음 규칙 구문은 $event_a.field = "something"이 있는 이벤트와 일치하지 않아야 하지만 일치할 수 있습니다.

events:
     not ($event_a.field = "something" **or** $event_b.field = "something")
condition:
     $event_a and #event_b >= 0

해결 방법

논리 무결성을 유지하기 위해 각 변수에 대한 비존재 검사를 개별 블록으로 분리합니다.

events:
  not ($event_a.field = "something")
  not ($event_b.field = "something")

condition:
  $event_a and #event_b >= 0

부호 없는 이벤트 필드가 포함된 산술

부호 없는 정수 UDM 필드를 사용하여 산술 (+, -, *) 또는 모듈로 (%) 연산에 정수 리터럴을 사용하면 오류가 발생합니다. 표준 정수 리터럴은 기본적으로 부호 있는 64비트 정수(INT64)로 설정되며, 이는 부호 없는 64비트 정수(UINT64)로 정의된 UDM 필드(예: network.received_bytes, network.sent_bytes, principal.file.size)와 호환되지 않습니다.

이 엄격한 유형 제약조건은 sum() 또는 max() 집계 및 제수 표현식을 포함하여 events 및 outcome 섹션 모두에 적용됩니다. YARA-L 문법 검사기는 부호 있는 정수와 부호 없는 정수를 모두 일반 int 유형으로 처리합니다. 따라서 UINT64 UDM 필드와 INT64 정수 리터럴을 혼합하는 규칙은 저장 시간 문법 검증을 통과할 수 있지만 규칙 실행 중 또는 테스트 실행을 클릭할 때 실패할 수 있습니다. 규칙이 실패하면 일반적인 '규칙 오류'(cannot complete [arithmetic/mod] operation between unsigned and signed integer)가 생성됩니다.

예를 들어 다음 events 비교는 저장 시 문법 검증을 통과하지만 실행 중에 실패합니다. $e.network.received_bytes (UINT64)에 부호 있는 정수 리터럴 2 (INT64)이 곱해지기 때문입니다.

events:
  $total_bytes = $e.network.total_bytes
  $total_bytes = $e.network.received_bytes * 2

해결 방법

다음 방법 중 하나를 사용하여 이 유형 불일치를 해결할 수 있습니다.

  • 명시적 정수 변환 (cast.as_int() 또는 cast.as_uint()): cast.as_int()를 사용하여 부호 없는 UDM 필드를 부호 있는 정수로 변환하거나 cast.as_uint()를 사용하여 정수 리터럴을 부호 없는 정수로 변환합니다. 정수 결과가 필요하거나 정수 피연산자만 허용하는 모듈로 (%) 연산자를 사용할 때 이 메서드를 사용합니다.
  • 부동 소수점 강제 변환: 2.0 또는 100.0와 같은 부동 소수점 리터럴을 사용하여 정수 상수가 float로 작동하도록 강제합니다. 동일한 줄에 다른 나누기 연산자가 없는 간단한 표현식에서는 (2/1)와 같은 나누기 연산을 사용할 수도 있습니다. 이미 나눗셈 연산자 (/)가 포함된 표현식에서는 (N/1) 나눗셈 강제 변환 대신 부동 소수점 리터럴 또는 cast.as_int()를 사용합니다. 같은 줄에 있는 여러 / 문자는 정규 표현식 리터럴로 해석되어 컴파일 중에 파싱 오류가 발생하거나 규칙 편집기에서 구문 강조 표시가 중단될 수 있습니다.

events 섹션의 해결 방법

cast.as_int(), 부동 소수점 리터럴 또는 나눗셈 강제 변환을 사용하여 events 섹션의 오류를 수정합니다.

  • cast.as_int() 사용:

    events:
      $total_bytes = $e.network.total_bytes
      $total_bytes = cast.as_int($e.network.received_bytes) * 2
    
  • 부동 소수점 리터럴을 사용합니다.

    events:
      $total_bytes = $e.network.total_bytes
      $total_bytes = $e.network.received_bytes * 2.0
    
  • 나눗셈 강제 변환을 사용합니다.

    events:
      $total_bytes = $e.network.total_bytes
      $total_bytes = $e.network.received_bytes * (2/1)
    

outcome 섹션의 해결 방법

다음 outcome 표현식은 문법 오류 없이 저장되지만 sum($e.network.sent_bytes) 및 sum($e.network.received_bytes)가 서명된 INT64 리터럴 100 및 1와 결합된 UINT64 값을 반환하므로 런타임에 실패합니다.

outcome:
  // Saves, but fails at runtime:
  $upload_ratio_pct = sum($e.network.sent_bytes) * 100 / (sum($e.network.received_bytes) + 1)

부동 소수점 리터럴 또는 cast.as_int()를 사용하여 표현식을 다시 작성하여 오류 없이 저장하고 실행합니다.

  • 부동 소수점 리터럴을 사용합니다.

    outcome:
      $upload_ratio_pct = sum($e.network.sent_bytes) * 100.0 / (sum($e.network.received_bytes) + 1.0)
    
  • cast.as_int() 사용:

    outcome:
      $upload_ratio_pct = sum(cast.as_int($e.network.sent_bytes)) * 100 / (sum(cast.as_int($e.network.received_bytes)) + 1)
    

GeoIP 보강 및 eventual consistency

시스템은 초기 보강 단계 (스트리밍 및 지연 시간에 민감한 단계)에서 즉각적인 정확성보다 속도를 우선시하므로 데이터가 누락되고 거짓양성이 발생할 수 있습니다. 시스템은 백그라운드에서 데이터를 계속 보강하지만 규칙이 실행될 때 데이터를 사용하지 못할 수 있습니다. 이는 정상적인 eventual consistency 프로세스의 일부입니다.

보강 지연으로 인한 거짓양성을 방지하려면 값을 평가하기 전에 필드가 비어 있지 않은지 명시적으로 확인하세요.

예를 들어 다음 규칙 이벤트를 고려해 보세요.

$e.principal.ip_geo_artifact.network.asn = "16509" AND
$e.principal.ip_geo_artifact.location.country_or_region = "United Kingdom"

이 규칙은 이벤트에 보강된 필드인 $e.principal.ip_geo_artifact.network.asn = "16509" 및 $e.principal.ip_geo_artifact.location.country_or_region = "United Kingdom"가 모두 있어야 한다는 사실에 의존합니다. 보강이 제때 완료되지 않으면 규칙에서 거짓양성이 생성됩니다.

이를 방지하려면 이 규칙을 다음과 같이 확인하는 것이 좋습니다.

$e.principal.ip_geo_artifact.network.asn != "" AND
$e.principal.ip_geo_artifact.network.asn = "16509" AND
$e.principal.ip_geo_artifact.location.country_or_region != "" AND
$e.principal.ip_geo_artifact.location.country_or_region = "United Kingdom"

이 규칙은 ASN 16509가 있지만 영국 외부에 있는 IP에 의해 이벤트가 트리거될 가능성을 제거합니다. 이렇게 하면 규칙의 전반적인 정밀도가 향상됩니다.

보강 지연 문제 해결 방법을 알아보세요.

문제 해결

이 섹션에서는 예상되는 성능을 설명하고 실시간 감지 동작이 테스트 결과와 다른 일반적인 문제에 대한 셀프 서비스 해결 방법을 제공합니다.

미래 날짜 이벤트

멀티 이벤트 규칙은 수집과 관련하여 시간순으로 이벤트를 처리하도록 설계되었습니다. 멀티 이벤트 규칙을 지정하고 활성화하면 미래 타임스탬프가 있는 이벤트에 대한 감지가 생성되지 않습니다. 예를 들어 event.timestamp에 ingest.timestamp 이후의 날짜와 시간이 설정된 경우입니다.

보강 지연

Google SecOps는 초기 알림을 최대한 빨리 노출하기 위해 수집 속도를 우선시합니다. 하지만 GeoIP, ASN 또는 UDM 메타데이터 확인과 같은 백그라운드 보강 프로세스는 eventual consistency 모델을 따릅니다.

초기 실행 (T₀)

라이브 엔진은 백그라운드 보강이 완료되기 전에 규칙을 평가할 수 있습니다. 로직이 감지 또는 제외를 위해 보강된 필드를 사용하는지 여부에 따라 다음과 같은 일시적인 불일치가 발생할 수 있습니다.

  • 거짓음성 (감지 지연): 이는 일반적인 결과입니다. 규칙이 트리거할 보강된 필드 (예: target.user.department == "Finance")에 따라 달라지고 해당 필드가 null인 경우 초기 실행 중에 규칙이 일치하지 않습니다.

  • 거짓양성 (제외 누락): 규칙이 보강된 필드를 사용하여 알려진 양호한 활동 (예: NOT target.ip_geo_country == "US")을 필터링하는 경우 '제외' 데이터가 아직 적용되지 않았기 때문에 규칙이 거짓양성을 트리거할 수 있습니다.

트루업 실행

이러한 백그라운드 실행은 지연 후 (예: 4시간 또는 30시간) 데이터를 다시 평가합니다. 이렇게 하면 다음과 같이 감지 상태가 '조정'됩니다.

  • 지연 감지: T₀에서 '거짓음성'이었던 이벤트가 보강이 완료되면 감지를 생성합니다.

  • 수정: T₀ 거짓양성은 시스템에 남아 있지만 완전히 보강된 데이터는 수동 분류를 위해 UDM 뷰어에 표시됩니다.

테스트 불일치 실행

테스트 실행 도구는 이미 조정된 이전 데이터에서 작동합니다. 수동 테스트를 실행할 때까지 데이터가 완전히 보강되므로 '정산' 결과를 즉시 확인할 수 있습니다. 즉, 실시간 초기 실행 중에 발생한 T₀ 거짓음성 또는 제외 기반 거짓양성이 표시되지 않습니다.

오류 해결

다음 표를 사용하여 실시간 알림과 테스트 결과 간의 불일치를 해결하세요.

문제 설명 실행 가능한 수정사항
제외 실패 초기 실행 중에 필드가 null이었기 때문에 제외 (예: != "ASN_123")에도 불구하고 규칙이 실행됩니다. 평가 전에 데이터가 보강되도록 이벤트 섹션에 null이 아닌 검사를 추가합니다. 예를 들면 다음과 같습니다.

$e.principal.ip_geo_artifact.network.asn != ""
실시간 일치와 테스트 일치 비교 실시간 규칙은 알림을 트리거하지만 동일한 데이터에 대한 테스트 실행은 "No Results를 표시합니다. 모든 보강된 필드 (GeoIP, ASN, File Path)를 확인하는 $e.field != ""를 추가하여 실시간 동작과 이전 동작을 동기화합니다.
메타데이터 누락 감지가 대시보드에 빈 GeoIP 또는 File Path 필드로 표시됩니다. 이는 T0 실행에서 예상되는 결과입니다. 이 문제를 해결하려면 규칙 일정에 field != "" 확인을 포함하거나 정산 지연을 구성하여 처리 시간을 늘리세요.

유효성 검사 및 테스트

규칙이 지연된 보강을 올바르게 처리하는지 확인하려면 다음을 수행합니다.

  1. 지연 식별: 거짓양성이라고 생각되는 감지를 찾습니다. 감지 유형 열에서 <span class="material-icons">lightbulb</span> 아이콘을 확인합니다. 이 아이콘이 없는 알림은 보강 지연이 가장 흔한 기본 실행에서 발생한 것입니다.

  2. 규칙 로직 업데이트: 로직에 사용되는 모든 보강된 데이터 포인트에 field != "" 검사를 추가합니다.
    예 (파일 경로):
    $e.target.process.parent_process.file.full_path != ""

  3. 테스트 및 확인:

    • 테스트 실행 기능을 사용하여 로직이 의도한 이전 데이터와 여전히 일치하는지 확인합니다.
    • 보강 필드가 채워진 후 보정 실행 중에만 규칙이 트리거되거나 올바르게 제외되는지 확인합니다.

자세한 내용은 규칙 실행 예약 이해 및 규칙의 맞춤 일정 구성을 참조하세요.

도움이 더 필요하신가요? 커뮤니티 회원 및 Google SecOps 전문가에게 문의하여 답변을 받으세요.