블로그로 돌아가기

URL 단축 서비스의 소셜 엔지니어링 인시던트 대응과 방어 체계 구축

QuikURL ShortenerSecurityGoogle Safe BrowsingSupabaseIncident Response

Google Search Console에서 보안 경고가 도착했다

3월 7일, Google Search Console로부터 메일 한 통이 왔다.

에서 소셜 엔지니어링 콘텐츠가 감지되었습니다.

Google Search Console 보안 문제 알림

내용을 요약하면 이렇다:

  • Google 세이프 브라우징 시스템에 의해 사이트 일부 페이지가 해킹되었거나, 사용자를 속여서 악성 소프트웨어를 설치하거나 민감한 정보를 요구하도록 설계된 서드 파티 리소스를 포함하고 있는 것으로 확인됨
  • 사이트 방문자를 보호하기 위해 영향을 받은 페이지의 검색 순위가 하락
  • Chrome 등의 브라우저에서 사이트 방문 시 경고가 표시

URL 단축 서비스의 특성상, 누구든 단축 링크를 생성할 수 있다. 한 사용자가 eronsea라는 short code로 피싱 사이트를 가리키는 단축 URL을 만들었고, 이것이 quik.im 도메인에서 서빙되면서 도메인 전체에 경고가 붙은 것이다.


문제 확인

실제로 해당 URL로 접속하자 Chrome이 위험한 사이트 경고를 표시했다.

Chrome 위험한 사이트 경고 페이지

문제의 핵심은 단순했다. URL 생성 시점에 목적지가 악성인지 검증하는 장치가 없었다.


대응 1: 해당 URL 즉시 비활성화

가장 먼저 할 일은 해당 링크를 비활성화하는 것이다. Supabase DB에서 해당 단축 URL의 is_active 컬럼을 FALSE로 변경했다.

-- 해당 URL 확인
SELECT * FROM urls WHERE short_code = 'eronsea';

-- 비활성화 (삭제보다 안전 — 기록 보존)
UPDATE urls SET is_active = false WHERE short_code = 'eronsea';

삭제 대신 비활성화를 선택한 이유는 추후 분석을 위해 기록을 보존하기 위해서다.


대응 2: Google Safe Browsing API 연동

같은 문제가 반복되지 않도록, URL 생성 시 Google Safe Browsing API v4로 목적지 URL의 안전성을 검사하는 기능을 추가했다.

API Key 발급

Google Cloud Console에서 Safe Browsing API를 활성화하고 API Key를 발급받았다. 무료로 일 10,000건까지 사용 가능하다.

Google Cloud API 서비스에서 Safe Browsing API 활성화

발급받은 키를 환경변수에 등록:

GOOGLE_SAFE_BROWSING_API_KEY=발급받은_키

구현 구조

API Key가 클라이언트에 노출되지 않도록 서버 사이드 API Route를 거치는 구조로 설계했다.

사용자가 URL 단축 요청
  → lib/safe-browsing.ts (클라이언트)
    → /api/v1/url/safe-browsing (서버 사이드 API Route)
      → Google Safe Browsing API 조회
      → 위험 감지 시:
         1. 링크 생성 차단 + 에러 메시지 반환
         2. Slack 보안 채널에 즉시 알림 발송
      → 안전 확인 시:
         정상적으로 단축 링크 생성

검사 대상은 4가지 위협 유형이다:

  • SOCIAL_ENGINEERING — 피싱, 사기 페이지
  • MALWARE — 악성 소프트웨어 배포
  • UNWANTED_SOFTWARE — 애드웨어, 브라우저 하이재커
  • POTENTIALLY_HARMFUL_APPLICATION — 기기 보안을 위협하는 앱

API 장애 시에는 fail-open 정책을 적용했다. Safe Browsing API가 응답하지 않을 때 URL 생성을 차단하면 외부 서비스 장애가 핵심 기능의 가용성을 훼손한다. 대신 생성은 허용하되, Slack 알림과 관리자 모니터링으로 보완한다.


