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

지운 토큰이 기록에서 문을 연다

지운 토큰이 기록에서 문을 연다

TT Lab 에서 이어서 보기

이 실습은 VM 에서 돕니다

우분투 24.04 VM 한 대입니다. root 이고 systemd 와 커널 기능이 그대로 있습니다. 실습 파드에는 capability 가 없어 이 실습이 다루는 설정을 바꿀 수 없기 때문에 VM 으로 돕니다. 처음 뜨는 데 몇 분이 걸립니다. 세션이 끝나면 VM 은 통째로 사라지므로 남기고 싶은 것은 화면에서 복사해 두세요.

목표

Git 기록에 남은 결제 API 토큰을 찾고, 그 토큰으로 실제로 무엇이 열리는지 확인한 뒤, 토큰을 회전하고 기록과 원격 저장소에서 지웁니다. 마지막으로 같은 일이 다시 커밋되지 않게 막고, 이미 누가 썼는지 저널에서 찾습니다.

왜 중요한가

비밀값 유출의 흔한 모습은 "실수로 올렸다가 바로 지웠다" 입니다. 다음 커밋에서 파일을 지우면 화면에서는 사라지지만, Git 은 모든 커밋을 그대로 들고 있습니다. 태그 하나, 누군가의 클론 하나, 원격 서버에 남은 객체 하나면 토큰은 계속 살아 있습니다.

그래서 순서가 중요합니다. 먼저 토큰을 죽이고(회전), 그다음 기록을 정리하고, 마지막으로 재발을 막습니다. 기록 정리를 먼저 하면 마음은 편해지지만 이미 복제된 사본에 대해서는 아무것도 하지 못합니다.

단계

  1. /root/payments 의 작업 트리만 gitleaks 로 훑어 /root/leak/tree.json 에 저장합니다.
  2. 같은 설정으로 모든 참조의 기록을 훑어 /root/leak/history.json 에 저장합니다.
  3. 찾은 토큰으로 고객 목록 API 를 불러 응답을 /root/leak/impact.json 에 저장합니다.
  4. 새 토큰을 만들어 /etc/payapi/token 에 넣고 서비스가 다시 읽게 합니다.
  5. 기록을 다시 써서 토큰을 지우고, 원격에 덮어 올리고, 원격의 옛 객체까지 정리합니다.
  6. pre-commit 훅으로 토큰이 든 커밋을 막습니다.
  7. 저널에서 옛 토큰으로 성공한 요청을 찾아 /root/leak/usage.json 에 정리합니다.

참고

지금 파일만 본다

/root/payments 의 작업 트리만 gitleaks 로 훑어 결과를 JSON 으로 /root/leak/tree.json 에 저장하세요. 저장소의 .gitleaks.toml 을 설정으로 씁니다.

gitleaks 8.16 은 detect 하나로 두 가지를 합니다. 기록을 보지 않고 파일만 보게 하는 옵션이 따로 있고, 보고서 형식과 경로도 옵션으로 줍니다. 찾은 것이 없으면 빈 배열이 저장됩니다.

기록 전체를 본다

같은 설정으로 이번에는 모든 브랜치와 태그의 기록을 훑어 /root/leak/history.json 에 저장하세요. 사내 토큰 규칙(labhub-api-token)에 걸린 항목이 나와야 합니다.

기록 모드가 기본입니다. 어느 커밋까지 볼지는 git log 에 넘길 인자로 정하는데, 지금 체크아웃한 브랜치만이 아니라 모든 참조를 보게 해야 태그에만 남은 커밋까지 닿습니다.

이 토큰으로 무엇이 열리는가

기록에서 찾은 토큰을 그대로 써서 http://127.0.0.1:8088/v1/customers 를 부르고, 받은 응답 본문을 /root/leak/impact.json 에 저장하세요. 토큰은 Authorization: Bearer <토큰> 머리글로 보냅니다.

앞 단계 보고서의 Secret 필드나, 그 커밋의 파일을 git show <커밋>:<경로> 로 꺼내면 토큰이 보입니다. 이 토큰이 아직 살아 있다는 것이 이 사고의 크기입니다.

토큰을 먼저 죽인다

새 토큰(lhk_live_ 뒤에 소문자 16진수 32자리)을 만들어 /etc/payapi/token 에 넣고(권한 600), 결제 API 가 그것을 다시 읽게 하세요. 옛 토큰은 401, 새 토큰은 200 이어야 합니다.

파일을 바꾸는 것과 서비스가 바꾼 값을 쓰는 것은 다른 일입니다. 이 서비스는 기동할 때 토큰을 메모리에 읽어 두고, 유닛에 다시 읽는 방법(ExecReload)이 적혀 있습니다.

기록과 원격에서 지운다

/root/payments 의 기록을 다시 써서 옛 토큰을 REDACTED 로 바꾸고, 원격 /srv/git/payments.git 에도 모든 브랜치와 태그를 덮어 올린 뒤, 원격에 남은 옛 객체까지 정리하세요. 두 저장소 어느 객체에도 옛 토큰이 없어야 하고, 원격의 main 에는 client.py 가 그대로 있어야 합니다.

새 커밋으로 지우면 옛 커밋은 그대로 남습니다. 기록 전체에서 문자열을 바꾸는 도구(git filter-repo 의 --replace-text)를 쓰고, 그 도구가 origin 을 지운다는 것을 기억하세요. 원격에 강제로 올린 뒤에도 참조를 잃은 옛 객체는 정리(gc)하기 전까지 남습니다.

다음 커밋을 막는다

/root/payments 에 pre-commit 훅(.git/hooks/pre-commit, 실행 가능)을 두어, 스테이지에 토큰이 있으면 커밋이 거부되게 하세요. 토큰이 없는 평범한 커밋은 그대로 되어야 합니다.

gitleaks 에는 스테이지된 변경만 보는 모드가 있습니다. 훅은 0 이 아닌 값으로 끝나면 커밋을 멈춥니다. 채점기는 저장소 사본에서 두 가지 커밋을 직접 해 봅니다.

누가 이미 썼는가

결제 API 의 저널에서 옛 토큰으로 성공한 요청을 찾아 /root/leak/usage.json 에 fingerprint(저널에 찍힌 옛 토큰의 지문)와 external_clients(성공한 요청을 보낸 주소 중 127.0.0.1 을 뺀 것, 사전순 배열)를 저장하세요.

저널 줄에는 토큰 원문 대신 지문(fp)이 찍힙니다. 이 서비스는 sha256 의 앞 12자를 지문으로 씁니다. 옛 토큰의 지문을 구해 status=200 인 줄만 고르세요. 여러분이 3단계에서 보낸 요청은 127.0.0.1 로 찍혀 있습니다.