[CS300 #259] 보안 로그와 SIEM — 기록하지 않은 것은 일어나지 않은 것이 된다
컴퓨터공학 300 주제 시리즈의 259번째 글이다. 전체 지도는 여기.
한 줄 요약
보안 로그는 “누가, 언제, 어디서, 무엇을 했고, 결과가 어땠나” 를 남기는 기록이고, SIEM 은 흩어진 로그를 모아 정규화·상관 분석·경보·보존을 하는 체계다. 좋은 탐지는 좋은 로그에서 시작하며, 로그 자체가 새로운 유출 경로가 되지 않게 하는 것도 같은 무게의 일이다.
왜 필요한가
OWASP Top 10 은 2021년판부터 로깅 실패를 독립 범주로 다루고, 2025년판은 이름을 “Security Logging and Alerting Failures” 로 바꿨다. 기록만으로는 부족하고 누군가 알아채야 한다는 뜻이다.
로그가 없거나 부실하면 세 가지가 무너진다.
- 탐지: 무차별 대입, 권한 상승, 대량 다운로드가 일어나도 아무도 모른다.
- 대응: 앞 글에서 본 것처럼 사고 타임라인을 만들 재료가 없다. 최초 침입 시점을 모르면 어느 백업이 깨끗한지도 모른다.
- 책임 추적: 누가 무엇을 바꿨는지 증명하지 못한다. CIA 에 더해 말하던 책임 추적성(accountability)이 로그로 구현된다.
핵심 개념
무엇을 기록하나
OWASP Logging 치트시트와 NIST SP 800-92 를 바탕으로 정리하면 다음과 같다.
| 꼭 남길 것 | 남기면 안 되는 것 |
|---|---|
| 인증 성공·실패, 로그아웃, MFA 변경 | 비밀번호(틀린 것 포함) |
| 인가 실패(403) | 세션 ID, 액세스 토큰, API 키 |
| 입력 검증 실패, 비정상 요청 | 암호화 키, 연결 문자열 |
| 권한·역할 변경, 계정 생성·삭제 | 카드 번호, 주민번호 등 민감 개인정보 원문 |
| 관리자 행위, 설정 변경, 배포 | 법적으로 수집 근거가 없는 정보 |
| 민감 데이터 대량 조회·내보내기 | |
| 애플리케이션 오류·예외, 보안 기능 중지 |
각 이벤트에는 언제(UTC 또는 시간대 포함), 어디서(서비스, 호스트, 출처 주소), 누가(사용자·서비스 어카운트), 무엇을(행동, 대상), 결과(성공·실패)가 있어야 한다.
로그 자체의 보안
- 로그 주입(CWE-117): 사용자 입력에 개행을 넣어 가짜 로그 줄을 만들 수 있다. 구조화 로그(JSON)를 쓰면 직렬화 과정에서 개행이 이스케이프된다.
- 민감정보 누출: 디버그 로그에 요청 헤더 전체를 찍으면
Authorization토큰이 그대로 남는다. 로그 저장소는 보통 운영 DB 보다 접근 통제가 느슨하다. - 변조 방지: 공격자는 흔적을 지운다. 로그는 생성 즉시 별도 시스템으로 전송하고, 원본 서버에서 지울 수 없게 한다(추가 전용 저장, 쓰기 권한 분리).
- 시간 동기화: 모든 소스의 시계가 맞아야 상관 분석이 된다.
SIEM 의 파이프라인
수집 ──> 파싱·정규화 ──> 보강 ──> 상관 분석·탐지 ──> 경보·대응 ──> 보존·검색
(에이전트, (공통 필드: (자산 정보, (규칙, 통계, (티켓, 온콜, (사고 분석,
syslog, 시각, 주체, 위협 정보, 이상 탐지) 자동 차단) 감사 대응)
API) 행동, 결과) 지리 정보)
- 정규화가 핵심이다. 웹 서버는
remote_addr, 방화벽은src, 클라우드 로그는sourceIPAddress라고 부르는 값을 같은 필드로 맞춰야 규칙 하나로 모두를 볼 수 있다. - 상관 분석은 단일 이벤트로는 무해하지만 조합하면 의미가 생기는 패턴을 잡는다. 실패 여러 번 뒤의 성공, 새 국가에서의 로그인 직후 권한 변경 같은 것들이다.
- 탐지 규칙의 공용어: Sigma 는 SIEM 제품에 독립적인 탐지 규칙 형식이고, MITRE ATT&CK 은 공격 기법 분류 체계다. 규칙에 ATT&CK 기법 ID 를 붙이면 “우리는 어떤 기법을 탐지할 수 있고 어디가 빈칸인가” 를 지도처럼 볼 수 있다.
경보 피로
탐지 규칙을 많이 켤수록 좋은 게 아니다. 오탐이 많으면 담당자가 경보를 무시하게 되고, 진짜 경보도 묻힌다. 규칙마다 누가, 어떤 절차로 대응하는지가 정해져 있어야 하고, 대응하지 않는 경보는 끄거나 고친다.
직접 해 보기
두 가지를 해 본다. (1) 평문 로그와 구조화 로그의 로그 주입 차이, 민감 필드 가리기. (2) “5분 안에 5회 이상 실패 후 같은 출처에서 성공” 상관 규칙. 주소는 문서용 대역이다.
import json
from collections import defaultdict, deque
from datetime import datetime, timedelta
# 1) 수집 단계: 평문 로그 vs 구조화(JSON) 로그
user = "admin\n2026-10-10T15:00:01Z login_ok user=admin" # 공격자가 넣은 사용자명
print("평문 :", f"2026-10-10T15:00:00Z login_fail user={user}") # 가짜 줄이 하나 더 생긴다
SENSITIVE = {"password", "token", "authorization", "cookie"}
def log_event(**fields):
clean = {k: ("[REDACTED]" if k.lower() in SENSITIVE else v) for k, v in fields.items()}
return json.dumps(clean, ensure_ascii=False) # 개행은 \n 으로 이스케이프된다
print("JSON :", log_event(ts="2026-10-10T15:00:00Z", event="login_fail", user=user,
src="203.0.113.50", password="hunter2"))
# 2) 상관 분석 규칙: 5분 안에 실패 5회 이상 후 같은 출처에서 성공 -> 경보
raw = [("15:00:0%d" % i, "login_fail", "admin", "203.0.113.50") for i in range(6)] + \
[("15:01:30", "login_ok", "admin", "203.0.113.50"),
("15:02:00", "login_fail", "kim", "198.51.100.7"),
("15:02:05", "login_ok", "kim", "198.51.100.7")]
WINDOW, THRESHOLD = timedelta(minutes=5), 5
fails = defaultdict(deque)
for t, ev, user, src in raw:
ts = datetime.strptime("2026-10-10 " + t, "%Y-%m-%d %H:%M:%S")
q = fails[(user, src)]
while q and ts - q[0] > WINDOW:
q.popleft()
if ev == "login_fail":
q.append(ts)
elif ev == "login_ok" and len(q) >= THRESHOLD:
print(f"ALERT [T1110 Brute Force] {user}@{src}: 실패 {len(q)}회 뒤 성공 ({t})")
q.clear()
실행 결과(Python 3.12):
평문 : 2026-10-10T15:00:00Z login_fail user=admin
2026-10-10T15:00:01Z login_ok user=admin
JSON : {"ts": "2026-10-10T15:00:00Z", "event": "login_fail", "user": "admin\n2026-10-10T15:00:01Z login_ok user=admin", "src": "203.0.113.50", "password": "[REDACTED]"}
ALERT [T1110 Brute Force] admin@203.0.113.50: 실패 6회 뒤 성공 (15:01:30)
평문 로그에서는 공격자가 넣은 사용자명이 로그인 성공 기록처럼 보이는 줄을 하나 더 만들었다. 줄 단위로 파싱하는 수집기는 이걸 진짜 이벤트로 받아들인다. JSON 로그에서는 같은 값이 한 필드 안의 문자열로 남는다. 상관 규칙은 kim 의 실패 한 번 후 성공은 무시하고, admin 계정의 반복 실패 후 성공만 잡았다. 실제로는 이 규칙에 “성공한 출처가 처음 보는 주소인가”, “그 계정이 관리자인가” 같은 맥락을 더해 오탐을 줄인다.
현업에서는
- 쿠버네티스 감사 로그: API 서버의 감사 정책으로 “누가 어떤 리소스에 어떤 동사를 썼나” 를 남긴다. API 서버에 감사 정책 파일과 백엔드를 지정해야 기록이 시작되므로, 쓰는 배포판에서 켜져 있는지 먼저 확인한다. 시크릿 읽기, RBAC 변경,
exec·attach, 특권 파드 생성은 반드시 기록한다. 시크릿 객체의 내용이 감사 로그에 들어가지 않도록 시크릿은 Metadata 수준으로 남기는 것이 공식 문서 예시의 방식이다. - 홈랩 규모의 SIEM: 거창한 제품 없이도 로그 수집기(Fluent Bit 등) → 검색 저장소(Loki, OpenSearch 등) → 경보 규칙(Grafana 경보 등)으로 핵심 기능을 갖출 수 있다. 먼저 SSH 로그인, sudo, 쿠버네티스 감사 로그 세 가지부터 모으면 효과가 크다.
- 보존 기간: 침입은 발견까지 오래 걸리는 경우가 많다. 보존 기간이 짧으면 최초 침입을 추적할 수 없다. 법·규제 요구와 비용을 함께 보고 정한다.
- 경보는 런북과 함께: 각 경보에 “확인할 것 → 판단 기준 → 조치” 를 적은 짧은 문서를 연결한다. 새벽에 경보를 받은 사람이 바로 움직일 수 있어야 한다.
확인 문제
- 보안 이벤트 로그에 들어가야 할 다섯 가지 요소는?
- 로그에 남기면 안 되는 정보 세 가지를 들라.
- 로그 주입 공격이란 무엇이며, 구조화 로그가 어떻게 완화하는가?
- SIEM 에서 정규화가 필요한 이유는?
- 경보 규칙을 무조건 많이 켜는 것이 좋지 않은 이유는?
풀이
- 언제, 어디서(출처·서비스), 누가, 무엇을(행동·대상), 결과.
- 비밀번호, 세션 ID·액세스 토큰, 암호화 키, 카드 번호 등 민감 개인정보 원문 중 셋.
- 입력에 개행 등을 넣어 가짜 로그 줄을 만들거나 로그 형식을 깨뜨리는 공격. JSON 등 구조화 로그는 값을 이스케이프된 문자열 필드로 저장하므로 새 줄이 생기지 않는다.
- 소스마다 같은 의미의 값을 다른 이름·형식으로 기록하므로, 공통 필드로 맞춰야 하나의 규칙으로 여러 소스를 상관 분석할 수 있다.
- 오탐이 늘어 경보 피로가 생기고 진짜 경보가 무시된다. 대응 절차가 없는 경보는 가치가 없다.
더 읽을거리 (References)
- NIST, SP 800-92: Guide to Computer Security Log Management
- OWASP, Logging Cheat Sheet
- MITRE, ATT&CK T1110: Brute Force
- Kubernetes, Auditing