보안 감사 로깅: 신뢰할 수 있는 기록 남기기

감사 로그가 일반 로그와 어떻게 다른지, 무엇을 기록하고 무엇을 절대 남기지 말아야 하는지, 그리고 해시 체인 같은 변조 방지 기법으로 '신뢰할 수 있는 기록'을 만드는 방법을 정리합니다.

· 14 min read · PALDYN Team

지난 글에서 컴플라이언스를 다루며 “침해를 탐지하려면, 사고를 조사하려면, 컴플라이언스를 증빙하려면 결국 안전하고 변조 불가능한 감사 로그가 필요하다”는 말로 끝을 맺었다. 지금까지 다뤄온 탐지(SIEM), 대응(IR), 컴플라이언스는 모두 한 가지 전제 위에 서 있다. “무슨 일이 있었는지에 대한 믿을 수 있는 기록이 존재한다”는 전제다. 그 기록이 없거나, 있어도 누군가 마음대로 고칠 수 있다면 그 위에 쌓은 모든 것이 무너진다. 이번 글에서는 그 토대인 보안 감사 로깅을 다룬다.

감사 로그는 일반 로그와 다르다

개발자에게 “로그”라고 하면 보통 디버깅 로그를 떠올린다. console.log, 스택 트레이스, 요청 처리 시간 같은 것들이다. 이런 운영/애플리케이션 로그는 시스템이 어떻게 동작하는지를 개발자가 이해하기 위한 것이다. 양이 많고, 자유 형식이며, 며칠 지나면 버려도 그만이다.

감사 로그(audit log) 는 목적이 완전히 다르다. 감사 로그는 “누가 무엇을 했는가”라는 보안적으로 의미 있는 사건을 사후에 추적하기 위한 법적·보안적 기록이다. 독자도 다르다. 운영 로그의 독자가 개발자라면, 감사 로그의 독자는 보안 분석가, 사고 조사관, 그리고 감사관이다. 그래서 요구사항도 다르다.

  • 완전성: 보안상 중요한 사건은 빠짐없이 남아야 한다
  • 무결성: 기록된 뒤에는 누구도(관리자조차) 몰래 고치거나 지울 수 없어야 한다
  • 추적성: 하나의 사건을 시간 순으로, 주체별로 재구성할 수 있어야 한다
  • 장기 보관: 규제에 따라 수개월~수년간 보존해야 한다

이 차이를 흐릿하게 두면 위험하다. 디버깅 로그에 의존해 사고를 조사하려다 보면, 정작 필요한 “누가 이 데이터를 지웠는가”는 어디에도 없고 노이즈만 가득한 경우가 많다.

좋은 감사 로그가 답해야 하는 것

잘 설계된 감사 로그의 한 줄은 그 자체로 하나의 완결된 문장이어야 한다. 즉 다섯 가지 질문 — 누가, 무엇을, 언제, 어디서, 그래서 결과는 — 에 모두 답할 수 있어야 한다.

감사 로그가 답해야 할 5가지

가장 중요한 원칙은 구조화(structured) 다. 사람이 읽기 좋은 문장("User 1024 deleted order 8841")이 아니라, 기계가 파싱하기 좋은 JSON 같은 형식으로 남겨야 한다. 그래야 SIEM에서 “지난 1시간 동안 action=DELETE이고 result=failed인 이벤트를 actor별로 집계” 같은 질의가 가능해진다. 자유 텍스트 로그는 사람 눈으로는 읽히지만 자동 탐지에는 무력하다.

다섯 요소를 조금 더 풀어보면 이렇다. 누가(actor) 는 인증된 주체의 안정적인 식별자여야 한다 — 표시 이름이 아니라 변하지 않는 사용자 ID. 언제(timestamp) 는 UTC 기준에 신뢰할 수 있는 시계(NTP 동기화)에서 와야 하며, 가능하면 밀리초 단위까지 남긴다. 결과(result) 는 특히 강조하고 싶다. 성공한 행위만 남기는 실수가 흔한데, 보안 관점에서는 실패한 시도가 더 중요할 때가 많다. 100번의 로그인 실패 뒤의 1번 성공은 무차별 대입 공격의 명백한 신호다.

무엇을 로깅하고, 무엇을 절대 남기지 말아야 하는가

모든 것을 로깅할 수는 없다(비용·노이즈). 보안 관점에서 우선순위가 높은 사건은 대체로 정해져 있다.

반드시 남길 사건
  · 인증: 로그인 성공/실패, 로그아웃, MFA 등록·해제
  · 인가: 권한 변경, 역할 부여, 접근 거부(403)
  · 계정: 생성·삭제·비활성화, 비밀번호 변경
  · 민감 데이터: 개인정보 열람·수정·삭제·내보내기
  · 관리 행위: 설정 변경, 권한 상승(sudo), 키 회전
  · 보안 통제: 통제 우회·비활성화 시도

여기서 가장 흔하고 치명적인 실수는 민감정보를 로그에 흘리는 것이다. 감사 로그는 보안 사건을 남기려는 것이지 비밀을 복사해 두려는 게 아니다. 다음은 절대로 로그에 들어가면 안 된다.

// ❌ 나쁜 예: 민감정보가 그대로 로그에 박힌다
logger.audit({
  action: "LOGIN",
  username: user.email,
  password: req.body.password,      // 평문 비밀번호
  sessionToken: session.token,      // 세션 토큰
  card: req.body.cardNumber,        // 카드번호
});

// ✅ 좋은 예: 식별자와 결과만, 비밀은 마스킹·제외
logger.audit({
  action: "LOGIN",
  actor: `user:${user.id}`,         // 안정적 식별자
  result: ok ? "success" : "failed",
  source_ip: req.ip,
  // 비밀번호·토큰·카드번호는 아예 담지 않는다
});

