← 인프라·격리

같은 결함이 닷새 간격으로 두 번 났고 원인은 4개월 전 커밋 하나였다

배포 제외 목록에서 빠진 자산 두 건이 닷새 간격으로 공개 서빙됐다. 첫 건은 캐시 퍼지 세 가지가 성공 응답을 주고도 살아남았고, 둘째 건의 원인은 4개월 전 백업 커밋 하나였다

목차
  1. 문제
  2. 노출은 해시 대조로 확정했다
  3. 배포에서 빼도 캐시 사본은 산다
  4. 닷새 뒤 같은 결함이 다시 났다
  5. 결론
  6. 한계·재검토 트리거

공개 표면에 있으면 안 되는 것 두 건이 닷새 간격으로 각각 발견됐고, 둘 다 정상 응답 코드인 200으로 서빙되는 중이었다.
그 사이에 계정 전수감사도, 대표 연락처 전면 이전도, 사이트를 세 리포로 나누는 재분배도 끝나 있었다.
파일 단위로는 전부 옳게 실행된 뒤였다.

문제

공개 표면을 정리하는 작업은 대개 파일 단위로 진행된다.
어느 파일에 무엇이 남았는지 찾고, 고치고, 지운다.
검증도 같은 단위로 붙는다.
그 파일이 리포에서 사라졌는지 본다.
이 단위는 실행하기 쉽고 대조하기도 쉽다.

그런데 공개 여부를 정하는 것은 리포가 아니라 배포다.
정적 사이트는 리포의 일부만 골라 올리고, 그 선별은 배포 제외 목록 한 곳에 적혀 있다.
파일을 아무리 정확하게 손봐도 그 목록을 읽지 않으면 무엇이 나가는지는 확인되지 않는다.
아래 두 사례가 정확히 이 간극에서 나왔다.

노출이 쌓이는 방식도 이 단위 문제와 붙어 있다.
공개된 것들은 어느 날 실수로 새어 나간 게 아니라 사이트를 성실히 관리한 흔적으로 남았다.
문서를 정리하고 백업을 챙기는 작업이 그 순간에는 전부 맞는 판단이었다.
그래서 나중에 다시 볼 때 의심할 대상이 잡히지 않는다.
파일마다 정당한 이유가 붙어 있고, 정당한 파일이 배포되면 안 되는 파일일 수 있다는 사실만 어디에도 적혀 있지 않다.

노출은 해시 대조로 확정했다

재분배에 착수하기 직전, 운영 문서 두 건이 공개로 서빙되는 것을 잡았다.
메일 운영 런북과 DNS(도메인 이름 시스템) 존 백업이고 둘 다 200이었다.
본문에 시크릿 값 자체는 없었다.
대신 2단계 인증 시드 파일의 경로, API(응용 프로그램 인터페이스) 토큰의 이름과 권한 범위, 존 전문이 그대로 읽혔다.
값이 없어도 공격면의 지도는 완성된다.

노출 판정은 응답 코드로 내리지 않았다.
플래그가 걸린 세 건을 재검수하면서 서빙되는 내용과 리포 안 원본의 내용 해시를 대조했고, 해시가 일치한 건만 실노출로 확정했다.
200은 무언가가 응답했다는 사실까지만 말한다.
그 무언가가 지우려던 그 문서인지는 따로 세야 하는 값이다.

배포에서 빼도 캐시 사본은 산다

배포 제외 목록에 항목을 추가하고 다시 배포했다.
빌드는 성공했고 오리진에서 파일이 사라진 것도 확인했다.
그런데 공개 주소는 계속 200을 돌려줬다.

주소 단위 퍼지, 전체 퍼지, 재배포 세 가지를 차례로 걸었다.
셋 다 성공 응답을 받았고 셋 다 사본을 죽이지 못했다.
원인은 Cloudflare Pages의 계층형 캐시가 정적 자산을 보존하는 TTL(캐시 보존 시간)이 1주라는 것이었다.
퍼지 명령이 돌려준 성공은 요청이 접수된 지점까지의 결과였고, 상위 계층에 남은 사본은 그 응답 뒤에서 계속 살아 있었다.

