PokerNow 카드 가시성 보안 분석

"상대 홀카드를 클라이언트에서 조회할 수 있는가?" — 다벡터 감사 + 로컬 재현 실험

대상: pokernow.com (play-money) · 게임 pglhBisjLlxYW5SjXaJrhGzxP · 본인 소유 테스트 게임 · 2026-06-24

✅ SECURE 11+ 벡터 전수 검사 결과, 상대 히든카드는 클라이언트 어디에서도 획득 불가. PokerNow는 서버 권위적 모델이 올바르게 구현됨.

1. 핵심 결론

홀카드는 딜 시점 서버 메모리에만 존재하다가 소유자의 활성 소켓으로만 push된다. WebSocket 상태(gC)에서 상대 카드는 value:null로 내려오며, 카드를 ID로 반환하는 조회(REST) 엔드포인트는 존재하지 않는다. 따라서 gameID·playerID를 알아도 상대 패는 볼 수 없다.

2. 데이터 모델 — gC 이벤트의 pC

모든 게임 상태는 단일 socket.io 이벤트 gC로 흐른다. 실제 캡처:

// 뷰어 = ZwVO_RuAVv (본인). 채널: wss://www.pokernow.com/socket.io/?EIO=3
"pC": {
  "ZwVO_RuAVv": { "cards":[ {"value":"7d","showing":false}, {"value":"Qc","showing":false} ] },  // 본인 = 실제값
  "DUKOzEtRuQ": { "cards":[ {"value":null,"showing":false}, {"value":null,"showing":false} ] }   // 상대 = null
}

상대 카드는 서버가 값 자체를 보내지 않는다. 공개는 쇼다운 시 showing:true로만 발생.

3. 다벡터 누출 감사

상대 카드가 존재할 수 있는 모든 클라이언트 경로를 검사 (읽기 전용, 비파괴적).

#벡터방법결과
1WS gC.pC라이브 프레임 파싱상대 = null
2미지 이벤트 (DSP/IGPU/UGD/rEM…)페이로드 딥스캔카드 없음
3바이너리 WS 프레임Blob/ArrayBuffer 디코드해당 없음
4React fiber 메모리fiber 트리 4만 노드 순회pC 미발견
5localStorage/session/IndexedDB전 키 스캔없음
6DOM / 렌더링 (SVG·background)card-back vs face뒷면만
7REST (ledger·log·sessions·configs)인증 GET + 카드 스캔카드 없음
8prob1/name1 (equity)상대 정보 누출null

관측: 3핸드 / gC 13회 / self=ZwVO_RuAVv / 누출 0건.

4. IDOR — "상대 ID로 조회하면?"

IDOR 성립 조건은 ① 그 ID로 조회하는 엔드포인트 존재 ② 소유권 검증 부재. PokerNow는 둘 다 실패시킨다.

요청 (상대 ID = DUKOzEtRuQ)응답카드
/games/<gid>/players/DUKO200 · 52 byte없음 (좌석/상태만)
/games/<gid>/players/DUKO/cards404엔드포인트 부재
/players/DUKO · /api/players/DUKOHTML / 404프로필만
/player/DUKO (추측)429 Rate Limited서버가 비정상 프로빙 차단
홀카드를 반환하는 by-ID 엔드포인트가 아예 없고, 카드는 소유자 소켓 전용 전송이므로 ID를 알아도 조회 대상 자체가 없다.

5. 로컬 재현 실험 — 취약 vs 패치

PokerNow 시나리오를 미러링한 socket.io 포커 서버 2종을 작성해 동일 공격을 실행 (로컬 :4099, 인가된 환경).

VULNERABLE

// over-broadcast
io.emit('gameState', game);
// IDOR: 검증 없음
app.get('/player/:id/cards', (q,r)=>
  r.json({cards: players[q.params.id].cards}))
A(상대) 카드: [ '5h', 'Qs' ]  ❌
GET /player/A/cards →
  {cards:['5h','Qs']}❌ VULNERABLE — 상대 패 탈취 성공

PATCHED

// 수신자별 뷰 필터링 (= PokerNow)
cards: pid===viewer ? p.cards
       : p.cards.map(()=>null)
io.to(socketId).emit('gameState', viewFor(id))
// IDOR fix: 소유권 체크
if(requester!==id) return res.status(403)
A(상대) 카드: [ null, null ]  ✅
GET /player/A/cards →
  403 forbidden✅ SECURE — 상대 카드 안 보임

매핑 — OWASP API Top 10

취약점OWASP수정 원칙
over-broadcastAPI3 Excessive Data Exposure수신자별 뷰 필터링, 숨길 데이터 미전송
IDORAPI1 BOLA객체 ID마다 소유권/권한 검증

6. 선행 연구 & 책임공개

2024-06, 연구자 Kevin Roleke가 저장형 XSS(username·club 설명·ledger 설명 미필터링)로 카드 노출을 보고 → 다음날 패치. 서버 조회가 아니라 피해자 브라우저에 코드를 심어 그 사람 카드를 빼낸 방식이었다. 본 감사의 SECURE 결과가 현재 패치 상태를 독립 검증한다.

PokerNow는 공식 버그바운티/security.txt가 없으나 직접 제보(support@pokernow.com)에 신속 대응한다. 신규 발견 시 책임공개 권장.