App Attest로 검증한 것과 남은 공격 범위

App Attest, 단기 토큰과 request proof가 검증하는 범위와 남은 공격 경계

한강자리는 계정 없이 주차장, 행사, 날씨와 알림을 제공한다. 서버는 사용자 인증 대신 HTTP 요청이 등록된 iOS 앱 인스턴스에서 시작됐는지 확인할 신호가 필요했다. API 주소와 payload 형식, 앱에 포함한 API key는 외부에서 관찰하거나 추출할 수 있다.

한강자리는 App Attest로 앱 인스턴스의 key를 등록했다. assertion은 상태나 운영 metric을 바꿀 수 있는 요청에만 사용했다.

일반 요청에는 앱 인스턴스 신호를 확인하고 서버 상태나 운영 지표를 바꾸는 요청에는 본문 무결성 검증을 추가한다.

App Attest 이후 남는 공격 범위

App Attest 적용 뒤에도 공격자는 공개 API를 관찰하고 자동화된 호출을 만들 수 있다. 서버는 다음 신호를 위험 평가와 요청 허용 정책에 사용한다.

  • 배포한 앱 인스턴스에서 시작된 요청인지 확인한다.
  • 서버에 등록한 key와 검증 자료가 이어지는지 확인한다.
  • 상태 변경 요청의 핵심 내용이 바뀌지 않았는지 확인한다.
  • fallback 신호에는 제한된 권한을 적용한다.

첫 화면 조회 API는 외부에서도 반복 호출할 수 있다. 서버는 앱 토큰이 없는 호출을 등록된 앱 요청과 구분하고 rate limit과 abuse metric을 적용한다. 알림 설정처럼 서버 상태를 바꾸는 요청은 앱 인스턴스 신호와 요청 본문을 함께 검증한다.

앱 인스턴스 등록

iOS 보안 모듈은 App Attest 지원 여부를 확인하고 지원되면 단말 안에서 key를 만든다.

앱은 private key를 직접 받지 않는다. key identifier만 받고 등록이 성공한 뒤에만 단말의 안전한 저장소에 남긴다.

등록 흐름은 서버 challenge에서 시작한다.

sequenceDiagram
  autonumber
  participant App as iOS 앱
  participant Apple as Apple App Attest
  participant API as 한강자리 API
  participant DB as 앱 인스턴스 저장소

  App->>API: register용 challenge 요청
  API-->>App: one-time challenge
  App->>Apple: key attestation 생성
  Apple-->>App: attestation object
  App->>API: key id와 attestation object 등록
  API->>API: attestation 검증
  API->>DB: 앱 인스턴스 검증 자료 저장
  API-->>App: 짧은 수명 app access token

서버는 Apple App Attestation 인증서 체인, 서버가 발급한 challenge, Team ID와 Bundle ID 같은 앱 식별 맥락과 key identifier가 서로 맞는지 검증하고 public key와 counter를 저장한다.

일반 요청을 묶는 단기 토큰

등록이 끝난 뒤에는 매 요청마다 attestation을 반복하지 않는다. iOS 앱은 등록한 key로 짧은 수명의 app access token을 갱신하고 일반 API 요청에 token header를 붙인다.

flowchart LR
  Request["일반 앱 API 요청<br/>access token header"] --> Verify["서명과 만료 검증"]
  Verify --> Context["앱 인스턴스 조회<br/>등록 맥락과 상태 확인"]
  Context --> API["API 처리"]

이 token은 사용자를 식별하는 인증 토큰이 아니다. 서버는 등록한 앱 인스턴스와 token 상태를 조회해 일반 API 요청의 허용 여부를 결정한다.

서버는 token을 검증한 뒤 DB의 앱 인스턴스를 다시 본다. token이 말하는 앱 인스턴스와 저장된 등록 맥락이 어긋나거나 비활성 상태라면 거부한다.

body까지 묶는 상태 변경 요청

상태를 만들거나 운영 metric을 오염시킬 수 있는 쓰기 요청에는 등록된 key로 서명한 request proof를 붙였다.

요청 범주왜 더 강하게 봤는가
알림 설정 변경서버에 알림 설정을 만들거나 정리한다.
telemetry 수집이벤트나 성능 신호를 운영 metric에 반영한다.

