TT Lab
블로그 시작하기
배우기 러닝패스 코스

비밀번호·키·비밀값 다루기

비밀값은 로그로 더 자주 샌다

TT Lab 에서 이어서 보기

한 줄 요약

비밀값은 코드보다 로그로 더 자주 샙니다. 로그에 무언가를 싣는 모든 자리가 가리는 함수를 지나게 하고, 처리하지 못한 예외는 사용자에게 오류 번호만 주고 자세한 내용은 가린 채 로그에 남깁니다.

왜 이 요구가 필요한가

접근 로그에 요청 헤더를 통째로 남기면 Authorization: Bearer … 가 그대로 쌓입니다. 쿼리를 통째로 남기면 OAuth 콜백의 code 와 재설정 링크의 token 이 쌓입니다. 로그인 본문을 디버깅하려고 한 줄 남겨 두면 비밀번호가 평문으로 쌓입니다. 로그는 애플리케이션 데이터보다 더 많은 사람이 보고, 더 오래 남고, 수집기와 검색 도구와 외부 서비스로 복사됩니다. OWASP Logging Cheat Sheet 는 로그에 직접 남기지 말 것으로 세션 식별자, 액세스 토큰, 비밀번호, 민감한 개인정보를 꼽습니다.

예외는 또 다른 경로입니다. DB 드라이버는 접속에 실패하면 접속 문자열을 메시지에 넣곤 하고, 그 문자열에는 비밀번호가 들어 있습니다. 그 메시지를 500 응답에 그대로 실으면 사용자가 보고, logger.exception 으로 남기면 트레이스백 끝에 날것으로 붙습니다. 메시지를 가린다고 고쳐도 logger.exception 을 그대로 두면 트레이스백 쪽으로 다시 샙니다.

어떻게 지키나

가리는 일을 세 함수에 모읍니다.

SENSITIVE = {"authorization", "cookie", "password", "token", "api_key", "secret", "code", ...}

def redact(value):
    if isinstance(value, dict):                       # 열쇠 이름은 대소문자 무시
        return {k: "***" if str(k).lower() in SENSITIVE else redact(v) for k, v in value.items()}
    if isinstance(value, (list, tuple)):
        return [redact(v) for v in value]
    return redact_text(value) if isinstance(value, str) else value

redact_query 는 쿼리에서 민감한 이름의 값만 바꾸고, redact_text 는 문장 속 Bearer 토큰 과 접속 문자열의 비밀번호를 바꿉니다. 중요한 것은 가리는 것이 값이라는 점입니다. 메서드, 경로, 상태 코드, 사용자, 이메일, state 는 남아야 장애를 추적할 수 있습니다. 로그를 통째로 끄는 것은 보안이 아니라 눈을 감는 일입니다.

예외 처리는 이렇게 합니다. 오류 번호(uuid4)를 만들고, 응답에는 {"error": "internal", "error_id": …} 만 싣습니다. 로그에는 같은 번호와 예외 종류, 그리고 traceback.format_exception 으로 만든 글을 redact_text 에 통과시킨 것을 남깁니다. 사용자가 고객센터에 번호를 알려 주면 운영자는 그 번호로 로그를 찾습니다. 사용자에게 필요한 것은 번호이고, 운영자에게 필요한 것은 가린 트레이스백입니다.

시험은 반대 방향 둘을 함께 봅니다. 눈에 띄는 가짜 비밀값을 넣고 그것이 caplog.text 에 없는지, 그리고 남아야 할 경로와 사용자가 있는지. 앞의 것만 보면 로그를 꺼 버린 수정이 통과합니다.

현장에서 만나는 모습

가장 흔한 누수는 디버깅하려고 넣은 로그 한 줄이 배포에 그대로 남는 것입니다. 두 번째는 대소문자입니다. Authorization 은 막았는데 클라이언트가 authorization 이나 Token 으로 보내면 빠져나갑니다. 세 번째는 깊이입니다. 맨 바깥 열쇠만 보면 {"profile": {"new_password": …}} 가 샙니다. 이미 남은 로그를 지우는 것은 어렵습니다. 수집기, 백업, 검색 색인에 복사본이 있기 때문에, 들어가기 전에 막는 쪽이 훨씬 쌉니다.

감사 로그에는 오류 번호와 경로와 예외 종류만 남기고 메시지는 싣지 않습니다. 감사 로그는 오래 보관하는 기록이라 가린 메시지라도 남길 이유가 없습니다.

English corner

다음 실습에서 할 것

주문 API 가 헤더·쿼리·로그인 본문을 통째로 로그에 남기고, 처리하지 못한 예외의 메시지를 500 응답에 싣습니다. 요구 A(값을 가려 로그에 남기기)와 요구 B(오류 번호만 응답하고 가린 트레이스백을 로그에)를 caplog 로 시험하고 고칩니다.