[Review] TikTok 1-Click Account Hijacking 분석 (CVE-2022-28799)

목차


들어가며

글로벌 숏폼 모바일 비디오 플랫폼인 TikTok은 구글 플레이스토어 기준 동아시아용 앱과 글로벌 앱을 합쳐 총 15억 건 이상의 다운로드를 기록하고 있다.

이처럼 전 세계인이 일상적으로 사용하는 앱이지만 단 한 번의 링크 클릭만으로 사용자의 인지 없이 계정이 완전히 탈취되는 취약점(CVE-2022-28799)이 마이크로소프트 보안 연구팀에 의해 발견되었다.

이 취약점이 성공적으로 트리거되면 공격자는 피해자의 계정을 장악하여 다른 사용자에게 메시지를 전송하고, 피해자의 명의로 임의의 비디오를 업로드할 수 있게 된다.

이번 글에서는 안드로이드 앱 내부의 검증 로직 우회와 웹 인터페이스의 취약점이 어떻게 연쇄적으로 결합되어 하나의 거대한 익스플로잇 체인을 형성하는지 구체적인 파이프라인을 분석한다.

본격적인 PoC 분석에 앞서, 익스플로잇 과정을 충분히 이해할 수 있도록 공격의 발판이 된 안드로이드 프레임워크의 핵심 기술들을 먼저 짚고 넘어간다.


취약점 분석을 위한 사전 개념

이 공격은 앱의 Deeplink 처리 과정에서 시작되어, 최종적으로 WebView의 권한이 탈취되며 완성된다.
공격의 페이로드를 이해하기 위해 다음 두 가지 개념을 명확히 이해해야 한다.

WebView와 JavaScript Interface

안드로이드의 WebView 컴포넌트는 앱 내부에서 자체적으로 웹 페이지를 띄워주는 역할을 한다.
이때 개발자는 addJavascriptInterface API를 사용하여 앱(Java)과 웹(JavaScript) 사이의 Bridge를 생성할 수 있다.
즉, 로드된 웹 페이지의 JavaScript 코드가 앱 내부에 구현된 특정 Java 클래스의 메서드를 직접 호출할 수 있게 되는 것이다.

예를 들어, 웹 페이지 내에서 injectObject.foo() 라는 자바스크립트 코드를 실행하면, 앱 내부에 구현된 foo() Java 메서드가 동작하게 된다.
안드로이드 API level 18부터는 보안을 위해 @JavascriptInterface 어노테이션이 명시된 메서드만 호출할 수 있도록 제한하고 있다.
하지만 이 기능은 양날의 검이다.
만약 공격자가 Webview에 신뢰할 수 없는 임의의 웹 콘텐츠를 로드할 수 있게 된다면, JavaScript Interface Injection을 통해 앱의 권한으로 다양한 내부 기능을 마음대로 실행할 수 있게 되며, 이는 데이터 유출이나 임의 코드 실행 등의 치명적인 결과로 이어진다.

Deeplink와 Internal Scheme

Deep Link는 특정 Scheme와 호스트로 구성되어, 사용자를 모바일 앱 내의 특정 Activity로 직접 이동시키는 특수한 하이퍼링크다.
외부에서 딥링크를 호출하기 위해서는 앱의 안드로이드 Manifest에 Intent Filter가 명시되어 있어야 한다.

Intent Filter 사용 예시 코드는 다음과 같다.

<activity
    android:name="com.example.android.GizmosActivity"
    android:label="@string/title_gizmos" >
    <intent-filter android:label="@string/filter_view_http_gizmos">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Accepts URIs that begin with "http://www.example.com/gizmos” -->
        <data android:scheme="http"
              android:host="www.example.com"
              android:pathPrefix="/gizmos" />
        <!-- note that the leading "/" is required for pathPrefix-->
    </intent-filter>
    <intent-filter android:label="@string/filter_view_example_gizmos">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Accepts URIs that begin with "example://gizmos” -->
        <data android:scheme="example"
              android:host="gizmos" />
    </intent-filter>
</activity>

특히 틱톡 앱은 사용자의 편의성을 위해 autoVerify 속성을 이용한 안드로이드 앱 링크 기능을 사용한다. 이 속성이 적용된 m.tiktok.com 과 같은 도메인 링크를 클릭하면, 사용자는 어떤 앱으로 열지 묻는 알림을 거치지 않고 즉시 틱톡 앱이 실행되며 해당 컴포넌트로 라우팅된다.

하지만 모든 딥링크가 외부에 노출된 것은 아니다.
앱 내부의 컴포넌트끼리 데이터를 교환하기 위해 사용하는 Internal Deeplink도 존재한다.
외부 브라우저 등에서 이러한 Internal Deeplink를 직접 호출하려고 시도하면, 안드로이드 시스템은 적절한 핸들러를 찾지 못해 “unable to resolve Intent” 에러를 반환하며 접근을 차단한다.


