주문 링크가 곧 열쇠: 무엇이 노출되는가
2026년 11월 3일 실제 100 USDT 주문을 만들고 쿠키와 인증 없이 다시 읽었습니다. 10개 필드, 만료 없음, 그리고 전체 URL을 받는 제3자 스크립트 하나. 주문 링크의 실체를 실측합니다.
2026년 11월 3일 10:03:11 UTC, 저희는 veilswap.io에서 실제 주문을 만들었습니다. 100 USDT(TRON) → XMR. 응답은 다섯 필드였습니다 — ID 4192151d-…, 입금 주소 TUTmcejZAXe2nGhoJ6CJ5Fz8qHn9QMuvTs, 0.13951129 XMR, 상태 waiting_deposit, 그리고 601초 뒤인 10:13:12 마감. 그 순간 이후의 모든 일은 하나의 문자열을 통해 일어납니다: https://veilswap.io/order/4192151d-04c6-4595-936c-48b15a4ad0c6. 계정도, 이메일도, 비밀번호도 없습니다. 링크 자체가 자격 증명이고, 이 글은 그 링크가 무엇을 누구에게 얼마나 오래 넘기는지를 다룹니다. (XMR→USDT 출구 루트)
이 설계는 의도적이고, 대가도 의도적입니다. 계정 없는 거래소는 로그인을 요구할 수 없으니 들고 다닐 수 있는 것을 줘야 합니다. 그게 URL입니다. orders 테이블은 20개 컬럼이고 email·phone·name이 없습니다. 주문을 복구할 두 번째 경로도, 당신과 구매를 연결할 두 번째 식별자도 없다는 뜻입니다. 거래는 이렇습니다 — 당신과 스왑 사이의 연결고리를 하나 줄이고, 직접 지켜야 할 비밀을 하나 늘립니다. 강도는 진짜입니다. 운영 DB의 143건 전부가 uuid v4이고 122비트 무작위입니다. 초당 10억 회 추측이면 기대값은 약 1.7×10^20년입니다. 공격은 추측이 아니라 휴대 쪽에 있습니다.
링크가 실제로 넘기는 것. 쿠키도 Authorization도 없이 GET /api/order/<id>를 호출하면 200과 10개 필드가 돌아옵니다: depositAddress, depositExtraId, fromAmount, fromAsset, toAsset, toAmount, status, providerStatus, validUntil, id. 헤더는 cache-control: no-store. to_address와 refund_address는 반환하지 않고 서버에 남습니다. 종료 상태가 되면 payinHash와 payoutHash도 반환하지 않습니다 — 결제 완료 주문에서 확인했고 두 키가 사라져 있었습니다. 즉 유출된 링크는 무엇을·얼마를·어느 입금 주소로·어디까지 진행됐는지를 넘기고, 최종 목적지와 정산 후 해시는 넘기지 않습니다. (모네로 확인 대기)
만료되는 것은 결제 창뿐이고 링크가 아닙니다. 601초는 입금 창입니다. 10:04 UTC에 09:24:55에 생성되어 이미 환불된 주문을 다시 읽었더니 생성 39분 뒤에도 입금 주소와 validUntil이 그대로 돌아왔습니다. 코드도 같습니다. 주문 라우트는 GET만 내보내고 PATCH·PUT·DELETE가 없어 교체도, 폐기도, '주문 삭제'도 없습니다. 한 가지 사실이 더 예리하게 만듭니다. 143건의 주문에 입금 주소는 134개뿐이고, 그중 두 주소가 각각 7건과 4건을 맡았습니다. 링크가 그런 주소를 가리키면 사적인 주소가 아니라 여러 주문이 지나간 공유 주소를 가리키는 포인터입니다. 이 재사용은 풀 쪽 할당이고 링크의 결함이 아니지만, 링크가 드러내는 내용을 바꿉니다 — 내 주문만이 아니라 여러 주문이 통과한 주소를 가리킵니다.
로컬에 사본 세 개가 남고, 모두 저희 손 밖입니다. 브라우저 기록은 URL을 보관하고, 동기화는 로그인된 모든 기기로 복사합니다. 주문 페이지 스크린샷에는 보통 입금 주소를 인코딩한 QR과 주소창이 함께 찍힙니다. 스왑 위젯은 vs_active_order를 localStorage에 쓰면서 주문 ID, 입금 주소, extra id, 두 금액, 두 자산 id를 담고, 종료 상태를 받을 때만 지웁니다. 공용 노트북에서 결제하지 않고 탭을 닫으면 어제의 입금 주소가 그 프로필에 남습니다. sessionStorage의 vs_order_session은 /exchange가 옛 주문을 자동 복원할지만 정하고, 기록을 남길지는 정하지 않습니다. 이 세 사본은 저희에게 보이지 않고 저희 쪽에서 폐기할 수도 없습니다. 그래서 링크는 '사이트가 지켜 주는 것'이 아니라 '당신이 들고 다니는 것'으로 다뤄야 합니다.
페이지 자체가 URL을 보는 제3자를 하나 싣습니다. /order/<id> HTML은 32,795바이트이고 noindex, nofollow robots 메타를 가지며 쿠키를 설정하지 않고 cache-control: no-store를 반환합니다. 01a0d6f6-4444-7a6d-a45f-6375a01eba8f.spst2.com에서 ustat.js(3,560바이트)를 불러옵니다. 내용을 보면 location.href를 url 필드에 넣고 referrer, 화면 크기, 언어, canvas 지문을 모아 /api/report로 POST합니다. 교환 페이지에서 도는 Clarity는 주문 페이지에 0곳(이미 제외됨)이지만, 이 작은 계측 스크립트는 완전한 주문 URL을 받습니다. referrer-policy: strict-origin-when-cross-origin은 교차 사이트 Referer를 오리진까지만 줄일 뿐, 직접 불러온 스크립트가 location을 읽는 것은 막지 못합니다. 이것이 저희 인프라를 완전히 떠나는 유일한 공개 경로입니다.
저희 쪽에서는 링크가 로그 한 줄로 떨어집니다. nginx 형식은 remote_addr, CF-Connecting-IP에서 온 실제 클라이언트 IP, 시각, 전체 UUID가 들어간 요청 줄, 상태, 바이트, UA를 기록합니다. 로그는 일 단위로 로테이션되고 14세대를 보관하므로 언제나 약 15일치 'IP↔주문' 대응표가 존재합니다. 그 창에서 주문 페이지 열람 83회, API 폴링 738회, 149개 주문 ID를 세었습니다. 서로 다른 주문 경로 61개 중 6개가 둘 이상의 클라이언트 IP에서 열렸고, API 쪽에서는 8건이 둘 이상 주소(최대 3개)에서 폴링됐습니다. bot UA가 4개 경로를 가져갔고 그중 3개는 사람 UA도 함께 있었습니다. 링크를 채팅에 붙였을 때 서버에서 보이는 모습입니다 — 미리보기 봇이 수신자가 클릭하기 전에, 몇 분 안에 옵니다. 404 쪽은 120건·82경로·9 IP이고, 가장 바쁜 송신원은 /api/order/1과 /api/order/callback을 때렸습니다. 범용 스캐너이지 열거가 아닙니다. 성공한 무차별 대입은 관측되지 않았고 122비트에서는 일어날 수도 없습니다.
링크를 가진 사람이 할 수 있는 것과 없는 것. 할 수 없는 것: 수취 주소 변경, 환불 주소 변경, 주문 취소, 자금 이동. 쓰기 엔드포인트가 없고 두 주소는 생성 시 고정됩니다. 입금 주소로 보낸 코인은 그저 자기 주문을 결제할 뿐입니다. 할 수 있는 것: 금액과 페어 읽기, 입금 주소를 온체인에서 감시, 상태를 원하는 만큼 폴링. GET을 40회 연속 호출해 40번 모두 200이었습니다. 읽기에 앱 계층 레이트 리밋이 없기 때문입니다(견적에는 있고, 주문 생성은 시간당 IP당 6건). 현실적 피해는 공개와 사회공학입니다. 주문 ID, 금액, '아직 미결제'를 아는 사람은 내부자만 알 법한 정보를 붙여 지원팀을 사칭할 수 있고, 입금 주소를 지켜보며 입금 순간을 볼 수도 있습니다. 후자가 더 조용합니다. 감시되는 입금 주소는 당신의 타이밍을 드러내고, 타이밍만으로 그 스왑을 직전의 일과 연결하기에 충분한 경우가 많습니다.
비용 없는 습관. 1) URL 전체를 채팅, 지원 티켓, 스크린샷에 붙이지 마십시오. 보낼 것이면 입금 주소와 금액만, 또는 주소창을 잘라내십시오. 2) 보관해야 한다면 비밀번호 관리자 항목으로(날짜와 페어로 이름 붙여) 두고, 브라우저 기록에만 두지 마십시오. 3) paid, expired, refunded가 되면 페이지를 한 번 열어 vs_active_order를 지우게 하고, 공용 기기면 사이트 데이터도 지우십시오. 4) 남의 컴퓨터에서는 프라이빗 창을 쓰십시오. 5) 도움이 필요하면 전체 ID를 넘긴다고 생각하십시오. 지원팀은 ID 없이 주문을 찾을 수 없습니다. 비밀이 나가는 유일한 지점이니 비밀번호를 보낼 만한 경로를 쓰십시오. 6) 틀을 바로 잡으십시오. 링크는 '읽기 권한'이지 지갑이 아닙니다. 잃어도 자금은 그대로이고(결제된 주문은 그대로 정산됩니다), 줘도 자금은 움직이지 않습니다(주소를 바꿀 수 없으니까요). 두 가지는 이름을 붙일 가치가 있습니다. 계측 스크립트는 주문 페이지에서 돌지 않아야 합니다(전체 URL을 제3자에게 넘깁니다). 입금 주소 재사용은 페이지에 표시되어야 합니다(현재 143건이 134개 주소를 공유합니다).