Introduction

Android 애플리케이션은 로그인 폼, 검색 필드, 파일 업로드, 딥링크, QR 코드 등 다양한 경로를 통해 사용자 입력과 지속적으로 상호작용합니다. 이러한 입력이 적절하게 검증되지 않으면 SQL Injection, Cross-Site Scripting(XSS), 명령어 주입, 논리 우회 등 광범위한 공격에 노출될 수 있습니다.

Input Validation 문제는 사용자가 제어하는 데이터와 애플리케이션 로직 사이의 경계에 위치하기 때문에 모바일 애플리케이션에서 가장 중요하고 자주 악용되는 취약점 중 하나입니다.

AndroGoat 앱의 Input Validations 메뉴에는 다음 4가지 시나리오가 포함되어 있습니다:

  1. XSS (Cross-Site Scripting) - WebView 기반 스크립트 주입
  2. SQL Injection - 데이터베이스 쿼리 조작
  3. WebView File Access - 로컬 파일 시스템 접근
  4. QR Code XSS - QR 코드를 통한 스크립트 주입

Input Validations 메뉴에는 XSS, SQLi, WebView, QR CODE로 총 4가지 기능이 존재합니다.

Input Validations - XSS

취약점 개요

XSS는 웹 애플리케이션의 고전적인 취약점이지만, Android 앱에서 WebView를 사용할 때도 동일하게 발생할 수 있습니다. 이 취약점은 애플리케이션이 사용자 입력을 적절한 검증 및 인코딩 없이 받아들이고 처리한 후 사용자에게 다시 표시할 때 발생합니다.

AndroGoat의 XSS 시나리오에서는 HTML 또는 JavaScript 코드와 같은 악의적인 입력이 입력 필드를 통해 주입되고 WebView나 UI 컴포넌트에서 직접 렌더링됩니다. 애플리케이션이 입력을 신뢰하고 안전한 콘텐츠로 취급하기 때문에 주입된 스크립트가 일반 텍스트가 아닌 실행 가능한 코드로 처리됩니다.

취약점의 위험성

이 취약점의 위험성은 악성 코드가 실행될 때 발생할 수 있는 영향에 있습니다:

  • 세션 토큰 및 쿠키 탈취: 사용자의 인증 정보를 가로챌 수 있습니다
  • 사용자 입력 도청: 비밀번호나 개인정보를 실시간으로 훔칠 수 있습니다
  • UI 조작: 애플리케이션 인터페이스를 변조하여 피싱 공격을 수행할 수 있습니다
  • 무단 작업 수행: 사용자를 대신하여 권한이 필요한 작업을 실행할 수 있습니다
  • 피싱 페이지로 리다이렉션: 가짜 로그인 페이지로 사용자를 유도할 수 있습니다

기능 분석

XSS 버튼을 눌러 해당 액티비티로 이동합니다.

화면에는 텍스트 입력창과 Display 버튼이 있습니다. ADB를 사용하여 현재 활성 액티비티를 확인합니다.

PS C:\Users\WIN11> adb shell dumpsys activity activities | findstr "mCurrentFocus"
  mCurrentFocus=Window{a11f6d4 u0 owasp.sat.agoat/owasp.sat.agoat.XSSActivity}

현재 액티비티가 XSSActivity임을 확인했습니다. JADX에서 디컴파일한 코드를 살펴보겠습니다.

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(C1278R.layout.activity_xss);
        WebView webView = (WebView) findViewById(C1278R.id.webview);
        webView.setWebChromeClient(new WebChromeClient());
        WebSettings webSettings = webView.getSettings();
        Intrinsics.checkNotNullExpressionValue(webSettings, "webView.settings");
        webSettings.setJavaScriptEnabled(true);
        webView.loadDataWithBaseURL(null, "<html>\n<body>\n<script>\nfunction displayContent()\n{\nvar a=document.getElementById(\"name\");\ndocument.write(a.value);\n\n}\n</script>\nName: <input type=\"text\" id=\"name\"/>\n<br/><br/><input type=\"button\" value=\"Display\" onclick=\"displayContent()\" style=\"background-color:black;color: white; border: 2px solid #000000\"/>\n</body>\n\n</html>", "text/html", "UTF-8", null);
    }

