LINE Corporation이 2023년 10월 1일부로 LY Corporation이 되었습니다. LY Corporation의 새로운 기술 블로그를 소개합니다. LY Corporation Tech Blog

Blog


Android 취약점 탐색 자동화를 위한 Jandroid 적용기

안녕하세요. LINE에서 보안 업무를 담당하고 있는 박선주입니다. LINE 보안 팀에서는 릴리스될 서비스와 애플리케이션의 보안 위협을 사전에 발견하여 제품을 더 안전하게 만들기 위해 노력하고 있습니다. 서비스나 애플리케이션을 대상으로 보안 검수를 진행하다 보면, 기존에 보안 이슈가 발생했던 코드와 유사한 패턴을 사용하거나 취약한 패턴의 코드를 재사용하여 취약점이 발생하는 경우가 많습니다. 이처럼 빈번하게 발생하는 보안 이슈를 쉽고 빠르게 탐지하기 위해서 상용 솔루션을 사용하거나 자체적인 도구를 개발하고 있는데요. 이번 포스팅에서는 Android 애플리케이션에서 사용할 수 있는 자동화 취약점 탐색 도구인 Jandroid를 LINE Android 애플리케이션에 적용하여 보안 이슈를 발견한 내용을 공유하고자 합니다.

Jandroid란?

Jandroid는 F-Secure Labs에서 제작한 취약점 탐색 도구입니다. Androguard라는 오픈 소스를 기반으로 제작되었습니다. Androguard는 Android 실행 파일을 분석하는 역할을 담당합니다. 크게 아래와 같은 기능이 있습니다.

  • APK 파일 정보 추출
  • 클래스와 메서드 목록 추출
  • 상호 참조(cross reference) 분석

이 기능을 기반으로 Android 애플리케이션에서 보안상 위험한 메서드나 일련의 코드를 찾을 수 있는 취약점 패턴 매칭 기능과 패턴을 정의하는 템플릿 기능을 제공하는 것이 Jandroid입니다.

Jandroid는 취약한 코드를 어떻게 찾을까?

Jandroid는 취약한 코드 패턴을 발견하기 위해서 정적 분석 방법을 사용합니다. 정적 분석은 컴파일된 코드로 프로그램의 실행 흐름을 분석하는 방법입니다. 애플리케이션 코드에 구조적인 문제나 보안상 위험한 코드의 사용 등 취약한 코드 패턴이 존재하는지 확인할 수 있습니다. Jandroid 동작 방법을 자세히 살펴보기 위해 어떻게 취약한 코드 패턴을 찾는지 알아보겠습니다.

Jandroid는 위험한 메서드가 위치한 곳부터 Android 애플리케이션 내의 액티비티(activity)까지 코드 흐름을 살펴보면서 실제로 호출이 가능한지 탐색하는 방법으로 보안 이슈를 찾습니다. 액티비티란 Android에서 실행되는 구성 요소로, 애플리케이션 실행의 기본이 됩니다. 위험한 메서드가 액티비티의 실행으로 호출된다는 것은, 사용자가 작동시키거나 외부에서 액티비티를 트리거해서 위험한 메서드에 사용자의 데이터를 주입해 호출할 수 있다는 뜻으로 해석할 수 있습니다.

예를 들어 java.lang.ProcessBuilder 같은 커맨드를 실행할 수 있는 위험한 메서드가 액티비티가 실행되며 호출된다면, 실제로 사용자가 이를 호출할 가능성이 있는 것입니다. Jandroid에서는 이런 위험을 찾기 위해 다음과 같은 방법을 사용합니다.

그림 1. Jandroid 코드 탐색 방법 1.1

그림 1의 ProcessBuilder 메서드는 사용자가 검색하기 위해 템플릿에 명시한 메서드입니다. 템플릿에서 검색할 메서드를 가져와 애플리케이션 내 코드에서 검색한 뒤 그 결과를 저장합니다. 액티비티에서 호출 가능한 결과들인지 탐색할 때 사용자가 템플릿에 명시한 메서드를 시작점으로 사용하며, 이 시작점을 직접 호출하는 상호 참조 경로를 찾습니다.

