【제50회】 초대 URL에서 비밀번호가 유출되는 대위기! 주소창의 &key= 완전 제거 및 메모리 내 관리
요약
WebRTC P2P 채팅 기능에서 초대 URL에 비밀번호가 평문으로 주소창에 노출되는 심각한 보안 취약점이 발견되었습니다. 이로 인해 화면 공유나 브라우저 기록을 통해 정보 유출 위험이 높았습니다. 이에 따라, 기밀 정보를 URL에서 완전히 제거하고, 비밀번호를 메모리 내 변수만 관리하도록 시스템 설계를 근본적으로 개선했습니다.
핵심 포인트
- 초대 URL에 포함된 비밀번호는 주소창에 평문으로 노출되는 심각한 보안 위험이 있습니다.
- 비밀번호(암호화 키)는 JavaScript 인스턴스의 메모리 변수 내에서만 관리해야 합니다.
- 주소창은 룸 ID(`p2p-room=...`)로 정규화 및 치환하여 민감 정보를 제거했습니다.
- `window.history.replaceState`를 사용하여 뒤로 가기 기록에 과거의 파라미터가 남지 않도록 처리했습니다.
안녕하세요! '츠바이 채널(2ch.biz)' 개발 기록 제50회입니다.
WebRTC를 활용한 P2P 암호화 채팅은 지난번 'IndexedDB 비동기 데드락 해소' 작업을 거쳐, 스마트폰과 PC 모두에서 대기 시간 없이 초고속으로 입장할 수 있게 되었습니다.
하지만 실제로 사용자들끼리 사용하기 시작하자마자, 보안 엔지니어 독자로부터 식은땀이 날 정도의 지적이 도착했습니다.
"초대 URL을 복사하여 채팅에 들어간 후,
브라우저 주소창에 비밀번호가 평문 그대로 남아 있습니다!
화면 공유를 하거나, 채팅 중인 URL을 다른 사람에게 그대로 재공유할 경우, 비밀번호가 전 세계에 새어 나갑니다!"
'원클릭으로 비밀번호까지 자동 입력되어 입장하면 편리하겠지'라고 생각하며 구현했던 친절한 기능이, 심각한 보안 홀(평문 유출)이 되어버린 것입니다.
이번 시간에는 **"URL에 기밀 정보를 포함해서는 안 된다"**는 웹 보안의 철칙으로 돌아가, 주소창 및 초대 URL에서 &key= 파라미터를 완전히 제거하고, 비밀번호를 안전하게 메모리 내에 보관하는 개정 전반을 전달해 드립니다.
원래 구현에서는 친구들을 P2P 채팅에 초대하기 쉽도록 다음과 같은 형식의 URL을 발행했습니다.
URL의 해시 프래그먼트(# 이후)는 HTTP 요청 헤더(Referer)로 외부 서버에는 전송되지 않기 때문에, '해시라면 안전하겠지'라고 방심하고 있었습니다.
하지만 실제 클라이언트 사이드 운영 환경에서 URL에 비밀번호를 남기는 것은 다음과 같은 막대한 유출 위험을 안고 있었습니다.
- 주소창 재복사: 채팅 중에 "이 방의 URL 좀 줘"라는 요청을 받은 참여자가 주소창을 그대로 복사하여 보내버리는 경우.
- 화면 공유/방송 노출: Discord나 Zoom 화면 공유, YouTube 라이브 방송 등에서 주소창이 비치는 순간 비밀번호가 유출되는 경우.
- 브라우저 기록에 저장: 공유 PC나 코인넷 카페의 브라우저 기록에 비밀번호가 포함된 URL이 그대로 남는 경우.
- 어깨 너머로 보기 (숄더 해킹): 카페나 사무실에서 뒤쪽에서 화면을 엿보았을 때, 주소창에 큰 글씨로 비밀번호가 보이는 경우.
안전성을 최우선으로 하기 위해, 다음과 같은 설계로 방침을 근본적으로 전환했습니다.
- 초대 URL에는 룸 ID만 포함: URL은
https://2ch.biz/#p2p-room=p08e200775f형식으로 하고, 비밀번호와 관련된 정보는 단 한 글자도 포함하지 않습니다. - 입장 후 주소창도 ID만 유지: 비밀번호가 포함된 상태로 입장했더라도, 브라우저의 주소창(
window.location.hash)을 즉시#p2p-room=${roomId}로 정규화 및 치환합니다. - 비밀번호(암호화 키)는 메모리 내 관리: 비밀번호는 JavaScript 인스턴스의 변수(
this.password) 안에서만 보관하고, URL이나 로컬 스토리지 같은 영구 영역에는 절대 평문으로 기록하지 않습니다.
비밀번호는 JavaScript 인스턴스의 변수(
호스트나 참여자가 화면상의 "📋 초대 URL 복사" 버튼을 누를 때 생성되는 URL에서, &key=를 완전히 제거했습니다.
// p2p_chat.js
copyInviteUrl() {
if (!this.roomId) {
...
사용자가 방 입장을 완료한 후크 함수인 onRoomEntered()에서, 주소창 URL을 룸 ID 단독으로 정제(sanitize)합니다.
// p2p_chat.js
onRoomEntered(roomId) {
this.roomId = roomId;
...
window.history.replaceState를 이용함으로써, 브라우저의 '뒤로 가기' 버튼 기록에 과거의 파라미터가 포함된 URL을 남기는 것 없이 주소창을 깨끗한 상태로 덮어쓰기 업데이트할 수 있습니다.
URL에서 &key=가 확실하게 제거되었는지, 단일 테스트 스크립트를 작성하여 검증했습니다.
// test_invite_url_clean.js
const assert = require('assert');
function generateInviteUrl(originPath, roomId, password) {
...
$ node test_invite_url_clean.js
생성된 URL: https://2ch.biz/#p2p-room=room_p08e200775f
✅ 모든 보안 어설션에 합격했습니다!
편의성을 추구한 나머지, "URL을 클릭하는 것만으로 모두가 입장할 수 있다면 좋겠다"라며 비밀번호를 쉽게 파라미터에 포함시켜 버린 습관.
하지만 URL은 **'공유되는 것', '화면에 표시되는 것', '이력에 남는 것'**을 전제로 하는 공개적인 식별자입니다.
비밀 정보가 URL에서 완전히 제거되고 안전한 인메모리(in-memory) 관리로 전환되면서, 사용자가 안심하고 화면 공유나 URL 전달을 할 수 있게 되었습니다.
자, URL이 깔끔해진 만큼, 다음으로 신경 쓰인 부분은 **'룸 ID의 시각적 형태(디자인)'**입니다.
현재 룸 ID는 room_p08e200775f처럼 room_이라는 접두사가 붙어 있습니다.
하지만 스마트폰의 작은 화면에서 복사하거나 수동으로 입력할 때, 이 room_이 은근히 방해가 되어 오타를 유발하는 원인이 되기도 했습니다.
"이 room_이라는 접두사는 애초에 정말 필요한 걸까...?"
실제 서비스: https://2ch.biz/
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기