"상대 홀카드를 클라이언트에서 조회할 수 있는가?" — 다벡터 감사 + 로컬 재현 실험
홀카드는 딜 시점 서버 메모리에만 존재하다가 소유자의 활성 소켓으로만 push된다. WebSocket 상태(gC)에서 상대 카드는 value:null로 내려오며, 카드를 ID로 반환하는 조회(REST) 엔드포인트는 존재하지 않는다. 따라서 gameID·playerID를 알아도 상대 패는 볼 수 없다.
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로만 발생.
상대 카드가 존재할 수 있는 모든 클라이언트 경로를 검사 (읽기 전용, 비파괴적).
| # | 벡터 | 방법 | 결과 |
|---|---|---|---|
| 1 | WS gC.pC | 라이브 프레임 파싱 | 상대 = null |
| 2 | 미지 이벤트 (DSP/IGPU/UGD/rEM…) | 페이로드 딥스캔 | 카드 없음 |
| 3 | 바이너리 WS 프레임 | Blob/ArrayBuffer 디코드 | 해당 없음 |
| 4 | React fiber 메모리 | fiber 트리 4만 노드 순회 | pC 미발견 |
| 5 | localStorage/session/IndexedDB | 전 키 스캔 | 없음 |
| 6 | DOM / 렌더링 (SVG·background) | card-back vs face | 뒷면만 |
| 7 | REST (ledger·log·sessions·configs) | 인증 GET + 카드 스캔 | 카드 없음 |
| 8 | prob1/name1 (equity) | 상대 정보 누출 | null |
IDOR 성립 조건은 ① 그 ID로 조회하는 엔드포인트 존재 ② 소유권 검증 부재. PokerNow는 둘 다 실패시킨다.
| 요청 (상대 ID = DUKOzEtRuQ) | 응답 | 카드 |
|---|---|---|
/games/<gid>/players/DUKO | 200 · 52 byte | 없음 (좌석/상태만) |
/games/<gid>/players/DUKO/cards | 404 | 엔드포인트 부재 |
/players/DUKO · /api/players/DUKO | HTML / 404 | 프로필만 |
/player/DUKO (추측) | 429 Rate Limited | 서버가 비정상 프로빙 차단 |
PokerNow 시나리오를 미러링한 socket.io 포커 서버 2종을 작성해 동일 공격을 실행 (로컬 :4099, 인가된 환경).
// 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 — 상대 패 탈취 성공
// 수신자별 뷰 필터링 (= 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 | 수정 원칙 |
|---|---|---|
| over-broadcast | API3 Excessive Data Exposure | 수신자별 뷰 필터링, 숨길 데이터 미전송 |
| IDOR | API1 BOLA | 객체 ID마다 소유권/권한 검증 |
2024-06, 연구자 Kevin Roleke가 저장형 XSS(username·club 설명·ledger 설명 미필터링)로 카드 노출을 보고 → 다음날 패치. 서버 조회가 아니라 피해자 브라우저에 코드를 심어 그 사람 카드를 빼낸 방식이었다. 본 감사의 SECURE 결과가 현재 패치 상태를 독립 검증한다.
security.txt가 없으나 직접 제보(support@pokernow.com)에 신속 대응한다. 신규 발견 시 책임공개 권장.