그림 2. Jandroid 코드 탐색 방법 1.2

그림 2의 'Activities'는 사용자가 검색하기 위해 템플릿에 명시한 액티비티를 의미합니다. Jandroid에서 검색할 액티비티의 속성 정보를 템플릿에서 읽어 애플리케이션에서 검색한 속성과 일치하는 액티비티를 저장합니다. 그런 뒤 앞서 찾은 상호 참조 경로가 애플리케이션 내 액티비티까지 이어지는지 검사합니다. 만약 경로가 액티비티까지 도달한다면, 액티비티가 실행되면서 ProcessBuilder 메서드가 호출될 것입니다. 이렇게 상호 참조 탐색 작업을 반복하며 액티비티까지 호출될 수 있는 경로를 찾는 방법으로 잠재적인 취약점을 찾습니다.

다른 방법으로는, 앞서 그림 1에서 설명한 단일 메서드 호출뿐 아니라 일련의 코드 패턴이 액티비티에서 호출되는지도 탐색할 수 있습니다. 종종 한 메서드가 다른 메서드와 함께 호출되는 경우 보안 이슈가 발생할 수 있는데요. 예를 들어 WebView의 loadUrl 메서드와 addJavascriptInterface 메서드가 함께 호출되는 코드 패턴이 있습니다. 이 패턴에서 이어지는 코드 흐름이 액티비티까지 이어진다면, WebView에 추가된 'native bridge'를 공격자가 마음대로 사용할 수 있는 가능성이 있습니다.

그림 3. Jandroid 코드 탐색 방법 2

그림 3은 이런 코드 패턴을 찾기 위한 방법입니다. 여러 메서드가 호출되면서 취약점이 발생할 여지가 있는 곳에서 시작해, 사용자가 호출할 수 있는 끝 지점인 액티비티까지 호출이 가능한지 검사하는 방법으로 취약한 코드를 찾아냅니다. 예를 들어 메서드 명 addJavascriptInterface을 검색해서 그 결과로 나온 클래스 명을 저장합니다. 그런 뒤 템플릿에서 정의한 시작점인 loadUrl 메서드와 앞서 저장한 클래스 명을 결합하여 동일 클래스에서 loadUrl 메서드를 사용하고 있는지 찾습니다. 만약 loadUrl 메서드가 사용 중이라면 해당 메서드의 상호 참조를 재귀적으로 탐색합니다. 그림 2와 마찬가지로, 상호 참조로 찾은 경로가 사용자가 템플릿에 명시한 액티비티까지 호출할 수 있는지 반복해서 진행합니다. 

이런 방법으로 검사를 진행하여 패턴화된 코드가 탐지되었다면, 해당 애플리케이션은 사용자가 보안 위협이 존재하는 코드까지 실행할 수 있다는 것을 의미하고, 이는 곧 취약점으로 이어질 가능성이 높습니다.

Jandroid를 선택한 이유

Android 취약점 탐지 자동화 도구로 Jandroid를 선택한 이유는 사용자가 탐지하고 싶은 취약 코드 패턴을 자유롭게 정의할 수 있기 때문입니다. 대부분의 Android 애플리케이션에서 발생하는 취약점과 로직 내 버그들은 해당 애플리케이션의 기능에 따라 달라지는 경우가 많습니다. 애플리케이션마다 발생하는 이슈가 제각각이며 같은 이슈가 빈번하게 일어나는 경우가 많습니다. 또한 다양한 애플리케이션의 취약한 코드 패턴을 찾기 위해 코드를 일일이 검수하려면 시간이 많이 소모됩니다. 그러므로 보안 검사를 진행할 애플리케이션의 특징을 파악해서, 취약한 이슈나 로직 버그가 발생했던 패턴들의 코드를 템플릿화하여 그에 맞는 결과물을 보며 검수할 수 있다면, 취약한 코드를 보다 수월하게 찾을 수 있고 검수 시간도 줄일 수 있습니다. 또한 Androguard는 컴파일된 코드를 이용해 분석을 진행하기 때문에 APK 파일만으로도 간편하게 검사가 가능하다는 장점이 있습니다.