끊은 것은 캐시보다 앞단이다.
존 단위 리다이렉트 규칙으로 해당 경로 전체에 와일드카드 301을 걸자 그 시점에 공개가 멎었다.
캐시를 지우는 대신 캐시에 닿기 전에 요청을 돌려세운 것이다.

닷새 뒤 같은 결함이 다시 났다

2026-08-30, 구 이력서 PDF 한 건이 같은 방식으로 공개 서빙되는 것이 발견됐다.
대표 연락처 전면 이전과 계정 전수감사에서 지운 것들이 그 안에 그대로 있었다.
이전 대표 연락처, 구 이메일 주소, 실명 병기, 지운 자격 문구가 정리 이전 상태로 남아 있었다.
지운 뒤에도 지우기 전의 사본이 공개 주소 하나로 계속 배포되고 있었다.

원인은 4개월을 거슬러 올라간다.
2026-04-26에 백업을 남겨두려고 찍은 평범한 커밋 하나가 그 파일을 리포에 넣었고, 그 파일이 놓인 정적 자산 디렉터리가 배포 제외 목록에 없었다.
사실은 이 둘뿐이다.
그 뒤 4개월 동안 아무것도 이 조합을 경고하지 않았다.

두 사실 각각은 그동안 여러 번 눈앞을 지나갔다.
백업 파일은 리포를 열 때마다 목록에 있었고, 배포 제외 목록도 그 사이 몇 번 손봤다.
문제는 둘을 같은 화면에 놓고 대조한 적이 없다는 것이다.
파일 쪽만 보면 백업은 정상이고, 목록 쪽만 보면 적힌 항목은 전부 맞다.
어긋남은 두 목록의 차집합에만 있고, 그 차집합을 계산하는 절차가 어느 단계에도 없었다.

첫 사례 (2026-08-25 발견)둘째 사례 (2026-08-30 발견)
공개된 것운영 문서 두 건구 이력서 PDF 한 건
리포에 들어온 시점발견과 같은 달발견 4개월 전
직접 원인배포 제외 목록에 해당 경로 없음배포 제외 목록에 해당 경로 없음
실노출 확정 방법내용 해시 대조공개 주소 응답 확인
발견 시점 조치제외 추가 후 와일드카드 301미조치

두 사례를 나란히 놓으면 직접 원인 한 줄만 겹친다.
첫 사례에서 목록에 한 항목을 추가했다.
그때 목록 전체를 다시 읽었다면 자산 디렉터리가 빠져 있다는 것도 같은 화면에서 보였다.
그 한 번을 하지 않은 대가가 닷새다.

결론

감사 단위를 파일에서 배포 경계로 올린다.
감사 단위가 파일이면 배포 경계는 애초에 감사 대상 밖이다.
계정을 전수로 훑고 연락처를 전면 이전하고 리포를 셋으로 나누는 동안, 무엇이 실제로 배포되는지를 정하는 목록은 한 번도 통째로 읽히지 않았다.

그래서 확인 대상을 바꾼다.
무엇을 지웠는가 말고 무엇이 나가는가를 목록으로 확인한다.
목록에 항목을 더할 일이 생기면 그 항목만 보지 말고 목록 전체를 그때 통째로 다시 읽는다.
첫 사례를 조치하던 그때 함께 끝날 수 있었던 일이다.

전수감사도 연락처 이전도 재분배도 각각의 범위 안에서는 계획대로 끝났다.
매번 어긋난 자리는 조치 쪽이 아니라 그 범위를 어디에 그었는가였다.
예외를 먼저 세는 습관은 관제에서 몸에 붙었고 이번에도 작동했지만, 그 습관은 자기가 보기로 정한 범위 안에서만 예외를 센다.
그래서 그 범위를 파일에서 배포 경계까지 넓혀 다시 그었다.

한계·재검토 트리거

2026-09 기준 / 재검토 트리거: 배포 제외 목록을 전수로 대조하는 점검을 붙인 뒤에도 배포 경계 누락이 한 건이라도 다시 관측되면, 목록 대조로는 부족하다는 뜻이므로 이 글의 판정을 다시 연다.