코드를 자세히 살펴보면 여러 보안 문제가 발견됩니다.

첫 번째 문제: JavaScript 활성화

webSettings.setJavaScriptEnabled(true);

이 설정 자체가 취약한 것은 아니지만, JavaScript를 활성화하면서 사용자 입력을 검증하지 않는다면 심각한 보안 위험이 발생합니다. 이는 악성 스크립트가 실행될 수 있는 환경을 조성합니다.

두 번째 문제: 안전하지 않은 DOM 조작

HTML 코드 내의 JavaScript 함수 displayContent()를 보면:

function displayContent() {
    var a = document.getElementById("name");
    document.write(a.value);
}

이 함수는 입력 필드의 값을 가져와 document.write()로 직접 출력합니다. document.write()는 전달받은 내용을 그대로 렌더링하기 때문에, HTML 태그나 스크립트가 포함되어 있으면 그대로 실행됩니다.

세 번째 문제: 입력 검증 부재

입력값에 대한 어떠한 필터링이나 인코딩 처리가 없습니다. 일반적으로 XSS를 방어하기 위해서는:

  • 사용자 입력을 HTML 엔티티로 변환 (<&lt;, >&gt;)
  • 위험한 문자를 제거하거나 이스케이프 처리
  • Content Security Policy(CSP) 적용
  • 안전한 DOM 조작 메서드 사용 (textContent, innerText)

취약점 검증 (Proof of Concept)

기본적인 HTML 태그를 사용하여 스크립트 태그가 필터링 없이 실행되는지 확인합니다.

테스트 페이로드 #1: HTML 태그 삽입

<h1>XSS TEST</h1>

입력한 HTML이 정상적으로 렌더링되어 큰 제목으로 표시됩니다. 이는 HTML 태그가 필터링 없이 처리됨을 의미합니다.

Input Validations - SQLi

취약점 개요

SQL Injection은 사용자가 제공한 입력이 적절한 검증, 파라미터화된 쿼리 사용 없이 SQL 쿼리를 구성하는 데 직접 사용될 때 발생하는 취약점입니다. AndroGoat의 SQL Injection 시나리오에서는 사용자 이름, 검색 매개변수, ID와 같은 필드의 입력이 원시 SQL 문에 연결되어 로컬 SQLite 데이터베이스에 대해 실행됩니다.

애플리케이션이 입력을 안전하다고 가정하기 때문에 공격자는 쿼리의 의도된 로직을 변경하는 악의적인 SQL 구문을 주입할 수 있습니다.

취약점의 위험성

이 취약점의 위험성은 매우 심각합니다:

  • 인증 우회: 로그인 검증을 무력화하여 관리자 계정으로 무단 접근
  • 민감한 데이터 검색: 사용자 이름, 비밀번호, 개인정보 등을 무단으로 조회
  • 데이터베이스 레코드 수정 또는 삭제: 데이터 무결성 파괴
  • 권한 상승: 애플리케이션 내에서 권한을 확대
  • 데이터 유출: 백엔드 서비스와 동기화되는 데이터의 경우 더욱 심각

데이터베이스가 로컬(SQLite)이더라도 모바일 앱에서 SQL Injection을 악용하면 데이터 유출, 무단 접근, 신뢰 상실로 이어질 수 있습니다. 특히 추출된 데이터가 백엔드 서비스와 동기화되거나 민감한 사용자 정보를 포함하는 경우 더욱 위험합니다.

기능 분석

SQL Injection 페이지로 이동하면 테스트를 위해 2명의 사용자를 먼저 생성해야 한다는 안내 메시지가 표시됩니다. 사용자를 생성한 후 현재 액티비티를 확인합니다.

SQLInjectionActivity임을 확인했습니다. 디컴파일된 코드를 살펴보겠습니다.

Activity 초기화 코드:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(C1278R.layout.activity_sqlinjection);
        Button sqliButton = (Button) findViewById(C1278R.id.SQLiButton);
        final EditText username = (EditText) findViewById(C1278R.id.userName);
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Search Users");
        sqliButton.setOnClickListener(new View.OnClickListener() { // from class: owasp.sat.agoat.SQLinjectionActivity$$ExternalSyntheticLambda1
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                SQLinjectionActivity.onCreate$lambda$1(username, this, builder, view);
            }
        });
    }

