오늘 Hacker News에서 1800점을 넘기며 압도적으로 올라온 글이 하나 있다. 제목은 "The newest Instagram exploit is the goofiest I've seen". 직역하면 "내가 본 인스타그램 익스플로잇 중 제일 어이없는 거"다. 보안 관련 글이 HN 1등을 먹는 경우는 흔하지만, 점수가 이 정도로 쏠린다는 건 보통 "허무할 만큼 단순한 실수"일 때다.
나는 인프라/DevOps를 주로 하지만, 사내 서비스에 소셜 로그인 붙이고 OAuth 연동 디버깅하는 건 결국 우리 몫으로 떨어진다. 그래서 이런 종류의 사고는 남의 일이 아니다. 이 글에서는 해당 사례를 빌미로, 소셜 로그인과 계정 연동(account linking)에서 한국 서비스도 똑같이 밟는 함정들을 정리해보겠다.
왜 지금 화제인가
원문 글의 핵심 주장은 "Meta 쪽 계정 연동 흐름에서 어이없을 정도로 단순한 검증 누락으로 계정 탈취가 가능했다"는 것이다. 정확한 기술적 디테일은 원문(공식 CVE나 Meta의 공식 패치 노트가 아니라 개인 리서처 블로그)을 직접 확인하길 권한다. 다만 이런 류의 사고가 반복되는 패턴은 거의 정해져 있다.
핵심은 이거다. 소셜 로그인은 "로그인"이 아니라 "신원 증명을 외부에 위임하는 것"인데, 많은 서비스가 이걸 단순 로그인처럼 다룬다. 그리고 계정을 기존 계정에 "연결(link)"하는 순간, 검증 한 줄 빼먹으면 그대로 남의 계정에 내 소셜 계정을 붙여버릴 수 있다.
핵심: 계정 연동이 왜 위험한가
가장 흔한 시나리오를 보자. 우리 서비스가 이메일/비번 가입도 받고, 구글/카카오 소셜 로그인도 받는다고 하자. 사용자가 둘 다 쓸 수 있게 하려면 "같은 이메일이면 같은 계정으로 묶는다"는 로직이 들어간다. 바로 여기가 사고의 진원지다.
# 위험한 의사코드
def social_callback(provider_profile):
email = provider_profile["email"]
user = User.find_by(email=email)
if user:
# 이미 그 이메일로 가입한 계정이 있으니 그냥 묶어버린다
link_social_account(user, provider_profile)
return login(user)
...
문제는 provider_profile["email"]이 "검증된 이메일"이라는 보장이 없다는 점이다. OIDC를 쓴다면 ID 토큰에 email_verified 클레임이 있는데, 이걸 안 보고 그냥 email만 믿으면 끝장이다. 공격자가 피해자의 이메일을 자기 소셜 계정에 등록(미검증 상태로)해두고 그 계정으로 콜백을 태우면, 우리 서버는 "아 이 이메일 주인이네" 하고 피해자의 기존 계정에 공격자의 소셜 로그인을 붙여준다.
비유하자면 이렇다. 호텔 프런트에서 "302호 손님이세요?"라고 물었더니 "네"라고만 하면 카드키를 내주는 거다. 신분증(검증된 이메일, 토큰 서명)을 안 본다. 인스타그램 사례의 "어이없음"도 결국 이 결의 검증 누락으로 보인다.
제대로 하려면 최소한 이렇게 가야 한다.
def social_callback(provider_profile):
# 1. 토큰 서명/aud/iss/exp 먼저 검증 (라이브러리에 맡기되 검증 켜졌는지 확인)
# 2. 이메일이 provider 측에서 검증된 건지 확인
if not provider_profile.get("email_verified"):
# 미검증 이메일은 자동 연동 금지 → 별도 확인 절차로
return require_manual_verification()
email = provider_profile["email"]
user = User.find_by(email=email)
if user:
# 기존 계정에 묶을 때는 "그 계정 소유자의 명시적 동의"를 받는다
if not user_confirmed_linking(user):
return ask_for_login_then_link()
link_social_account(user, provider_profile)
...
두 가지가 핵심이다. (1) provider가 이메일을 검증했는지 확인, (2) 기존 계정에 붙일 때는 그 계정 주인이 직접 로그인/동의하게 만들기. 둘 중 하나만 빠져도 탈취 경로가 열린다.
실무 관점: 어디서 자주 터지나
1. "이메일로 매칭"이라는 편의 기능이 제일 위험하다. UX 팀은 "같은 이메일이면 자동으로 묶어주세요, 사용자 헷갈려요"라고 요구한다. 기분은 이해하지만, 자동 연동은 반드시 검증된 이메일 + 명시적 동의 조건이 붙어야 한다. 나는 사내 리뷰에서 "auto-link" PR이 올라오면 무조건 이 두 가지를 묻는다.
2. 라이브러리가 다 해줄 거라는 착각. NextAuth(Auth.js), Passport, Spring Security 같은 라이브러리들이 OAuth 흐름을 많이 추상화해준다. 그런데 account linking 정책은 라이브러리마다 기본값이 다르다. 예를 들어 NextAuth는 과거에 동일 이메일 자동 연동을 막아두는 게 기본이었고(allowDangerousEmailAccountLinking 같은 옵션명에서 위험성을 짐작할 수 있다), 이걸 편하다고 켜는 순간 위 시나리오에 노출된다. 옵션명에 dangerous가 붙어 있으면 이유가 있다.
3. ID 토큰 검증을 "라이브러리가 알아서"라고 믿는 것. aud(audience), iss(issuer), exp(만료), nonce 검증이 실제로 켜져 있는지는 직접 확인해야 한다. 특히 access token을 ID token처럼 쓰거나, userinfo 엔드포인트 응답을 검증 없이 신뢰하는 코드가 의외로 많다. access token은 "이 사람이 누구다"를 증명하는 용도가 아니라 "이 리소스에 접근해도 된다"는 용도다. 신원 증명은 ID 토큰(OIDC)으로 해야 한다.
4. 한국 특화 함정 - 카카오/네이버. 국내 소셜 로그인은 표준 OIDC를 완전히 따르지 않거나, 이메일을 선택 동의 항목으로 받게 되어 있는 경우가 많다. 그래서 이메일이 null로 오거나, 사용자가 동의를 안 해서 이메일이 없는 케이스를 처리해야 한다. 이메일 기반 매칭 전략 자체가 위태로워지는 거다. 이럴 땐 provider별 고유 ID(예: 카카오 회원번호)를 기준으로 연동 관리하고, 이메일은 보조 정보로만 쓰는 게 안전하다. 공식 문서에서 각 동의 항목의 보장 여부를 반드시 확인하자.
대안/완화책. 자동 연동을 아예 막고, 계정 통합은 "이미 로그인한 상태에서 마이페이지 - 소셜 계정 연결" 흐름으로만 허용하는 게 가장 안전하다. UX는 조금 불편해지지만, 탈취 경로 하나를 통째로 닫는다. 그리고 계정 연동/해제 같은 민감 이벤트는 반드시 감사 로그를 남기고, 가능하면 이메일 알림까지 보내자. 사고가 나도 빨리 탐지된다.
정리
한 줄 요약: 소셜 로그인은 인증 위임이지 인증 그 자체가 아니다. 이메일만 믿고 자동으로 계정을 묶는 순간 탈취 경로가 열린다.
- 소셜/이메일 로그인을 같이 쓰는 서비스를 운영한다면 → 지금 당장 자동 연동 로직과 email_verified 검증 여부를 확인하라.
- NextAuth/Passport/Spring Security 같은 라이브러리를 쓴다면 → account linking 기본값과 토큰 검증 옵션이 켜졌는지 직접 확인하라.
- 국내 소셜(카카오/네이버)을 붙인다면 → 이메일 매칭에 의존하지 말고 provider 고유 ID 기준으로 가라.
이번 인스타그램 사례의 정확한 기술 디테일은 개인 리서처 블로그 기반이라 단정하긴 어렵다. 다만 "어이없을 만큼 단순한 검증 누락"이라는 평가는, 위에서 본 패턴 중 하나일 가능성이 높아 보인다. 우리 코드에 같은 구멍이 없는지 점검하는 계기로 삼으면 충분히 값어치 있다.
참고 자료
'Tech_News' 카테고리의 다른 글
| github.dev에서 한 번 클릭으로 GitHub 토큰이 털린다 - VSCode 웹뷰 버그 뜯어보기 (0) | 2026.06.04 |
|---|---|
| Cloudflare Turnstile가 WebGL 핑거프린팅을 요구하기 시작했다 — 봇 차단의 대가는 누가 치르나 (0) | 2026.06.02 |
| OpenBSD 팀이 만든 openrsync, 왜 지금 다시 볼 만한가 (0) | 2026.06.01 |
| SQLite로 Durable Workflow 만들기: Temporal 안 쓰고 워크플로 엔진 자가 구축하기 (1) | 2026.05.30 |
| Postgres 하나로 Durable Workflow 짜기 — DBOS 방식이 실무에서 먹히는 이유 (0) | 2026.05.29 |