대응 3: is_blocked — 재활성화 방지

여기서 한 가지 문제가 남는다. is_activeFALSE로 바꿔도, 해당 링크를 만든 사용자가 대시보드에서 다시 활성화할 수 있다. is_active는 원래 사용자가 자유롭게 토글하는 값이기 때문이다.

이를 막기 위해 urls 테이블에 is_blocked 컬럼을 추가했다.

ALTER TABLE urls ADD COLUMN is_blocked BOOLEAN NOT NULL DEFAULT FALSE;

그리고 해당 URL에 대해 is_blocked = TRUE로 설정:

UPDATE urls SET is_blocked = true, is_active = false WHERE short_code = 'eronsea';

DB 트리거로 재활성화 차단

is_blockedTRUE인 URL은 어떤 경우에도 is_activeTRUE로 변경할 수 없도록 데이터베이스 트리거를 걸었다.

CREATE OR REPLACE FUNCTION prevent_blocked_url_modification()
RETURNS TRIGGER AS $$
BEGIN
  -- 차단된 URL의 활성화 시도 거부
  IF OLD.is_blocked = TRUE AND NEW.is_active = TRUE THEN
    RAISE EXCEPTION 'Cannot activate a blocked URL';
  END IF;

  -- 차단 해제는 admin 권한만 가능
  IF OLD.is_blocked = TRUE AND NEW.is_blocked = FALSE THEN
    IF NOT EXISTS (
      SELECT 1 FROM user_roles WHERE user_id = auth.uid() AND role = 'admin'
    ) THEN
      RAISE EXCEPTION 'Only administrators can unblock URLs';
    END IF;
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

핵심 설계:

  • is_active — 사용자가 자유롭게 토글하는 값
  • is_blocked — 관리자만 설정/해제 가능한 값
  • 보호 로직이 애플리케이션이 아닌 DB 트리거에 있으므로 클라이언트에서 어떤 요청을 보내든 우회 불가

대시보드에서는 차단된 링크에 빨간 "Blocked" 배지를 표시하고, 활성화 토글을 비활성화하여 사용자에게 상태를 명확히 전달한다.

차단된 링크의 대시보드 UI — Blocked 배지와 비활성화된 토글


대응 4: Google Search Console 검토 요청

모든 조치를 완료한 뒤 Google Search Console에서 검토를 요청했다.

  1. 보안 문제 페이지 → "검토 요청"
  2. 설명에 조치 내용 기재: "악성 단축 URL 비활성화 및 영구 차단, URL 생성 시 Safe Browsing API 검증 추가"
  3. 보통 1~3일 내에 경고가 해제된다

해결 완료: Google 검토 통과

3월 9일 오전 9시, 하루 뒤 바로 Google Search Console로부터 검토 완료 메일을 받았다.

Google Search Console 검토 완료 알림

Google에서 요청하신 보안 검토 내용을 접수하고 처리했습니다. Google 시스템에 따르면 quik.im에 더 이상 유해한 사이트 또는 다운로드 링크가 포함되어 있지 않습니다.

검토 요청 후 3일 만에 경고가 해제되었다. Chrome 브라우저의 위험 사이트 경고도 사라졌고, 검색 순위도 정상 복구되었다.


정리

URL 단축 서비스는 구조적으로 악용에 노출되어 있다. 이번 인시던트를 통해 3단계 방어 체계를 구축했다:

  1. 사전 검증 — Google Safe Browsing API로 생성 시점에 악성 URL 차단
  2. 실시간 알림 — 악성 URL 생성 시도 시 Slack 보안 알림
  3. 영구 차단is_blocked + DB 트리거로 관리자만 해제 가능한 차단

사용자가 입력하는 값을 그대로 외부에 노출하는 서비스라면, "입력 시점 검증"만으로는 부족하다. 사후에 발견된 문제를 관리자가 영구적으로 차단할 수 있는 장치, 그리고 그 차단을 사용자가 우회할 수 없도록 DB 레벨에서 강제하는 구조까지 갖춰야 한다.