취약한 쿼리 실행 코드:

 public static final void onCreate$lambda$1(EditText $username, SQLinjectionActivity this$0, AlertDialog.Builder builder, View it) {
        Intrinsics.checkNotNullParameter(this$0, "this$0");
        Intrinsics.checkNotNullParameter(builder, "$builder");
        String qry = "SELECT * FROM users WHERE username='" + ((Object) $username.getText()) + "'";
        try {
            this$0.mDB = this$0.openOrCreateDatabase("aGoat", 0, null);
            SQLiteDatabase sQLiteDatabase = this$0.mDB;
            Cursor qryResult = sQLiteDatabase != null ? sQLiteDatabase.rawQuery(qry, null) : null;
            StringBuilder strb = new StringBuilder("");
            if (qryResult == null || qryResult.getCount() <= 0) {
                strb.append("User: (" + ((Object) $username.getText()) + ") not found");
            } else {
                qryResult.moveToFirst();
                do {
                    strb.append("Username: (" + qryResult.getString(1) + ") password: (" + qryResult.getString(2) + ")\n");
                } while (qryResult.moveToNext());
                qryResult.close();
            }
            builder.setMessage("Users Found:\n " + ((Object) strb));
            Toast.makeText(this$0, strb.toString(), 1).show();
            Log.e("QueryResult", strb.toString());
        } catch (Exception e) {
            Log.d("Error", "Error occurred when querying database: " + e.getMessage());
            builder.setMessage("Error occurred:\n " + e.getMessage());
            Toast.makeText(this$0.getApplicationContext(), "Error occurred: " + e.getMessage(), 1).show();
        }
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { // from class: owasp.sat.agoat.SQLinjectionActivity$$ExternalSyntheticLambda0
            @Override // android.content.DialogInterface.OnClickListener
            public final void onClick(DialogInterface dialogInterface, int i) {
                dialogInterface.dismiss();
            }
        });
        AlertDialog dialog = builder.create();
        Intrinsics.checkNotNullExpressionValue(dialog, "builder.create()");
        dialog.show();
        Log.v("Query", qry);
        SQLiteDatabase sQLiteDatabase2 = this$0.mDB;
        if (sQLiteDatabase2 != null) {
            sQLiteDatabase2.close();
        }
    }

취약점 원인 분석

이 코드에서 SQL Injection이 발생하는 이유를 단계별로 분석해보겠습니다.

첫 번째 문제: 문자열 연결을 통한 쿼리 생성

String qry = "SELECT * FROM users WHERE username='" + 
             ((Object) $username.getText()) + "'";

사용자가 입력한 값을 그대로 SQL 쿼리에 삽입하고 있습니다. 이는 SQL Injection의 전형적인 패턴입니다. 사용자가 입력하는 모든 내용이 SQL 문법의 일부로 해석될 수 있습니다.

두 번째 문제: Prepared Statement 미사용

Cursor qryResult = sQLiteDatabase.rawQuery(qry, null);

rawQuery() 메서드를 사용하면서도 파라미터 바인딩을 하지 않았습니다. Android의 SQLite API는 Prepared Statement를 지원하지만, 두 번째 인자에 null을 전달하여 이 기능을 사용하지 않았습니다.

세 번째 문제: 입력 검증 부재