PoC.1 Entry Point 노출 및 Deeplink 검증 우회

앞서 살펴본 배경지식들이 실제 틱톡 서비스에서 어떻게 취약점으로 발현되었는지, 첫 번째 공격 파이프라인을 단계별로 분석해 본다.
이 단계의 핵심 목표는 외부에서 접근할 수 없는 틱톡 앱 내부의 웹뷰를 강제로 실행시키고, 도메인 필터링 로직을 우회하여 공격자의 악성 서버를 띄우는 것이다.

Exported Deeplink를 통한 내부 Scheme 트리거

틱톡 안드로이드 앱은 수많은 딥링크 스킴을 사용하며, 이 중 일부는 Manifest에 노출되어 있고 일부는 내부 컴포넌트 간 통신에만 사용된다. 공격의 첫 Entry Point가 된 곳은 Manifest를 통해 Exported되어 있던 https://m.tiktok[.]com/redirect 딥링크이다.
이 딥링크는 특정 클래스에 의해 처리되며, 쿼리 파라미터를 통해 앱 내의 다양한 컴포넌트로 URI를 리다이렉트하는 역할을 수행한다.

공격자는 바로 이 기능을 악용했다.
외부에서 직접 호출할 경우 안드로이드 시스템에 의해 차단되는 Internal Scheme을 redirect 딥링크의 파라미터로 전달한 것이다.
그 결과, 공격자는 외부에 노출되지 않은 내부 액티비티(예: CrossPlatformActivity)를 외부망에서 강제로 호출하여 앱의 공격 표면을 크게 넓힐 수 있었다.
공격자가 구성한 페이로드의 형태는 내부 스킴인 [redacted-internal-scheme]://webview?url=을 트리거하여 웹뷰를 실행하는 구조를 가진다.

서버 파라미터 조작 및 필터 우회

내부 웹뷰를 강제로 띄우는 데는 성공했지만, 공격자의 악성 사이트를 무조건 로드할 수 있는 것은 아니었다.
틱톡 앱은 webview?url= 파라미터를 통해 전달된 URL이 신뢰할 수 있는 호스트인지 확인하기 위해 자체적인 필터링을 수행하고 있었다.
예를 들어, https://www.tiktok[.]com과 같은 정상 도메인은 렌더링되지만, https://www.example[.]com과 같은 신뢰할 수 없는 도메인을 삽입하면 필터에 의해 로드가 거부된다.

분석 결과, 이 필터링 판단은 클라이언트 단이 아닌 특정 HTTP GET 요청에 대한 서버 측의 응답을 기반으로 이루어지고 있었다.
하지만 공격자는 정적 분석을 통해 딥링크에 두 개의 특정 파라미터를 추가로 덧붙임으로써 이 서버 사이드 검증 로직을 우회할 수 있음을 발견해 낸다.

이러한 필터 우회의 결과로 틱톡 앱의 CrossPlatformActivity에 부착된 Webview는 공격자의 웹사이트를 성공적으로 로드하게 된다.

JavaScript Bridge 장악

가장 치명적인 문제는 이 Webview의 권한이었다. 앞서 사전 개념에서 언급한 addJavascriptInterface 기능이 이 웹뷰에서 과도하게 허용되어 있었다.

필터링 우회를 통해 로드된 공격자의 웹사이트는 틱톡 앱이 생성한 JavaScript Bridge 인스턴스에 완전히 접근할 수 있는 권한을 얻게 된다.
이는 곧 공격자의 웹사이트 내에 심어진 자바스크립트 코드가 틱톡 앱의 내부 패키지([redacted].bridge.*)에 구현된 70개 이상의 Java 메서드들을 마음대로 호출할 수 있는 상태가 되었음을 의미한다.


PoC.2 Account takeover

앞선 취약점들을 통해 공격자는 틱톡 앱 내부 웹뷰의 권한을 획득하는 데 성공했다.
이제 공격자의 목표는 획득한 JavaScript Bridge 제어권을 활용해 피해자의 세션 권한으로 API를 호출하고, 계정을 탈취하는 것이다.

Exposed Functionality 접근

틱톡 안드로이드 앱의 Webview에 로드된 공격자의 웹페이지는 이제 [redacted].bridge.* 패키지 하위에 구현된 70개 이상의 Java 메서드에 접근할 수 있게 되었다.
이 메서드들 중에는 사용자의 프라이빗 비디오나 프로필 설정과 같은 민감한 데이터를 조회하고 수정할 수 있는 기능이 포함되어 있었다.

