결제 구간 앱 서명 위변조 트래픽을 단계별로 차단한 방법
목차
배경
모바일 앱에서 서버로 들어오는 요청이 진짜 우리 앱에서 온 게 맞는지 확인할 방법이 필요했음. 그동안 디바이스 식별자만 보고 신뢰했는데, 리패키징된 패키지로 위변조 트래픽이 들어오는 정황이 잡힘. 결제대행사 연동 구간이라 더 미룰 수 없었음.
접근 — X-Sig-Hash 헤더
앱 서명 인증서의 SHA-256 해시를 매 요청마다 헤더에 실어 보내고, 서버에서 화이트리스트와 비교하는 방식으로 감.
| 항목 | 값 |
|---|---|
| 헤더명 | X-Sig-Hash |
| 알고리즘 | SHA-256 |
| 비교 대상 | 서명 인증서 raw bytes |
| 화이트리스트 | DB 테이블 + 메모리 캐시 |
검증 로직은 한 컴포넌트로 모음.
fun verify(header: String?): Result {
if (header.isNullOrBlank()) return Result.MISSING
return if (allowed.contains(header.lowercase()))
Result.OK
else Result.MISMATCH
}
가장 신경 쓴 부분 — grace period
문제는 이미 깔린 구버전 앱들이었음. 헤더 안 붙이고 들어오는 트래픽을 한 방에 끊으면 결제 흐름이 통째로 막히는 사고가 남. 그래서 enforcing 한 번에 안 가고 단계를 나눔.
- 1단계 (관찰): 헤더 없거나 mismatch 떠도 통과시킴. 카운트만 적재.
- 2단계 (경고): 응답에 deprecation 플래그 실어줌. 강제 업데이트 유도.
- 3단계 (강제): mismatch는 즉시 401, missing은 grace period 종료 후 거부.
지금은 디바이스 등록과 결제 트리거 두 엔드포인트에만 훅을 걸고 1단계로 운영 중. 일주일 로그 돌려보면서 화이트리스트 누락 잡을 예정.
의외로 까다로웠던 것
- 빌드 변형별 해시 차이: 디버그/스테이징/릴리스 키스토어가 다 다름. 사내 QA 트래픽이 mismatch로 잡혀서 한참 헤맴. 환경별 해시를 다 넣고 환경 라벨도 같이 박아둠.
- 대소문자 normalize: 클라이언트가 대문자로 보내는 케이스가 있어서 서버에서 lowercase 처리.
- 누락 vs 위조 구분: 둘 다 차단 대상이지만 메트릭은 분리. 누락은 구버전 지표, mismatch는 보안 지표로 봄.
회고
처음엔 "그냥 막으면 되지" 싶었는데, 설치 베이스가 깔린 앱에서 검증 정책 추가하는 건 결제 장애 한 번이면 끝나는 일이라 grace period 없이는 못 갔음. 단계 차단 + 메트릭 분리로 가니까 그나마 마음 편함. 다음 단계는 mismatch 비율 보고 2단계 승격 시점 정하기.
다음
댓글 0
첫 댓글 달아줘.