입력값에 대한 검증이나 필터링이 전혀 없습니다. SQL 구문에 사용되는 특수 문자:

  • 작은따옴표 (')
  • 세미콜론 (;)
  • 하이픈 (--)
  • 주석 (/* */)
  • UNION, SELECT 등 SQL 키워드

이러한 문자들이 필터링되지 않아 공격자가 자유롭게 SQL 구문을 조작할 수 있습니다.

네 번째 문제: 에러 메시지 노출

catch (Exception e) {
    builder.setMessage("Error occurred:\n " + e.getMessage());
}

데이터베이스 에러 메시지를 사용자에게 직접 표시하여 데이터베이스 구조에 대한 정보를 유출합니다.

취약점 검증 (Proof of Concept)

테스트 #1: 에러 기반 탐지 (Blind Technique)

먼저 작은따옴표(')만 입력하여 SQL 에러를 유발합니다:

'

이 입력으로 생성되는 쿼리:

SELECT * FROM users WHERE username='''

구문 오류가 발생하며, 애플리케이션이 SQL 명령을 직접 실행하고 있음을 증명합니다.

테스트 #2: 정상 사용자 검색

admin

admin 사용자가 존재하지 않는다는 메시지가 표시됩니다.

테스트 #3: 기본 SQL Injection - 전체 사용자 조회

' OR 1=1 --

최종적으로 완성된 SQL 쿼리:

SELECT * FROM users WHERE username=' ' OR 1=1 -- ';

쿼리 동작 분석:

  1. username='' - 빈 문자열과 비교 (거짓)
  2. OR 1=1 - 항상 참인 조건
  3. -- - SQL 주석 처리 (뒤의 모든 내용 무시)
  4. 결과: WHERE 절이 항상 참이 되어 모든 레코드 반환

데이터베이스의 모든 사용자의 Username과 password가 출력됩니다.

Input Validations - WebView

취약점 개요

Android 앱에서 WebView는 웹 콘텐츠를 표시하는 강력한 컴포넌트이지만, 잘못 설정하면 로컬 파일 시스템에 접근하거나 민감한 정보를 노출할 수 있습니다. 이 취약점은 애플리케이션이 사용자 제어 입력을 적절한 검증, 허용된 콘텐츠 제한 없이 Android WebView 내에서 로드하고 처리할 때 발생합니다.

AndroGoat의 WebView 시나리오에서는 사용자가 제공한 URL, HTML 콘텐츠, 매개변수와 같은 데이터가 WebView에서 직접 렌더링됩니다. 애플리케이션이 이 입력을 신뢰하면 공격자는 JavaScript 또는 무단 링크를 포함한 악의적인 콘텐츠를 WebView에 주입할 수 있습니다.

취약점의 위험성

이 취약점은 WebView의 공격 표면을 크게 확장합니다:

  • 악의적인 JavaScript 실행: XSS 공격과 유사한 스크립트 실행
  • 신뢰할 수 없는 웹 페이지 로드: 외부 악성 사이트 접근
  • 피싱 공격: 가짜 로그인 폼을 표시하여 자격 증명 탈취
  • UI 조작: 사용자를 오도하기 위한 인터페이스 변조
  • 로컬 파일 접근: file:// 프로토콜을 통한 앱 내부 파일 읽기
  • 민감한 데이터 유출: SharedPreferences, 데이터베이스 파일 등 접근

WebView가 JavaScript 활성화, file:// URL 접근 허용, JavaScript 인터페이스 노출과 같은 안전하지 않은 설정을 가지고 있다면 영향은 더욱 심각해지며, 민감한 데이터 유출, 세션 하이재킹, 애플리케이션 기능에 대한 무단 접근으로 이어질 수 있습니다.

전반적으로 WebView 처리에서의 부적절한 입력 검증은 사용자 입력과 애플리케이션 로직 사이의 신뢰 경계를 무너뜨려 애플리케이션을 클라이언트 측 공격에 취약하게 만듭니다.

기능 분석

WebView 메뉴로 이동하면 URL을 입력할 수 있는 필드와 Load 버튼이 표시됩니다. 현재 액티비티를 확인합니다.

PS C:\Users\WIN11> adb shell dumpsys activity activities | findstr "mCurrentFocus"
  mCurrentFocus=Window{7e502af u0 owasp.sat.agoat/owasp.sat.agoat.InputValidationsWebViewURLActivity}

InputValidationsWebViewURLActivity로 확인되었습니다. 디컴파일된 코드를 살펴보겠습니다.

Activity 초기화 코드:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(C1278R.layout.activity_input_validations_web_view_url);
        Button loadButton = (Button) findViewById(C1278R.id.load);
        final WebView webView = (WebView) findViewById(C1278R.id.webview1);
        final EditText urlEditText = (EditText) findViewById(C1278R.id.url);
        loadButton.setOnClickListener(new View.OnClickListener() { // from class: owasp.sat.agoat.InputValidationsWebViewURLActivity$$ExternalSyntheticLambda0
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                InputValidationsWebViewURLActivity.onCreate$lambda$0(webView, urlEditText, view);
            }
        });
    }

취약한 WebView 설정 및 로딩 코드:

public static final void onCreate$lambda$0(WebView $webView, EditText $urlEditText, View it) {
        WebSettings webViewSettings = $webView.getSettings();
        Intrinsics.checkNotNullExpressionValue(webViewSettings, "webView.settings");
        webViewSettings.setJavaScriptEnabled(true);
        webViewSettings.setAllowFileAccess(true);
        webViewSettings.setAllowContentAccess(true);
        webViewSettings.setAllowFileAccessFromFileURLs(true);
        webViewSettings.setAllowUniversalAccessFromFileURLs(true);
        String url = $urlEditText.getText().toString();
        $webView.loadUrl(url);
    }

취약점 원인 분석

InputValidationsWebViewURLActivity의 취약점은 사용자가 제공한 URL을 WebView에 로드하기 전에 적절한 검증과 제한이 없으며, WebView 구성 자체가 지나치게 허용적이라는 점에 있습니다.

첫 번째 문제: 파일 시스템 접근 허용

webViewSettings.setAllowFileAccess(true);

이 설정은 WebView가 file:// 프로토콜을 사용하여 로컬 파일 시스템에 접근할 수 있게 합니다. 일반적으로 웹 콘텐츠를 표시하는 WebView에서는 이 기능을 비활성화해야 합니다.

두 번째 문제: Cross-Origin 제한 무력화

webViewSettings.setAllowFileAccessFromFileURLs(true);
webViewSettings.setAllowUniversalAccessFromFileURLs(true);

이 두 설정은 매우 위험합니다:

  • setAllowFileAccessFromFileURLs(true): 로컬 파일에서 로드된 HTML이 다른 로컬 파일에 접근 가능
  • setAllowUniversalAccessFromFileURLs(true): 로컬 파일에서 로드된 콘텐츠가 모든 출처의 리소스에 접근 가능

이는 Same-Origin Policy(동일 출처 정책)를 완전히 무력화시킵니다. 공격자가 악의적인 HTML 파일을 로드하면 앱의 모든 로컬 파일을 읽고 외부 서버로 전송할 수 있습니다.

세 번째 문제: URL 입력 검증 부재

String url = $urlEditText.getText().toString();
$webView.loadUrl(url);

사용자가 입력한 URL을 아무런 검증 없이 그대로 loadUrl()에 전달합니다. 이로 인해:

  • file:// 프로토콜을 통한 로컬 파일 접근
  • javascript:// 프로토콜을 통한 스크립트 실행
  • content:// 프로토콜을 통한 콘텐츠 프로바이더 접근
  • 악의적인 외부 웹사이트 로드

모두 가능해집니다.

네 번째 문제: JavaScript 활성화

webViewSettings.setJavaScriptEnabled(true);

파일 접근과 JavaScript가 함께 허용되면 공격자는 JavaScript를 통해 로컬 파일의 내용을 읽고 XMLHttpRequest나 fetch API를 사용하여 외부 서버로 전송할 수 있습니다.

다섯 번째 문제: Content Access 허용

webViewSettings.setAllowContentAccess(true);

이 설정은 content:// URI를 통해 ContentProvider에 접근할 수 있게 하여 추가적인 공격 벡터를 제공합니다.\

테스트 #1: SharedPreferences 파일 접근

file:///data/data/owasp.sat.agoat/shared_prefs/users.xml

WebView가 앱의 내부 저장소에 있는 SharedPreferences XML 파일을 성공적으로 로드하여 표시합니다. 이는 민감한 설정 정보, API 키, 사용자 토큰 등이 노출될 수 있음을 의미합니다.

Input Validations - QR Code

취약점 개요

QR 코드는 URL, 텍스트, 연락처 정보 등을 빠르게 공유할 수 있는 편리한 수단이지만, QR 코드에 포함된 데이터를 적절한 검증, 새니타이제이션, 콘텐츠 유형 제한 없이 처리하면 심각한 보안 취약점이 발생합니다.

AndroGoat의 QR Code 시나리오에서는 스캔된 QR 코드에서 얻은 데이터를 안전하다고 가정하고 WebView에 표시하거나, URL을 열거나, 애플리케이션 로직에 전달하는 등의 추가 처리에 디코딩된 값을 직접 사용합니다.

취약점의 위험성

이 취약점의 위험성은 QR 코드가 악의적인 페이로드를 포함할 수 있다는 점입니다:

  • WebView에서 XSS: JavaScript 코드가 QR 코드에 포함되어 실행
  • 피싱 웹사이트로 리다이렉션: 사용자를 가짜 로그인 페이지로 유도
  • 의도하지 않은 애플리케이션 작업 실행: 권한 있는 기능 악용
  • 백엔드 또는 로컬 데이터베이스 작업에 해로운 입력 주입: SQL Injection 등
  • 세션 하이재킹: 인증 정보 탈취
  • 민감한 데이터 유출: 내부 데이터 외부 전송

사용자는 일반적으로 QR 코드를 신뢰하며 포함된 내용을 미리 볼 수 없기 때문에 이 공격 벡터는 특히 위험합니다. 공격자는 악의적인 QR 코드를 물리적 장소(포스터, 전단지)나 디지털 매체(이메일, 소셜 미디어)에 배포할 수 있으며, 사용자는 아무런 의심 없이 스캔하게 됩니다.

기능 분석

QR Code Scanner 메뉴로 이동하면 카메라 권한을 요청합니다. 권한을 허용하면 QR 코드 스캔 화면이 표시됩니다. 현재 액티비티를 확인합니다.

QRCodeXSSActivity로 확인되었습니다. 이름에서 알 수 있듯이 QR 코드와 XSS가 결합된 취약점입니다. 디컴파일된 코드를 살펴보겠습니다.

Activity 초기화 및 권한 요청 코드:

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(C1278R.layout.activity_qrcode_xssactivity);
        if (ContextCompat.checkSelfPermission(this, "android.permission.CAMERA") != 0) {
            ActivityCompat.requestPermissions(this, new String[]{"android.permission.CAMERA"}, TypedValues.TYPE_TARGET);
        }
        CodeScannerView scannerView = (CodeScannerView) findViewById(C1278R.id.scanner_view);
        final WebView webView = (WebView) findViewById(C1278R.id.webviewxss);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebChromeClient(new WebChromeClient());
        this.codeScanner = new CodeScanner(this, scannerView);
        CodeScanner codeScanner = this.codeScanner;
        if (codeScanner == null) {
            Intrinsics.throwUninitializedPropertyAccessException("codeScanner");
            codeScanner = null;
        }
        codeScanner.setDecodeCallback(new DecodeCallback() { // from class: owasp.sat.agoat.QRCodeXSSActivity$$ExternalSyntheticLambda0
            @Override // com.budiyev.android.codescanner.DecodeCallback
            public final void onDecoded(Result result) {
                QRCodeXSSActivity.onCreate$lambda$1(this.f$0, webView, result);
            }
        });
        scannerView.setOnClickListener(new View.OnClickListener() { // from class: owasp.sat.agoat.QRCodeXSSActivity$$ExternalSyntheticLambda1
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) throws IOException {
                QRCodeXSSActivity.onCreate$lambda$2(this.f$0, view);
            }
        });
    }

QR 코드 처리 로직 - 취약한 부분:

    public static final void onCreate$lambda$1(final QRCodeXSSActivity this$0, final WebView $webView, final Result it) {
        Intrinsics.checkNotNullParameter(this$0, "this$0");
        Intrinsics.checkNotNullParameter(it, "it");
        this$0.runOnUiThread(new Runnable() { // from class: owasp.sat.agoat.QRCodeXSSActivity$$ExternalSyntheticLambda2
            @Override // java.lang.Runnable
            public final void run() {
                QRCodeXSSActivity.onCreate$lambda$1$lambda$0(it, $webView, this$0);
            }
        });
    }

    /* JADX INFO: Access modifiers changed from: private */
    public static final void onCreate$lambda$1$lambda$0(Result it, WebView $webView, QRCodeXSSActivity this$0) {
        Intrinsics.checkNotNullParameter(it, "$it");
        Intrinsics.checkNotNullParameter(this$0, "this$0");
        String scannedText = it.getText();
        String htmlData = StringsKt.trimIndent("\n                    <html>\n                    <body>\n                        <h2>Scanned Product Details</h2>\n                        <h3>Product Found!</h3>\n                        <p>Product ID: <b>" + scannedText + "</b></p>\n                        <p>Status: Available</p>\n                    </body>\n                    </html>\n                ");
        $webView.loadData(htmlData, "text/html", "UTF-8");
        Toast.makeText(this$0, "Scan Complete", 0).show();
    }

화면 터치 이벤트 (카메라 재시작):

    public static final void onCreate$lambda$2(QRCodeXSSActivity this$0, View it) throws IOException {
        Intrinsics.checkNotNullParameter(this$0, "this$0");
        CodeScanner codeScanner = this$0.codeScanner;
        if (codeScanner == null) {
            Intrinsics.throwUninitializedPropertyAccessException("codeScanner");
            codeScanner = null;
        }
        codeScanner.startPreview();
    }

취약점 원인 분석

QRCodeXSSActivity의 취약점은 Reflected 또는 Stored Cross-Site Scripting(XSS)과 관련이 있으며, QR 코드 입력을 통해 발생합니다. 취약점 발생 과정을 데이터 흐름 관점에서 분석하면 다음과 같습니다.

첫 번째 문제: 신뢰할 수 없는 데이터 소스 (Source)

String scannedText = it.getText();

it.getText()를 통해 QR 코드에 담긴 문자열을 그대로 가져옵니다. 공격자가 <script>alert('XSS')</script> 같은 내용을 QR 코드에 인코딩했다면 이 변수에 그대로 저장됩니다. QR 코드는 사용자가 제어할 수 없는 외부 데이터이므로 신뢰해서는 안 됩니다.

두 번째 문제: 안전하지 않은 데이터 처리 (Flow)

String htmlData = "...<p>Product ID: <b>" + scannedText + "</b></p>...";

scannedText 변수를 아무런 필터링(Sanitization)이나 인코딩 없이 HTML 태그 사이에 문자열 결합(+)으로 직접 삽입합니다. 이 과정에서 HTML 특수 문자나 스크립트 태그가 제거되거나 이스케이프 처리되지 않습니다.\

세 번째 문제: 위험한 데이터 실행 지점 (Sink)

$webView.loadData(htmlData, "text/html", "UTF-8");

loadData()가 호출되면서 완성된 HTML을 WebView에서 렌더링합니다. 앞서 onCreate에서 setJavaScriptEnabled(true)로 JavaScript를 허용했기 때문에, QR 코드에 스크립트 태그가 있었다면 여기서 즉시 실행됩니다.

네 번째 문제: JavaScript 활성화

webView.getSettings().setJavaScriptEnabled(true);

이 설정이 없었다면 스크립트가 삽입되더라도 실행되지 않았을 것입니다. 하지만 JavaScript가 활성화되어 있어 XSS 공격이 성공적으로 수행됩니다.

다섯 번째 문제: 입력 타입 검증 부재

QR 코드의 내용이 제품 ID로 사용되어야 한다면, 숫자나 특정 형식의 문자열만 허용해야 합니다. 하지만 어떠한 형식 검증도 없이 모든 문자열을 받아들입니다.

취약점 검증 (Proof of Concept)

테스트 #1: 기본 XSS 페이로드

실습을 위해서는 악성 QR 코드를 먼저 생성해야합니다. QR 코드 텍스트 부분에 다음과 같은 내용으로 생성합니다.

<script>alert('QR CODE')</script>

AndroGoat 앱에서 QR 코드를 스캔하면:

JavaScript alert가 실행되어 "QR CODE"라는 메시지가 팝업으로 표시됩니다. 이는 QR 코드를 통해 임의의 JavaScript 코드를 실행할 수 있음을 증명합니다.