저희는 이런 이유로 Jandroid가 LINE의 수많은 Android 애플리케이션에 적용하기에 알맞다고 생각했습니다. 참고로 F-Secure Labs에선 ZDI에서 진행하는 'Mobile Pwn2Own'이라는 해킹 콘테스트에서 Jandroid를 사용하여 취약한 코드를 찾은 사례 중 하나를 발표하기도 했습니다. S사의 모바일 장비에 존재하는 여러 개의 버그를 조합해 사용자의 폰을 장악한 사례였는데요. 공격에 사용된 여러 취약점 중 노트 관련 애플리케이션의 취약점(CVE-2018-10501)은 Jandroid를 사용해 손쉽게 발견할 수 있었다고 합니다.

Jandroid 사용 방법

템플릿 파일 구성

앞서 설명드렸듯이 Jandroid는 사용자가 정의한 코드의 패턴을 찾기 위해서 템플릿 파일을 사용하는데요. 이 템플릿 파일을 통해 찾고자 하는 취약한 코드의 패턴을 정의할 수 있습니다. 템플릿 파일은 /templates/android/*.template 파일로 저장하고, 불러옵니다. 해당 파일은 아래와 같이 JSON 포맷으로 구성됩니다.

{
    "METADATA": {
        "NAME": "JSbridgeBrowsable"
    },   
    "MANIFESTPARAMS": {
        "BASEPATH": "manifest->application->activity OR manifest->application->activity-alias",
        "SEARCHPATH": {
            "intent-filter": {
                "action": {
                    "LOOKFOR": {
                        "TAGVALUEMATCH": "<NAMESPACE>:name=android.intent.action.VIEW"
                    }
                },
                "category": {
                    "LOOKFOR": {
                        "TAGVALUEMATCH": "<NAMESPACE>:name=android.intent.category.BROWSABLE"
                    }
                },
                "data": {
                    "RETURN": ["<NAMESPACE>:host AS @host", "<NAMESPACE>:scheme AS @scheme"]
                }               
            }
        },
        "RETURN": ["<smali>:<NAMESPACE>:name AS @activity_name"]
    },
    "CODEPARAMS": {
        "SEARCH": {
            "SEARCHFORCALLTOMETHOD": {
                "METHOD": "Landroid/webkit/WebView;->addJavascriptInterface",
                "RETURN": "<class> AS @web_view"
            }
        },
        "TRACE": {
            "TRACEFROM": "<method>:@web_view[]->loadUrl(Ljava/lang/String;)V",
            "TRACETO": "<class>:@activity_name",
            "TRACELENGTHMAX": 10,
            "RETURN": "<tracepath> AS @tracepath_browsablejsbridge"
        }
    },
    "GRAPH": "@tracepath_browsablejsbridge WITH <method>:<desc>:<class> AS attribute=nodename"
}

위 코드를 간략하게 설명하겠습니다.

  1. METADATA → NAME에 템플릿의 이름을 지정합니다.
  2. MANIFESTPARAMS 노드는 검색할 액티비티를 정의하는 곳으로, 그림 2의 'Activities'에 해당합니다. 내용을 살펴보면 AndroidManifest.xml 파일에서 찾고자 하는 액티비티의 범위를 정하고 있습니다. 하위 BASEPATH의 값은 AndroidManifest.xml 파일의 <manifest><application><activity> XML 태그 안에서 SEARCHPATH 노드와 일치하는 액티비티를 찾는다는 것을 의미합니다. 
  3. SEARCHPATH 노드는 <intent-filter> 안의 <action> 태그에서 android:name이 android.intent.action.VIEW인 값을 찾고, <category> 태그에서 android:name이 <NAMESPACE>:name=android.intent.category.BROWSABLE인 값을 찾습니다. 그리고 일치하는 액티비티가 있다면 해당하는 액티비티 명을 activity_name라는 템플릿 변수로 표현하여 저장합니다.
  4. CODEPARAMS 노드는 검색할 메서드를 정의하는 곳으로, 그림 3에서 탐색할 메서드에 해당합니다. 노드 내용을 살펴보면, SEARCH 노드의 SEARCHFORCALLTOMETHOD에서는 메서드 값에 정의되어 있는 클래스와 메서드를 검색하고, 검색된 클래스들을 RETURN의 @web_view 템플릿 변수에 저장합니다.
  5. TRACE 노드는 Jandroid에서 탐색할 시작점과 끝나는 점을 정의합니다. 시작점은 TRACEFROM에서, 끝나는 점은 TRACETO에서 정의합니다. 그림 3에서 검색하고 나서 탐색을 시작할 곳과, 그림 2에서 끝나는 점의 액티비티가 이 노드에 해당하는데요. 템플릿 내용을 확인해 보면 TRACEFROM에서는 위의 SEARCH 노드에서 검색하고, web_view 템플릿 변수에 저장했던 클래스와 loadUrl 메서드를 결합하여 시작점으로 정의합니다. 끝나는 점인 TRACETO는 이전에 찾았던 activity_name 템플릿 변수의 클래스로 정의합니다. 즉 해당 템플릿은 그림 2의 코드 패턴을 정의한 것입니다.

실행하기

커맨드 라인에서 python3으로 Jandroid 도구를 실행하면, 수많은 디버그 메시지와 함께 분석 중인 메서드 등의 정보가 출력됩니다. 탐지 결과는 /output/raw/*.json에 저장되며, 커맨드 라인에서 입력한 -g 인자 옵션에 따라 /output/graph/jandroid.html에 생성된 그래프를 확인할 수 있습니다. 인자 옵션에 대한 자세한 정보는 -h 옵션으로 확인할 수 있습니다. 

Jandroid> python3 src/jandroid.py -f /workspace/bin/apks/target/ -g visjs
 
----------------------------
           JANDROID
----------------------------
 
INFO     Creating template object.
INFO     1 potential template(s) found.
DEBUG    Parsing /Jandroid/templates/android/sample.template
INFO     Initiating Android analysis.
INFO     Performing basic checks. Please wait.
INFO     Basic checks complete.
INFO     Beginning analysis...
DEBUG    1 app(s) to analyse, using 2 thread(s).
DEBUG    Created worker process 0
DEBUG    Created worker process 1
DEBUG    AnalyzeAPK
DEBUG    Analysing without session
INFO     Analysing test.apk in worker thread 0.
DEBUG    AXML contains a RESOURCE MAP
DEBUG    Start of Namespace mapping: prefix 38: 'android' --> uri 105: 'http://schemas.android.com/apk/res/android'
DEBUG    START_TAG: manifest (line=2)
DEBUG    found an attribute: {http://schemas.android.com/apk/res/android}versionCode='b'19010402''
...
DEBUG    Parsing instructions
DEBUG    Parsing exceptions
DEBUG    Creating basic blocks in L$r8$java8methods$utility$Boolean$hashCode$IZ;->hashCode(Z)I [access_flags=public static synthetic] @ 0x18341c
DEBUG    Settings basic blocks childs
DEBUG    Creating exceptions
...

실행 후에 /output/graph/jandroid.html에 생성된 그래프 결과를 확인해 보면 아래 그림 4와 같습니다. Jandroid 템플릿 파일에서 정의한 메서드를 시작점으로 삼아 호출 흐름이 끝나는 점인 액티비티까지 연결되어 있는 것을 확인할 수 있습니다. 이 그래프 연결 정보는 Jandroid 실행이 끝나면 /output/raw/<App Name>.json에 저장되며, 그래프를 생성할 때 사용됩니다.

그림 4. Jandroid 결과 그래프

Jandroid의 한계와 개선 방법

Jandroid를 LINE의 Android 애플리케이션에 적용해보니, 템플릿에 정의한 취약한 코드 패턴을 잘 찾지 못하였습니다. 취약한 코드 패턴을 찾지 못한 이유를 살펴 본 결과, Jandroid에 몇 가지 한계점이 있었고 이를 개선해보았습니다.

서로 다른 클래스에서 호출되는 패턴은 탐지되지 않는 문제

Jandroid에서 서로 다른 두 개의 메서드를 호출하는 취약한 코드 패턴을 찾을 때, 두 개의 메서드가 서로 다른 클래스에서 호출되지만 결국 같은 루트(root) 경로에서 호출되고 있는 코드 패턴을 탐지하지 못하는 경우가 있었습니다. 예를 들어 아래 그림 5의 구조처럼 각각 다른 클래스에서 addJavaScriptInterface와 loadUrl 메서드가 호출되면, 호출되는 루트 경로가 같더라도 탐지하지 못했습니다.

그림 5. Jandroid에서 찾지 못하는 코드 패턴

그림 5를 보면 'Some Class'의 메서드가 서로 다른 Class B와 Class C의 메서드를 호출합니다. 이때 서로 다른 Class B와 Class C의 addJavaScriptInterface와 loadUrl 메서드를 호출하는 패턴을 찾으려고 할 때, 위 템플릿 파일로 정의된 경우 Jandroid에서는 다음과 같이 찾습니다.

addJavscriptInterface 메서드를 검색하여 결과로 나온 클래스(위의 예제에서는 Class B)들을 템플릿 변수에 저장합니다. 그리고 클래스가 담긴 템플릿 변수와 탐색을 시작할 loadUrl 메서드를 결합하여 시작점으로 탐색합니다. 즉 그림 5와 같은 코드 패턴인 경우, addJavascriptInterface 메서드를 사용하는 Class B를 찾았지만, 이후 시작점에서 탐색을 할 때 loadUrl 메서드가 Class B에 있다고 가정하기 때문에 실제 Class C에 있는 loadUrl 메서드는 탐지할 수 없게 됩니다. Jandroid에서는 addJavascriptInterface와 loadUrl 메서드가 같은 클래스에서 호출될 것이라고 가정하기 때문에 그림 5와 같은 코드 패턴을 탐지하지 못하는 것입니다.

개선한 방법

위 문제를 해결하기 위해 서로 다른 클래스에서 호출하는 메서드 호출 패턴을 탐지할 수 있도록 아래와 같은 방법으로 개선했습니다. 

그림 6. 다른 클래스 찾기 개선 방법

저희는 찾으려는 메서드를 사용자가 입력할 수 있도록 템플릿의 TRACEFROM 값에 WITH라는 연산자를 처리하는 기능을 구현했습니다. 이 WITH 연산자는 피연산자 위치에 정의된 메서드들을 검색해서 상호 참조를 재귀 탐색합니다. 탐색 중 서로 겹치는 경로가 있다면 그 경로를 시작점으로 액티비티의 클래스와 메서드가 끝나는 점까지의 경로를 찾습니다. 그림 6과 같이 'addJavascriptInterface WITH loadUrl'로 TRACEFROM 값을 설정하면 두 메서드의 상호 참조 경로가 겹치는 하나의 경로를 찾아냅니다. 그리고 이 경로부터 끝나는 점인 액티비티까지 탐색을 진행합니다. 

또한 Jandroid에서 메서드와 메서드 간의 경로를 탐색할 때도 여기서 구현한 방법을 동일하게 사용해서 개선할 수 있었습니다. TRACEFROM과 TRACETO가 메서드라면, 두 개의 메서드 경로를 탐색한 결과에서 겹치는 경로를 중간으로 설정하고 이 경로를 중심점으로 삼아 각각 나머지 경로를 연결해서 두 메서드의 호출 경로를 탐색하고 표현할 수 있게 구현했습니다.

일부 메서드의 상호 참조를 찾을 수 없는 문제

Jandroid는 취약한 코드 패턴을 찾기 위해서 상호 참조를 통한 탐색을 합니다. 하지만 이 탐색 방법만으로는 Android에서 모든 메서드의 상호 참조를 찾을 수 없습니다. 아래 그림과 같이 Fragment가 생성되었을 때를 예로 들어 보겠습니다. 

그림 7. Fragment의 호출 과정

Fragment가 생성되면 onAttach로 시작해 onCreate, onCreateView 순서로 콜백 메서드가 호출됩니다. 이 메서드들은 애플리케이션 코드가 아니라 Android 시스템에서 호출하기 때문에, 애플리케이션 코드에서는 이런 메서드들의 상호 참조를 찾을 수 없습니다. Fragment는 Android 애플리케이션 화면 전환에 사용되는데, 화면 전환 시에는 Android 시스템이 화면 리소스에 저장되어 있는 클래스의 뷰(view) 콜백 메서드를 호출해 줍니다. 그러므로 애플리케이션 코드에서 Fragment가 사용될 때는 메서드 콜백의 상호 참조를 찾을 수 없습니다. 즉 현재 Jandroid에서 사용하는 방법인 메서드 상호 참조를 통한 탐색 방법엔 한계가 있습니다.

Jandroid에서는 이런 콜백 함수들을 찾기 위해서 특수 케이스를 추가해 놓았습니다. 상호 참조의 재귀 탐색 도중 특수 케이스에 해당하는 메서드를 찾으면 메서드 명을 바꾸는 방식으로 구현되어 있습니다. 가령 콜백으로 호출되는 onAttach를 탐색할 때에는 onCreate로 메서드 명을 바꿔줍니다. 이렇게 상호 참조가 없는 메서드를 onAttach → onCreate와 같이 바꿔서 메서드를 탐색할 수 있게 만듭니다. 하지만 특수 케이스를 사용한 방법도 동일 클래스에서 콜백으로 호출되는 메서드에 한해서만 이름을 바꿔 처리해 주는 것이기 때문에 역시 한계가 있었습니다. 예를 들어 onAttach처럼 클래스에서 제일 처음 호출되는 메서드를 탐색하는 경우엔 특수 케이스 방법으로는 찾지 못합니다. 그래서 NavController나 Fragment 등 화면을 전환하는 기능이거나 다른 Android 시스템에서 호출하는 메서드는 탐색하지 못했습니다.

개선한 방법

Android 시스템에서 호출되는 메서드는, 상호 참조를 재귀 탐색하는 방법으로는 경로를 찾을 수 없습니다. 앞서 언급한 Android의 Fragment나 NavController 등은 앱 내 화면 전환을 담당하는데요. 상호 참조로는 찾을 수 없는 코드에 의해 실행 흐름이 바뀌기 때문에 이를 탐색하기 위해서 다음과 같이 탐색 방법을 개선했습니다.

탐색을 시작하기 전에 Android 애플리케이션 내 화면 전환에 사용되는 리소스들을 추출하여 이와 관련된 Fragment 정보를 분석합니다. 그리고 애플리케이션 패키지 내 메서드에서 해당 리소스를 사용하는지 검사하고, 사용한다면 해당 메서드와 리소스, Fragment 정보를 저장합니다. 이후 패턴을 탐색하면서 onCreateView와 같은 메서드에 도달하면 해당 클래스가 저장된 Fragment 정보와 일치하는지 검사한 후, 일치한다면 저장해두었던 Fragment의 리소스를 사용하는 메서드로 경로를 찾도록 구현하였습니다. 그리고 더 나아가서 메서드 명을 바꿔서 메서드를 연결해 주는 방법인 특수 케이스를 더 추가했습니다. onCreateView => onViewCreated처럼 같은 클래스에서 서로 호출하는 관계인 메서드들의 이름을 바꿔주는 특수 케이스를 추가해서, 그동안 탐색되지 않았던 상호 참조 관계의 메서드를 탐색할 수 있게 개선했습니다. 

코드 흐름을 따라가기가 어려운 문제

Jandroid의 탐지 결과 그래프는 시작점과 끝점의 클래스와 메서드 명으로 표시됩니다. 그런데 탐지 결과를 검증할 때 아래 그림 8과 같은 결과만으로는 시작점부터 끝나는 점까지 코드 흐름을 따라가기가 어렵습니다.

그림 8. 개선 전 결과 그래프

개선한 방법

탐색 시작점부터 끝나는 점까지의 경로를 전부 볼 수 있게 탐지 결과와 그래프 결과를 개선했습니다. 아래 그림 9는 경로를 전부 볼 수 있도록 개선한 후의 결과 그래프입니다.

그림 9. 개선 후 결과 그래프

LINE Android 애플리케이션에 적용한 결과

개선된 Jandroid를 이용해 LINE의 여러 Android 애플리케이션에서 취약점을 발견할 수 있었고, 이번 글에서 그중 한 가지 사례를 소개하겠습니다. 취약점을 발견하기 위해 사용한 템플릿은 다음과 같습니다.

{
    "METADATA": {
        "NAME": "JSbridgeBrowsable"
    },   
    "MANIFESTPARAMS": {
        "BASEPATH": "manifest->application->activity OR manifest->application->activity-alias",
        "SEARCHPATH": {
            "intent-filter": {
                "action": {
                    "LOOKFOR": {
                        "TAGVALUEMATCH": "<NAMESPACE>:name=android.intent.action.VIEW"
                    }
                },
                "category": {
                    "LOOKFOR": {
                        "TAGVALUEMATCH": "<NAMESPACE>:name=android.intent.category.BROWSABLE"
                    }
                },
                "data": {
                    "RETURN": ["<NAMESPACE>:host AS @host", "<NAMESPACE>:scheme AS @scheme"]
                }               
            }
        },
        "RETURN": ["<smali>:<NAMESPACE>:name AS @activity_name"]
    },
    "CODEPARAMS": {
        "SEARCH": {
            "SEARCHFORCALLTOMETHOD": {
                "METHOD": "Landroid/webkit/WebView;->addJavascriptInterface",
                "RETURN": "<method> AS @web_view"
            }
        },
        "TRACE": {
            "TRACEFROM": "<method>:@web_view[] WITH Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V",
            "TRACETO": "<class>:@activity_name",
            "TRACELENGTHMAX": 10,
            "RETURN": "<tracepath> AS @tracepath_browsablejsbridge"
        }
    },
    "GRAPH": "@tracepath_browsablejsbridge WITH <method>:<desc>:<class> AS attribute=nodename"
}

loadUrl과 addJavascriptInterface를 호출하는 취약한 패턴을 명시하고 있으며, 앞서 설명드린 WITH 기능을 사용했습니다. 이 패턴으로 탐지한 결과는 다음과 같습니다.

탐지된 그래프 결과 - 그림 10

그림 10의 탐지 결과를 확인해 보면, 템플릿에서 정의한 것과 같이 액티비티부터 WebView loadUrl과 addJavascriptInterface 메서드를 호출하는 메서드까지의 경로를 확인할 수 있습니다. 위 그래프 결과를 참고해 탐색한 경로를 나열해보면 다음과 같습니다.

MainActivity→onNewIntent => AppSchemeInterpreter→maybeHandleSchemeIntent => AppSchemeInterpreter→interpret => AppSchemeInterpreter→interpretPayment => WebViewFragment→onCreateView

해당 애플리케이션 코드 경로를 토대로 분석해 보면, 실제로 intent 속성 android.intent.category.BROWSABLE를 가지고 있는 MainActivity 액티비티부터 시작해서 WebViewFragment→onCreateView 메서드까지 코드 경로 도달이 가능하고, WebViewFragment→onCreateView 메서드에서는 WebView loadUrl과 addJavascriptInterface 메서드를 호출하는 코드 패턴을 가진 것을 확인할 수 있었습니다.

아래 그림 11은 실제 LINE의 Android 애플리케이션을 디컴파일해서 본 코드 흐름입니다. Jandroid의 결과와 동일한 경로로 메서드 흐름을 따라가면, onNewIntent → maybeHandleSchemeIntent → interpret → interpretPayment → onCreateView 순서로 호출되는 것을 확인할 수 있습니다.

그림 11. 애플리케이션을 디컴파일한 코드 결과 1

위 그림 11에서 메서드가 사용하는 인자를 확인해 보면 다음과 같습니다. maybeHandleSchemeIntent에서는 onNewIntent로 받은 intent를 string으로 변환하고 interpret를 호출합니다. 그리고 호출되는 interpret에서는 intent가 Uri.parse 메서드를 호출함으로써 Uri 객체로 파싱되어 저장되며 Uri 객체의 scheme 값을 검사합니다. scheme 값이 검사할 값과 일치하다면 interpretPayment를 호출합니다. interpretPayment에서는 Uri 객체에서 url 파라미터를 파싱한 데이터로 Navigation 객체를 생성합니다. 생성한 Navigation 객체는 이후에 interpret 함수에서 navController.navigate 함수 인자로 사용되고, url 파라미터는 WebViewFragmentArgs의 onCreateView 메서드 인자로 사용됩니다.

그림 12. 애플리케이션을 디컴파일한 코드 결과 2

위 그림 12를 확인해 보면, WebViewFragmentArgs의 onCreateView에서는 addJavaScriptInterface 메서드를 호출하는 webBridge→attackWebview가 호출되는 것을 확인할 수 있고, loadUrl 메서드를 호출하는 WebViewFragmentArgs→renderWebPage가 호출되는 것을 확인할 수 있습니다. 그리고 loadUrl 메서드를 호출하는 인자를 살펴보면, WebViewFragmentArgs의 onCreateView의 메서드에서 args.getUrl() 값, 즉 이전의 interpretPayment 함수에서 보냈던 url 파라미터가 loadUrl 메서드 인자로 호출되는 것을 확인할 수 있습니다.

이를 정리하여 공격자 입장에서 공격 코드를 작성해 본다면 다음과 같습니다. 브라우저에서 intent 데이터인 scheme 값과 path, parameter를 'scheme://payment?url=https://attacker.com'로 설정합니다. 사용자가 이 링크에 접근하면 애플리케이션이 실행되고, JavaScript 함수가 바인딩되어 있는 WebView로 임의의 공격자 사이트인 'https://attacker.com'가 실행됩니다. 공격자는 이 공격자 사이트에서 사용자에게 악의적인 행위를 할 수 있습니다.

실제 해당 사례에서 사용자의 토큰을 획득할 수 있는 애플리케이션 JavaScript 함수가 존재했고, 이를 통해 공격자가 사용자를 탈취할 수 있는 위험이 있었습니다. 결과적으로 코드 패턴을 개선한 Jandroid로 검사하니 해당 Android 애플리케이션에서 화면 전환에 사용하는 경로를 탐지한 것을 확인할 수 있었고, 취약한 코드 패턴을 찾을 수 있었습니다.

마치며

지금까지 개선된 Jandroid를 LINE Android 애플리케이션에 적용해 취약점을 발견했던 방법에 대해 알아보았습니다. 이번 포스팅에서 소개한 취약점 사례뿐 아니라, 보안 검수를 통해서 발견한 사례들을 지속적으로 템플릿에 추가한다면, 더 많은 취약점을 더 빠른 시간 내에 탐지할 수 있게 됩니다.

물론 Jandroid와 같은 방식으로 취약점을 탐지하는 방법에도 단점은 있습니다. 바로 사용자가 정의한 템플릿에 한해서만 취약한 패턴을 찾는다는 점입니다. 입력이 실제 메서드까지 도달 가능한지 분석하는 'Taint analysis'를 하지 않기 때문에, 실제로 템플릿에 정의한 취약한 코드 패턴이 존재하더라도 사용자의 입력에 따라 코드 흐름이 달라져 원하는 행위를 할 수 없거나, 특정 조건문 때문에 취약한 코드 패턴에 도달할 수 없는 'false-positive' 결과가 나오기도 합니다.

하지만 그럼에도 이러한 도구의 장단점을 고려해서 적절히 사용한다면, 실제 알고 있는 취약한 패턴을 찾기 위한 시간을 단축시킬 수 있고, 템플릿에 취약한 패턴을 정의해 자동으로 수행함으로써 점검 누락을 방지할 수도 있습니다. 궁극적으로 여러 Android 애플리케이션을 보다 수월하게 검수할 수 있기 때문에 이를 잘 활용한다면 보안 검수자에게 이점으로 다가올 것입니다. 이상으로 글을 마치겠습니다. 긴 글 읽어주셔서 감사합니다.