LabHub
배우기 러닝패스 코스

cron 이 새벽 세 시에 curl 을 했다 · 시스콜에서 잡으면 거짓말을 못 한다 · 이론

시스콜에서 잡으면 거짓말을 못 한다

LabHub 에서 이어서 보기

한 줄 요약

Falco 는 커널에서 시스콜을 받아 규칙과 맞춰 보는 런타임 탐지 엔진이다. 규칙 한 편은 rule·desc·condition·output·priority 다섯 항목으로 이루어지고, 반복되는 조건은 macro 로, 값 목록은 list 로 빼서 재사용한다.

왜 이게 필요했나

로그 기반 탐지에는 구조적인 한계가 하나 있다. 로그는 프로그램이 남기기로 한 것만 남는다. 침입자가 로그를 남기지 않는 방법으로 움직이면 — 예를 들어 /tmp 에 바이너리를 떨어뜨리고 바로 실행하면 — 어떤 애플리케이션도 그것을 기록하지 않는다.

시스콜은 다르다. 프로세스를 만들든 파일을 열든 소켓을 연결하든 커널을 거쳐야 하고, 커널을 속일 수는 없다. 그래서 "무엇이 실제로 일어났는가" 를 묻는 자리로는 시스콜이 가장 아래층이다. 커널 감사(auditd)도 같은 자리를 보지만, auditd 가 규칙마다 남길지 말지를 정하는 기록 장치라면 Falco 는 조건을 평가해 지금 알림을 낼지 를 정하는 판정 장치다. 둘은 대체재가 아니라 짝이다.

어떻게 동작하나

[규칙의 기본 요소](https://falco.org/docs/concepts/rules/basic-elements/)는 이렇게 정리된다. 필수는 rule(짧고 유일한 이름)·desc(무엇을 잡는지)·condition(이벤트에 적용할 필터 식)·output(맞았을 때 낼 문장)·priority(심각도) 다섯이고, 선택으로 enabled·tags·source·exceptions 가 있다. priority 로 쓸 수 있는 값은 여덟이다 — EMERGENCY·ALERT·CRITICAL·ERROR·WARNING·NOTICE·INFORMATIONAL·DEBUG.

- list: outbound_clients  items: [curl, wget, nc]- macro: from_scheduler  condition: (proc.aname[2]=cron or proc.aname[3]=cron)- rule: Scheduled job made an outbound request  desc: 예약 작업 밑에서 외부 요청 도구가 실행됐다  condition: spawned_process and proc.name in (outbound_clients) and from_scheduler  output: "예약 작업이 바깥으로 (user=%user.name proc=%proc.name cmd=%proc.cmdline)"  priority: WARNING  tags: [labhub, cron]

listmacro 는 순서에 제약이 있다. 둘 다 자기보다 앞에 정의된 것만 참조할 수 있다. 그래서 파일 안에서 목록 → 매크로 → 규칙 순으로 적는 것이 습관이 된다. spawned_process 처럼 이름만 나오는 조건은 Falco 가 함께 배포하는 [기본 매크로](https://falco.org/docs/reference/rules/default-macros/)다. 이 실습 VM 에서 재 보니 falco --validate 에 내 규칙 파일만 주면 Undefined macro 'spawned_process' 로 떨어지고, 기본 규칙 파일을 함께 줘야 통과한다 — 검증도 로딩과 같은 조건에서 해야 한다는 뜻이다.

조건에 쓸 수 있는 필드 이름은 [지원 필드 목록](https://falco.org/docs/reference/rules/supported-fields/)에 있다. 프로세스 계열에서 특히 쓸모 있는 것이 조상 이름이다. proc.pname 은 부모 하나뿐이지만 proc.aname[2] 는 할아버지를 가리킨다. cron 이 셸을 띄우고 그 셸이 curl 을 부르는 구조에서는 부모가 sh 라서, 조상까지 봐야 "예약 작업에서 나왔다" 를 말할 수 있다.

알림을 어디로 보낼지는 [출력 설정](https://falco.org/docs/concepts/outputs/)이 정한다. 기본은 표준 출력이라 systemd 아래에서는 저널로 들어가는데, 아침에 기계로 세려면 JSON 으로 바꾸고 파일로도 떨어뜨리는 편이 낫다. 출력 문장 안의 %proc.cmdline 같은 자리 표시자는 [출력 형식](https://falco.org/docs/concepts/outputs/formatting/) 문서에 정리돼 있다.

오탐을 줄이는 자리는 exceptions 다([예외 문서](https://falco.org/docs/concepts/rules/exceptions/)). fields 에 볼 필드를, comps 에 비교 방법을, values 에 값 묶음을 적으면 Falco 가 그것을 and not (…) 으로 조건 뒤에 붙인다. 조건 문자열을 직접 고치는 것과 결과는 비슷하지만 두 가지가 다르다 — 예외가 규칙과 분리돼 있어 나중에 덮어쓰기로 값만 바꿀 수 있고, 무엇을 왜 뺐는지가 눈에 보인다.

현장에서 만나는 모습

가장 흔한 실패는 예외를 조건에 녹여 버리는 것이다. and not proc.cmdline contains backup 을 조건 끝에 붙이면 6개월 뒤에 그 조각이 무엇을 위한 것이었는지 아무도 모른다. 그리고 그렇게 붙인 예외는 대개 너무 넓다 — backup 이라는 낱말이 들어간 모든 명령이 조용해진다.

두 번째는 규칙을 켜 놓고 출력을 안 보는 것이다. 이 실습 VM 에서 기본 설정 그대로 두면 알림이 저널로만 흐르고, 그 VM 이 사라지면 함께 사라진다. 알림이 파일이나 수집기로 가지 않는 탐지는 "돌고 있다" 는 안심만 준다.

세 번째는 우선순위 인플레이션이다. 모든 규칙을 CRITICAL 로 내면 등급은 정보가 되기를 그만둔다. 사람을 새벽에 깨울 것과 아침에 모아 보면 될 것을 나누는 일이 규칙을 쓰는 일의 절반이다.

다음 실습에서 할 것

실습에서는 Falco 가 깔린 VM 에서 규칙을 직접 쓴다. 목록과 매크로를 나누고, 출력을 JSON 파일로 돌리고, 제목의 사건(예약 작업이 매분 바깥으로 요청한다)을 실제로 심어 규칙이 잡는 것을 파일에서 확인한다. 그다음 같은 규칙에 걸리는 정상 작업을 하나 더 만들어, 그것만 exceptions 로 빼고 나쁜 것은 계속 잡히는지 다시 확인한다. 시스콜 계측은 커널 기능이라 이 실습도 VM 에서 돈다.