들어가며
모바일 서비스에서 회원가입 보상, 리워드 적립, 쿠폰 지급과 같은 기능을 운영하다 보면 반드시 만나게 되는 문제가 있다.
- 앱을 리버스 엔지니어링해서 API를 직접 호출하는 경우
- 탈옥(Jailbreak) 환경에서 앱을 변조하는 경우
- 스크립트나 매크로로 대량 계정을 생성하는 경우
- 앱을 실행하지 않고 서버 API만 호출하는 경우
이러한 공격을 막기 위해 Apple은 App Attest라는 무결성 검증 기능을 제공한다.
App Attest를 활용하면 서버는 다음을 검증할 수 있다.
“이 요청이 실제 App Store에서 배포된 우리 앱에서 발생한 것인가?”
이번 글에서는 App Attest의 개념부터 실제 구현 방법까지 자세히 알아본다.
App Attest란?
App Attest는 Apple이 제공하는 DeviceCheck 프레임워크의 기능 중 하나다.
앱이 실행되면 Apple 서버로부터 암호학적으로 검증 가능한 키(Key)를 발급받고, 이후 서버는 해당 키를 이용하여 요청이 실제 앱에서 생성되었는지 검증할 수 있다.
즉,
클라이언트
↓
Apple App Attest
↓
암호학적 증명 생성
↓
서버 검증
구조로 동작한다.
왜 필요한가?
일반적으로 서버는 다음 정보들을 받아 클라이언트를 식별한다.
- Device ID
- UUID
- 광고 ID(IDFA)
- 쿠키
- User-Agent
하지만 이런 값들은 쉽게 조작할 수 있다.
예를 들어 공격자는
- Charles
- Proxyman
- Burp Suite
- Frida
- Objection
등의 도구를 이용해 API 요청을 복제할 수 있다.
반면 App Attest는 Apple이 발급한 개인키(Private Key)를 기반으로 서명을 수행하기 때문에 공격자가 단순히 HTTP 요청만 복사해서는 우회하기 어렵다.
App Attest 동작 원리
전체 흐름은 다음과 같다.
1. 앱 실행
2. App Attest Key 생성
3. Attestation 생성
4. 서버 검증
5. 정상 기기로 등록
6. API 호출 시 Assertion 생성
7. 서버 검증
Step 1. Key 생성
앱은 최초 실행 시 App Attest Key를 생성한다.
let service = DCAppAttestService.shared
let keyId = try await service.generateKey()
생성된 keyId는 기기를 식별하는 고유 키 역할을 한다.
예시:
F3A9D7B4-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Step 2. Attestation 생성
서버는 클라이언트에게 Challenge를 전달한다.
예:
{
"challenge": "abc123"
}
클라이언트는 이를 SHA256 해시한 후 Attestation을 생성한다.
let challengeHash = SHA256.hash(data: challengeData)
let attestation = try await service.attestKey(
keyId,
clientDataHash: Data(challengeHash)
)
결과:
attestationObject
가 생성된다.
Step 3. 서버 검증
클라이언트는 다음 정보를 서버에 전송한다.
{
"keyId": "...",
"attestation": "...",
"challenge": "..."
}
서버는 Apple의 공개키 체인을 이용해 검증한다.
검증 항목:
- Apple이 발급한 키인가?
- Bundle ID가 일치하는가?
- Team ID가 일치하는가?
- Challenge가 일치하는가?
- 위조되지 않았는가?
검증 성공 시:
기기 등록 완료
상태가 된다.
Assertion이란?
Attestation은 최초 등록 시에만 수행한다.
이후 매 API 호출마다 Attestation을 수행하면 성능 비용이 크다.
그래서 App Attest는 Assertion이라는 개념을 제공한다.
Attestation = 최초 등록
Assertion = 이후 요청 검증
Assertion 생성
서버가 nonce를 전달한다.
{
"nonce": "xyz789"
}
클라이언트는 nonce를 서명한다.
let assertion = try await service.generateAssertion(
keyId,
clientDataHash: Data(hash)
)
Assertion 검증
서버는 저장된 공개키를 사용하여 검증한다.
검증 성공 시
이 요청은 등록된 실제 앱에서 생성되었다.
라고 판단할 수 있다.
실제 구현 아키텍처
실무에서는 보통 아래 구조를 사용한다.
┌─────────────┐
│ iOS App │
└──────┬──────┘
│
▼
┌─────────────┐
│ App Attest │
└──────┬──────┘
│
▼
┌─────────────┐
│ AP Server │
└──────┬──────┘
│
▼
┌─────────────┐
│ API Server │
└─────────────┘
AP Server는
- Key 등록
- Attestation 검증
- Assertion 검증
역할을 담당한다.
API Server는
"이 기기는 신뢰 가능한가?"
만 확인하면 된다.
서버에 저장해야 하는 정보
예시 테이블
CREATE TABLE app_attest_keys (
id BIGINT PRIMARY KEY,
user_id BIGINT,
device_id VARCHAR(255),
key_id VARCHAR(255),
public_key TEXT,
created_at DATETIME
);
저장 항목:
- user_id
- key_id
- public_key
- 등록 시각
App Attest만으로 충분할까?
아니다.
App Attest는 매우 강력하지만 만능은 아니다.
다음과 함께 사용하는 것이 일반적이다.
Android
- Play Integrity API
Device Intelligence
- 디바이스 지문(Device Fingerprint)
- Emulator 탐지
- Root/Jailbreak 탐지
계정 보호
- Phone Verification
- Email Verification
- Rate Limiting
행동 분석
- Fraud Score
- Velocity Check
- Device Cluster 분석
App Attest 도입 시 주의사항
1. 모든 기기에서 지원되지 않는다
App Attest는
- iOS 14+
- Secure Enclave 지원 기기
에서만 동작한다.
따라서 예외 처리가 필요하다.
if DCAppAttestService.shared.isSupported {
// App Attest 사용
}
2. 네트워크 실패 고려
최초 등록 과정에서 Apple 서버와 통신이 필요하다.
따라서
- 재시도
- 타임아웃
- 백오프
처리가 필요하다.
3. 서버 검증 구현 난이도가 높다
Apple은 검증 로직을 직접 제공하지 않는다.
서버에서
- CBOR 파싱
- X.509 인증서 검증
- COSE 검증
을 구현해야 한다.
보통은 오픈소스 라이브러리를 활용한다.
결론
App Attest는 iOS에서 제공하는 가장 강력한 앱 무결성 검증 기술 중 하나다.
이를 도입하면 서버는 단순히 “로그인된 사용자”가 아니라
“실제 App Store에서 배포된 우리 앱이 생성한 요청”
인지를 검증할 수 있다.
특히 다음과 같은 서비스에서 효과가 크다.
- 리워드 앱
- 포인트 서비스
- 쿠폰 서비스
- 금융 서비스
- 게임
- 대규모 회원가입 이벤트
다만 App Attest는 단독 솔루션이 아니라 보안 체계의 한 축으로 보는 것이 중요하다.
실무에서는 App Attest + Play Integrity API + Device Intelligence + Fraud Score를 조합하여 다계층 방어 체계를 구축하는 것이 일반적이다.
추가로 AP랩 관점에서 “Play Integrity API와 App Attest 비교”, “캐시워크 같은 리워드 앱에서 App Attest를 어떻게 적용할 것인가”, “서버 검증 코드(Node.js/NestJS 예제)“까지 포함한 실무 버전으로 확장할 수도 있습니다.
'어뷰징 방지' 카테고리의 다른 글
| iOS 탈옥(Jailbreak) 알아보기 (0) | 2026.06.01 |
|---|---|
| OS별 모바일 기기 식별자 (Andoird, iOS) (1) | 2026.04.12 |
| 어뷰저의 한국 SMS OTP 기반 다계정 생성 비용 분석(+PASS 인증) (2) | 2026.03.29 |
| 안드로이드 앱 심사 리젝 사건 - 에뮬레이터 차단 기능 (1) | 2026.03.01 |
| IP주소로 알 수 있는 정보 (3) | 2025.12.21 |