cron 이 새벽 세 시에 curl 을 했다 · 규칙은 문장이 아니라 자료 구조다 · 이론
규칙은 문장이 아니라 자료 구조다
한 줄 요약
Sigma 는 "이 로그에서 이런 것을 찾아라" 를 도구와 무관한 YAML 로 적는 형식이다. 필수 항목은 title·logsource·detection 셋뿐이고, 탐지의 본체는 detection 안의 이름 붙은 선택(selection)들과 그것을 조립하는 condition 한 줄이다.
왜 이게 필요했나
탐지 규칙은 도구마다 문법이 다르다. 같은 "예약 작업이 바깥으로 요청했다" 를 검색 엔진에서는 질의문으로, 로그 파이프라인에서는 필터로, EDR 에서는 자기 화면의 폼으로 적는다. 회사를 옮기면 규칙을 처음부터 다시 쓴다. 더 나쁜 것은 규칙을 공유할 수 없다는 점이다. 어떤 팀이 새 수법을 잡는 규칙을 만들어도 다른 팀은 자기 도구 말로 옮겨 적어야 하고, 옮기면서 조건 하나가 조용히 빠진다.
Sigma 는 그 중간에 형식을 하나 둔다. 규칙은 YAML 로 쓰고, 백엔드가 그것을 각 도구의 질의문으로 번역한다. 그래서 규칙은 사람이 읽고 리뷰하고 git 에 올리는 코드가 되고, 도구는 갈아 끼울 수 있는 뒷단이 된다.
어떻게 동작하나
규칙 한 편의 뼈대는 이렇다.
title: 예약 작업이 사외로 요청했다id: 8b2f5f4a-1d3c-4c22-9f0e-6a7b1c2d3e4fstatus: testdescription: cron 밑에서 curl·wget 이 실행되고 목적지가 사내가 아닌 경우logsource: product: linux category: process_creationdetection: selection: ParentImage|endswith: '/cron' CommandLine|contains: - 'curl' - 'wget' filter_internal: CommandLine|contains: 'repo.corp.internal' condition: selection and not filter_internallevel: high[규칙 문서](https://sigmahq.io/docs/basics/rules.html)가 못 박는 규칙은 세 가지다. title·logsource·detection 은 필수이고, status 는 stable·test·experimental·deprecated·unsupported 중 하나이며, level 은 critical·high·medium·low·informational 중 하나다. logsource 는 category(웹서버·방화벽 같은 갈래)·product(windows·linux 같은 제품)·service(그 제품 안의 서비스)로 어떤 로그를 대상으로 하는지 좁힌다([로그 원천 문서](https://sigmahq.io/docs/basics/log-sources.html)).
가장 헷갈리는 자리는 매핑과 리스트가 뜻이 다르다는 점이다. 선택 안에서 필드 여러 개를 나열하면 AND 이고, 한 필드에 값을 여러 개 주면 OR 이다. 위 예에서 ParentImage 와 CommandLine 은 둘 다 맞아야 하지만 curl 과 wget 은 둘 중 하나만 맞으면 된다.
condition 은 그 선택들을 조립하는 작은 언어다([조건 문서](https://sigmahq.io/docs/basics/conditions.html)). and·or·not 과 괄호를 쓰고, 이름이 여러 개일 때는 1 of selection_*(하나라도)과 all of selection_*(전부)로 묶는다. 1 of them·all of them 도 있지만 문서는 공유용 규칙에서 이 둘을 권하지 않는다 — 나중에 선택 하나를 더했을 때 규칙의 뜻이 소리 없이 바뀌기 때문이다.
값을 비교하는 방법은 필드 이름 뒤의 수식어가 정한다([수식어 문서](https://sigmahq.io/docs/basics/modifiers.html)). |contains·|startswith·|endswith·|re 가 자주 쓰이고, |contains|all 처럼 이어 붙이면 "나열한 값이 전부 들어 있어야 한다" 가 된다. 여기서 초보자가 반드시 한 번은 데는 곳이 |contains 의 낱말 경계다. nc 를 찾으려고 CommandLine|contains: 'nc' 라고 쓰면 rsync 가 걸린다. 문자열 안에 n 다음에 c 가 있기 때문이다.
현장에서 만나는 모습
현장에서 규칙이 죽는 이유는 대개 둘 중 하나다. 하나는 오탐이 많아서 아무도 안 본다. 처음에는 알림이 울리면 사람들이 확인하지만, 열 번 중 아홉 번이 정상 백업 작업이면 열한 번째부터는 아무도 열어 보지 않는다. 그래서 규칙을 넓게 써 두고 "일단 다 잡고 나중에 줄이자" 는 계획은 거의 언제나 실패한다.
다른 하나는 주소를 규칙에 박아 두는 것이다. 이번 사건의 목적지가 cdn.updates-cache.net 이었다고 그 문자열을 규칙에 넣으면, 다음 주에 도메인이 바뀌는 순간 규칙은 아무것도 못 잡는다. 오래 사는 규칙은 주소가 아니라 행위를 적는다 — 받아 온 것을 곧바로 셸에 파이프로 넘긴다든가, 셸을 붙여서 바깥으로 연결한다든가.
그리고 메타데이터를 장식으로 여기지 않는 것이 중요하다. falsepositives 에 "정상 백업 작업" 이라고 한 줄 적혀 있으면 새벽 세 시에 호출받은 사람이 3분 만에 판단한다. 그 줄이 없으면 30분을 쓴다. references 와 tags 도 같은 값을 한다.
다음 실습에서 할 것
실습에서는 합성한 프로세스 실행 기록 24건을 받아 규칙을 여섯 편 쓴다. 가장 작은 규칙에서 시작해 점점 좁히고, 정상 백업 작업을 필터로 덜어 내고, 주소 대신 행위로 잡는 규칙을 쓰고, 두 갈래를 1 of 로 묶는다. 규칙은 그때마다 실제로 사건 기록에 돌려 보고, 몇 건을 맞히고 몇 건을 놓쳤는지 숫자로 확인한다. 이 실습은 파일만 다루므로 실습 파드에서 돈다.