전체 글
약 2분

PRO 코드의 평문 우회와 잘못 적힌 출시 기록을 고쳤습니다

PRO 프로모션 코드에서 해시 비교를 건너뛰는 평문 조건을 지웠습니다. 함께 확인한 CHANGELOG에서는 아직 출시하지 않은 기능을 미출시 절로 옮겼습니다.

#보안 #비즈니스모델 #안드로이드 #CI #품질

들어가며

Pocket PDF에 인앱 결제와 구독을 추가하던 날, PRO 프로모션 코드를 확인했습니다. 입력을 SHA-256으로 해시해 저장된 값과 비교하는 코드가 있었는데, 그 앞의 평문 조건만으로도 PRO를 열 수 있었습니다.

해시가 일치하지 않아도 참이 됐습니다

문제를 보여주기 위해 당시 조건을 줄이면 다음과 같습니다.

fun isProCode(input: String): Boolean {
    val inputHash = sha256(input.trim())
    return input.equals("pro", ignoreCase = true)
        || input.equals("premium", ignoreCase = true)
        || inputHash == PROMO_SECRET_HASH
}

||로 연결된 조건은 하나만 참이어도 통과합니다. pro나 premium을 입력하면 마지막 해시 비교의 결과와 관계없이 PRO가 열렸습니다. 실제 코드에는 개발 중 쓰던 평문 조건이 넷 있었습니다.

PRO 가격은 월 ₩1,900, 연 ₩14,900, 평생 ₩19,900으로 정해 둔 상태였습니다. 결제를 붙이면서도 개발 중 열어 둔 입구는 닫지 않았습니다.

PROMO_SECRET_HASH의 옛 값도 확인했습니다. 후보를 해시해 비교했지만 어떤 코드를 위한 값인지 찾지 못했습니다. 평문 조건 넷과 이 옛 값을 제거하고, 사용할 코드를 정해 해시 하나만 남겼습니다.

판정은 여전히 앱 안에서 합니다. 평문 우회는 없앴지만, 클라이언트 코드를 분석하거나 수정하는 경우까지 해시가 막아 주지는 않습니다. 그 한계도 주석에 적었습니다.

CHANGELOG가 출시 커밋과 맞지 않았습니다

같은 작업에서 CHANGELOG도 고쳤습니다. v1.0.0 절에 고급 기능이 적혀 있었지만, 그 버전은 전날 이미 테스터에게 배포한 상태였습니다. 새 기능의 소스는 그다음 날 들어왔습니다.

출시 커밋에는 FolderEntity, ProPaywallSheet, PdfSigner 같은 파일이 없었습니다. 문서만 읽으면 테스터가 받은 앱에도 해당 기능이 있는 것으로 이해할 수 있었습니다.

새 기능은 v1.0.0 위에 만든 미출시 (Unreleased) 절로 옮겼습니다. 다음 번호는 비워 뒀습니다. 무엇을 묶어 출시할지 정한 뒤에 적기로 했습니다.

마치며

프로모션 코드에서는 평문으로 통과하던 조건을 없앴고, 출시 기록에서는 아직 나가지 않은 기능을 분리했습니다. 해시만 남긴 코드의 한계와 다음 출시 번호를 정하지 않은 상태도 함께 기록했습니다.

이날 함께 발견한 광고 ID 검사 문제는 보상형 광고의 검사 목록을 보완한 글에 따로 적었습니다.