token은 등록된 앱 인스턴스와 유효 기간을 검증한다. request proof는 서버 challenge와 요청 본문 요약값에 대한 assertion을 검증한다.

서버와 앱은 일회성 challenge와 요청 본문 요약값을 묶어 proof challenge를 만든다. 앱은 이를 App Attest assertion으로 서명하고 서버는 저장된 public key로 검증한다. 요청 본문이 달라지면 서버가 다시 계산한 요약값이 assertion의 client data와 일치하지 않는다. assertion counter는 서버에 저장한 이전 값보다 커야 하므로 캡처한 요청의 replay도 거부한다.

fallback의 제한된 권한

모든 iOS 실행 환경에서 App Attest를 쓸 수 있는 것은 아니다. 개발 환경, 일부 실행 환경, extension 성격에 따라 지원 여부가 다를 수 있다.

한강자리 iOS 앱은 개발용 정책과 배포 빌드 정책을 분리했다.

개발 중에는 로컬 확인을 막지 않는 장치가 필요하지만 배포 빌드는 App Attest 실패를 조용히 통과시키지 않는다.

DeviceCheck fallback도 있다. App Attest가 불가능하고 DeviceCheck는 쓸 수 있다면 앱은 제한된 fallback 신호로 app access token을 받을 수 있다.

서버는 fallback token을 App Attest token과 다른 권한으로 저장한다. 일반 조회는 허용하지만 request proof가 필요한 요청에는 사용할 수 없다.

flowchart TD
  Signal["앱 출처 신호"] --> Strong{"App Attest 가능?"}
  Strong -->|"예"| AppAttest["일반 요청 + 강화 요청 검증"]
  Strong -->|"아니오"| Fallback["제한된 fallback"]
  AppAttest --> Allow["요청 성격에 따라 허용"]
  Fallback --> Limited["일반 앱 요청만 제한적으로 허용"]

요청 유형별 보호 범위

App Attest 적용 뒤 달라지는 요청 검증과 별도로 유지할 보호 수단을 나눠 기록했다.

상황App Attest 적용 뒤 달라지는 점여전히 필요한 것
앱 밖에서 API 모양만 흉내 내는 호출앱 출처 신호가 없으면 등록된 앱 요청으로 처리하지 않는다.IP rate limit, abuse metric, cache 보호
상태 변경 body를 바꾸는 시도요청의 핵심 내용을 함께 검증한다.알림 설정 보호, 구독 정리, audit
이전 요청을 다시 보내는 replay일회성 challenge와 이전 값보다 큰 counter를 검증한다.clock, 저장소 장애, retry 정책
App Attest가 안 되는 환경fallback을 제한된 권한으로 다룬다.배포 빌드의 fail-closed 정책, 개발용 token 관리
손상된 단말이나 broker형 호출fraud metric을 위험 신호로 사용한다.rate limit, abuse monitoring

Apple은 App Attest 하나로 손상된 OS를 확정할 수 없다고 설명한다. 한강자리는 이 결과를 요청 출처의 위험 신호로 사용하며 rate limit, 로그 마스킹, token 만료, key recovery와 운영 metric을 함께 둔다.

App Attest fraud metric

Apple은 App Attest receipt를 서버에서 Apple로 보내 fraud metric을 받을 수 있게 한다. 이 값은 최근 30일 동안 특정 device/app 쌍에서 만들어진 attested key의 근사 수다.

한강자리는 이 신호를 즉시 차단 조건으로 쓰지 않고 baseline과 급격한 변화를 확인하는 metric으로 분류했다.

앱 재설치, 백업 복원과 기기 소유자 변경도 key 수를 늘릴 수 있다. 그래서 metric 하나로 요청을 차단하지 않는다.

요청 범주별 검증 정책

요청별 정책에는 앱 인스턴스 확인 여부, body assertion 필요 여부, fallback 허용 범위와 검증 실패 시 동작을 명시했다. 일반 API 요청에는 짧은 수명 token을 사용하고 상태나 운영 metric을 바꾸는 요청에는 request proof를 추가한다. DeviceCheck fallback은 일반 조회에만 허용하며 세 요청 범주를 서로 다른 권한으로 처리한다.

참고한 Apple 문서는 DeviceCheck, DCAppAttestService, Validating apps that connect to your server, Assessing fraud risk다.

Comments

댓글

    이미지 확대