가장 치명적인 점은 이 메서드들을 통해 매개변수로 전달된 임의의 URL로 인증된 HTTP 요청을 수행할 수 있다는 것이었다.
메서드는 JSON 문자열 형태의 파라미터를 받아 POST 요청의 Body를 구성하고, HTTP 헤더를 포함한 결과를 자바스크립트 콜백으로 반환하는 구조를 띄고 있었다.
즉, 인증된 요청을 수행할 수 있는 메서드 중 단 하나만 통제하더라도 공격자는 틱톡 사용자의 계정을 탈취할 수 있는 상태가 된 것이다.

토큰 탈취 및 프로필 변조

이제 공격자는 발견한 취약점들을 엮어 완성된 악성 링크를 피해자에게 전송한다.
피해자가 이 링크를 클릭했을 때 발생하는 익스플로잇 체인 흐름은 다음과 같다.

먼저 피해자가 링크를 클릭하면 틱톡 앱의 딥링크 필터링 우회 취약점이 트리거되며, 앱 내부의 웹뷰가 공격자의 서버인 https://www.attacker[.]com/poc를 로드한다.

이후 공격자의 서버는 JavaScript Bridge에 접근할 수 있는 악성 자바스크립트 코드가 담긴 HTML을 반환한다.
이 스크립트는 앱 내부의 Java 메서드를 호출하여 비디오 업로드 인증 토큰을 요청하고, 브라우저의 내장 객체인 XMLHttpRequest를 통해 획득한 토큰과 요청 헤더를 공격자의 서버로 빼돌린다.

image.pngimage.png

마지막으로 공격자의 스크립트는 피해자의 프로필 소개 글을 변경하는 API를 호출하여, 그 내용을 "!! SECURITY BREACH !!"로 변조한다.

image.png

피해자는 단순히 링크를 클릭했을 뿐이지만, 백그라운드에서는 위와 같은 과정이 처리되며 계정 권한이 공격자에게 넘어가게 된다.


대응 방안

지금까지 살펴본 CVE-2022-28799 역시 거창한 제로데이 취약점이 아닌, 딥링크 처리와 웹뷰의 자바스크립트 인터페이스 허용이라는 애플리케이션 개발 단계에서의 설정 및 검증 누락에서 기인했다.
틱톡 측은 리포트를 받은 후 즉각적으로 패치를 릴리즈했지만, 하이브리드 앱 환경에서 근본적인 방어를 위해서는 다음과 같은 아키텍처 관점의 보안이 요구된다.

Trusted Domain 화이트리스트 검증

웹뷰에 외부 웹 콘텐츠를 로드해야 할 경우, 신뢰할 수 있는 화이트리스트를 엄격하게 적용해야 한다.
이때 도메인을 검증하는 과정에서 부분 문자열 비교 방식을 사용해서는 안 되며, 만료된 도메인을 공격자가 탈취하여 악용하지 못하도록 화이트리스트 도메인의 만료일을 주기적으로 추적해야 한다.
또한 스테이징 서버나 내부 네트워크 도메인을 승인 목록에 포함할 경우, 공격자의 Spoofing에 의해 웹뷰가 장악될 위험이 있으므로 제외해야 한다.

기본 브라우저 사용 및 JS Interface 최소화

앱의 권한으로 임의의 코드가 실행될 위험을 막기 위해, addJavascriptInterface의 사용은 피할 수 없는 경우가 아니라면 최소화해야 한다.
만약 앱의 승인된 도메인 목록에 속하지 않는 URL을 열어야 한다면, 앱 내장 웹뷰가 아닌 디바이스의 기본 브라우저를 통해 열도록 처리하여 앱과 웹의 권한을 물리적으로 분리하는 것이 안전하다.


마치며

본 글에서 분석한 취약점은 이전에 포스팅했던 카카오톡의 사례와 마찬가지로, 안드로이드의 강력한 보안 모델이 적용되어 있더라도 모바일 앱과 웹이 교차하는 지점에서의 사소한 검증 누락이 시스템 붕괴로 이어질 수 있음을 보여준다.

다행히 이 취약점은 마이크로소프트 보안 연구팀의 책임 있는 공개를 통해 2022년 2월 틱톡에 전달되었고, 한 달도 되지 않아 신속하게 패치되었다.
현대의 플랫폼 환경에서는 다양한 기종과 기술이 복잡하게 얽혀 있는 만큼, 보안 업계의 긴밀한 위협 인텔리전스 공유와 범산업적인 협력이 중요하다.
이 분석 글이 모바일 딥링크와 웹뷰를 다루는 수많은 개발자와 보안 연구자들에게 직관적인 인사이트를 제공하기를 바란다.


참고자료
https://www.microsoft.com/en-us/security/blog/2022/08/31/vulnerability-in-tiktok-android-app-could-lead-to-one-click-account-hijacking/
https://developer.android.com/training/app-links/create-deeplinks?hl=ko
https://developer.android.com/reference/android/webkit/JavascriptInterface
https://developer.android.com/guide/components/intents-filters?hl=ko
https://developer.android.com/training/app-links/verify-applinks?hl=ko