Repository navigation
fix(session): callback.voice 가 메시지의 세션 소유를 검증하도록 (보안) - #254
Merged
Merged
Conversation
RealTime Go 코드를 정독하다 발견했다. 스트리밍 음성(RT3)은 messageId 를
**클라이언트가 쿼리로 보낸다**:
wss://stack-up.shop/realtime/sessions/{id}/audio?messageId=N
체인 어디에도 messageId 소유 검증이 없었다:
· RealTime router — 토큰의 SESSION 범위가 URL 세션 id 와 맞는지만 확인
· ws_audio — messageId 를 그대로 AI 로 전달
· AI — DB 를 모른다(설계상 내부 통신 전용)
· Core — findById 만 하고 세션을 안 봄 ← 여기서 막아야 했다
따라서 계정만 있으면 자기 세션 토큰으로 **남의 면접 답변을 덮어쓸 수 있었다.**
피해자 메시지의 전사가 공격자 음성으로 교체되고, 음성 분석 행이 삽입되며,
피해자 세션 채널로 SSE 까지 나간다. 메시지 id 는 연속 정수라 대상 지목도 쉽다.
Core 가 마지막 방어선이므로 여기서 세션 불일치 콜백을 드롭한다. 배치 업로드
경로는 placeholder 를 서버가 만들어 영향이 없었다 — 스트리밍 경로 전용 문제다.
같은 지점에 늦은 콜백 가드도 넣었다. StaleTranscriptionSweeper 가 FAILED 로
확정해 턴을 푼 뒤 늦은 콜백이 도착하면 옛 답변이 COMPLETED 로 되살아나 같은
턴에 답변이 둘이 된다(꼬리질문 applyFollowup 이 같은 이유로 막아 둔 것과 동일).
status != CREATED 면 드롭한다.
테스트 2건 추가 — 타 세션 메시지 지목 시 내용·상태 불변 + 이벤트 미발행,
확정된 메시지에 늦은 콜백이 와도 되살아나지 않음.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔴 인증된 사용자가 타인의 면접 답변을 덮어쓸 수 있었습니다
RealTime Go 코드를 정독하다 발견했습니다.
취약점 체인
스트리밍 음성(RT3)은
messageId를 클라이언트가 쿼리로 보냅니다:체인 어디에도
messageId소유 검증이 없었습니다:router.goSESSION범위가 URL 세션 id 와 맞는지만 확인ws_audio.gomessageId를 그대로 AI 로 전달 (buildAIStreamURL)voice_stream.pyVoiceCallbackServicefindById(interviewMessageId)— 세션을 안 봄 ← 막아야 할 곳공격 경로
stream-begin없이wss://…/realtime/sessions/A/audio?messageId=<피해자_메시지_id>로 연결callback.voice{sessionId: A, interviewMessageId: 피해자_메시지}발행결과: 피해자의 면접 답변 내용 교체,
message_voice_analyses행 삽입, 피해자 세션 채널로 SSE 발행. 메시지 id 는 연속 정수라 대상 지목도 쉽습니다.수정
Core 가 마지막 방어선이므로 여기서 막습니다 — 콜백의
sessionId와 메시지의 실제 세션이 다르면 드롭.배치 업로드 경로는 서버가 placeholder 를 만들어 영향이 없었습니다. 스트리밍 경로 전용 문제입니다.
덤 — 늦은 콜백 가드
같은 지점에서 별개 문제도 막았습니다.
StaleTranscriptionSweeper가 FAILED 로 확정해 턴을 푼 뒤 늦은 콜백이 도착하면, 옛 답변이COMPLETED로 되살아나 같은 턴에 답변이 둘이 됩니다. 꼬리질문applyFollowup이 같은 이유로 막아 둔 것과 동일한 사유라,status != CREATED면 드롭합니다.테스트
apply_dropsCallbackWhenMessageBelongsToAnotherSession— 타 세션 메시지 지목 시 내용·상태 불변, 분석 행 미저장, 이벤트 미발행apply_dropsLateCallbackForAlreadySettledMessage— 확정된 메시지가 되살아나지 않음./gradlew test308건 — 실패는 Testcontainers 5개 클래스뿐(로컬 Docker 없음, CI 검증)영향 범위
stream-begin이 만든 placeholder 는 항상 같은 세션·CREATED상태입니다리뷰어 체크포인트
CREATED이고 배치·스트리밍 모두 콜백 전까지 그 상태를 유지합니다.🤖 Generated with Claude Code