비밀번호, 세션 토큰, API 키, 카드번호, 주민등록번호 같은 값이 로그에 남으면 로그 저장소가 곧 또 하나의 유출 표적이 된다. 실제로 많은 사고가 “코드는 안전했지만 디버그 로그에 토큰이 평문으로 쌓여 있었다”에서 비롯됐다. 로깅 계층에 마스킹·필터링을 기본값으로 넣어 두는 것이 안전하다.

변조 방지: 로그를 신뢰할 수 있게 만들기

감사 로그의 핵심 가치는 무결성이다. 공격자가 시스템에 침입한 뒤 가장 먼저 하려는 일 중 하나가 자기 흔적을 지우는 것이기 때문이다. 그래서 “관리자 권한을 가진 사람도 과거 로그를 몰래 고칠 수 없게” 만드는 장치가 필요하다.

가장 기본은 격리와 단방향 전송이다. 로그를 만든 애플리케이션 서버에 그대로 쌓아 두지 않고, 별도의 로그 수집 시스템으로 append-only(추가 전용) 로 흘려보낸다. 애플리케이션 계정은 로그를 쓸 수만 있고 읽거나 지울 수는 없게 권한을 분리한다. 클라우드라면 WORM(Write Once Read Many) 스토리지나 객체 잠금(Object Lock)을 쓸 수 있다.

여기에 더해 암호학적 변조 탐지를 얹으면 강력해진다. 각 로그 항목의 해시에 직전 항목의 해시를 포함시키는 해시 체인이 대표적이다.

해시 체인으로 변조를 탐지한다

import hashlib, json

def chain_hash(prev_hash: str, entry: dict) -> str:
    # 현재 데이터 + 직전 해시를 함께 해싱
    payload = prev_hash + json.dumps(entry, sort_keys=True)
    return hashlib.sha256(payload.encode()).hexdigest()

prev = "0" * 64
for entry in audit_stream:
    entry["prev"] = prev
    entry["hash"] = chain_hash(prev, entry)
    store.append(entry)      # append-only 저장
    prev = entry["hash"]

원리는 디지털 서명이나 블록체인과 같다. 누군가 과거의 한 줄을 고치면 그 줄의 해시가 바뀌고, 그 뒤에 이어진 모든 항목의 prev와 어긋난다. 정기적으로 체인을 재계산해 검증하면 변조가 일어난 정확한 지점까지 짚어낼 수 있다. 더 엄격하게는 항목마다 HMAC이나 서명을 붙이거나, 외부 타임스탬프 기관에 주기적으로 체인의 끝 해시를 봉인(notarize)하기도 한다.

보관·접근통제·실무 체크리스트

기술적 장치를 갖췄다면, 운영 정책으로 마무리한다. 핵심은 세 가지다.

보관(retention)
  · 규제 요구 기간 확인 — 개인정보보호법·PCI-DSS 등
  · 핫(빠른 조회) / 콜드(장기 보관) 계층 분리로 비용 최적화
  · 보관 만료 전 자동 삭제 금지(법적 보존 의무 충돌 주의)

접근 통제(access)
  · 로그 조회 권한도 최소 권한 — 분석가/감사관만
  · "감사 로그를 본 행위"도 감사 로그로 남긴다(meta-audit)

시각 동기화(time)
  · 모든 서버 NTP 동기화 + UTC 저장
  · 시계가 어긋나면 사건 순서 재구성이 불가능해진다

특히 시각 동기화는 사소해 보이지만 사고 조사에서 결정적이다. 여러 서버의 로그를 하나의 타임라인으로 합칠 때 시계가 몇 초만 어긋나도 인과관계가 뒤바뀐다. 모든 시스템을 NTP로 맞추고 UTC로 저장하는 것을 기본값으로 두자. 그리고 로그를 남기는 것만큼 정기적으로 들여다보는 것도 중요하다. 아무도 보지 않는 로그는, 로깅·모니터링 실패에서 다뤘듯 사고를 몇 달씩 방치하게 만든다. 핵심 사건에는 알림(alert)을 걸어 두어야 비로소 로그가 살아 있는 방어가 된다.

마치며

여기까지 웹 보안의 큰 지형을 함께 걸었다. 보안의 원칙(CIA·위협 모델링·최소 권한·제로 트러스트)에서 출발해, 인증·인가, OWASP Top 10이 다루는 주입·XSS·CSRF·접근 통제 결함, 브라우저 보안 모델, 암호학, API·클라우드·공급망 보안, 그리고 마지막으로 시큐어 SDLC·테스트·탐지·대응·컴플라이언스까지.

이 모든 주제를 관통하는 하나의 메시지가 있다면, 보안은 기능이 아니라 태도이자 과정이라는 것이다. 어느 한 줄의 코드나 하나의 도구가 시스템을 안전하게 만들지는 않는다. 위협을 가정하고, 다층으로 방어하고, 최소 권한을 적용하고, 실패를 전제로 설계하고, 일어난 일을 신뢰할 수 있게 기록하고 — 이 사이클을 꾸준히 도는 팀이 안전한 시스템을 만든다.

그리고 그 사이클의 끝이자 새로운 시작점에 바로 이 글이 다룬 감사 로그가 있다. 무슨 일이 있었는지를 정직하게 기록하는 것 — 그것이 배우고, 개선하고, 다시 방어하는 모든 일의 출발점이다. 지금까지 함께해 주신 모든 분께 감사드린다. 여기서 멈추지 말고, 여러분의 시스템에 이 원칙들을 하나씩 적용해 나가길 바란다.


지난 글: 보안 컴플라이언스 입문: GDPR·PCI-DSS·ISO 27001 기초


읽어주셔서 감사합니다. 😊