iOS Pentesting Checklist: A Practical Verification Pass
Step-by-step iOS pentest flow: data storage, keychain, ATS, jailbreak detection, SSL pinning, Frida hooks and traffic inspection.
iOS Pentesting Checklist: A Practical Verification Pass#
Most iOS assessments still follow a 2014 skeleton: dump the IPA, grep the binary, proxy the traffic, hook pinning. That skeleton misses a lot. Since iOS 14 through iOS 26 the platform added hardened keychain defaults, Data Protection classes, App Tracking Transparency, Privacy Manifests, App Privacy Nutrition Labels, universal link requirements, ATS stricter defaults, and iOS 26 enabling TLS 1.3 with the X25519MLKEM768 quantum-secure key exchange by default in URLSession and Network APIs, and runtime mitigations that changed where secrets live. The checklist below reflects what you verify in 2026, not what a 2014 blog told you to do.
Order matters. Static triage tells you what to look for. Runtime checks confirm it. Traffic inspection ties both to the server. A finding that only exists in one layer is not a finding until it survives a second check.
Threat model and what you protect#
Before any command, write down who the app trusts. This frames every later check.
TRUST BOUNDARY
+---------------------------------------------+
| device (iOS 26.x) |
| +-----------+ +----------------------+ |
| | app | | OS sandboxes | |
| | process |<--| keychain, data | |
| | | | protection, app | |
| | | | groups, ATS | |
| +-----+-----+ +----------------------+ |
+--------|------------------------------------+
| HTTPS + token
v
+---------------------------------------------+
| your backend |
| API, token issuer, push provider |
+---------------------------------------------+
Adversary gains: a lost or stolen phone, a malicious app on the same device,
a rooted/jailbroken environment, a network position (cafe Wi-Fi, MITM), an
insider with a TestFlight build.
Each boundary below is a place where data or trust crosses. Test each one.
| Boundary | What crosses | Control to verify |
|---|---|---|
| device to network | API calls, tokens | ATS, pinning, TLS config |
| app to user data | local files, keychain | Data Protection class, file permissions |
| app to other apps | pasteboard, schemes, app groups | entitlements, scheme handlers |
| app to hardware | biometrics, Secure Enclave | LAContext, keystore semantics |
| app to backend | identity claims, role hints | server-side authorization |
1. Prepare the lab#
A dedicated device is the foundation. Keep anything that touches a production account on the lab only. Update the phone often enough that you can reason about the minimum supported iOS version.
- Install Xcode Command Line Tools on macOS.
- Install pieces with Homebrew:
brew install --cask burp-suite objection,pip3 install frida-tools,brew install libimobiledevice. - Load the Burp CA or Charles certificate on the device.
- Leave Settings > General > Software Update on.
- Use a throwaway Apple ID and a second test account.
For physical device proxying remember the iOS 26 TLS change: URLSession and Network APIs now default to TLS 1.3 with X25519MLKEM768, so an endpoint that negotiates TLS 1.2 or lower, or one that signs with RSA-PKCS1 v1.5, is a finding even before you hook anything.
2. Pull the app off the device#
There are a few routes to an IPA. An internal TestFlight build is the cleanest source. Otherwise, dump the encrypted IPA from a jailbroken device with frida-ios-dump or pull it with ipatool.
mkdir -p work && cd work pip3 install ipatool # decrypt/export: ipatool download -b com.example.app -o app.ipa
Extract the package.
unzip -o app.ipa -d unzipped ls unzipped/Payload/com.example.app/
The .app bundle holds the binary, Info.plist, embedded frameworks and asset catalog. If the target is React Native or Flutter, note whether the bundle ships a Hermes bytecode file or libapp.so; reversing those needs the matching loader, not just Hopper.
3. Static triage: Info.plist and entitlements#
Info.plist shows which permissions the app declares, what URL schemes it opens, and how it handles arbitrary loads.
plutil -p unzipped/Payload/com.example.app/Info.plist | head -n 60
Fields worth checking:
CFBundleURLTypes: the deep link surface.NSAppTransportSecurity:NSAllowsArbitraryLoads: truemeans ATS is off.UIFileSharingEnabled/LSSupportsOpeningDocumentsInPlace: file exposure via iTunes.- Camera, microphone and location usage descriptions.
MinimumOSVersion: older targets may ride known bugs.NSPrivacyTrackingand the privacy manifest declaration files (PrivacyInfo.xcprivacy).
Entitlements show what the signature actually allows.
codesign -d --entitlements :- unzipped/Payload/com.example.app/com.example.app
Watch for keychain-access-groups, com.apple.developer.associated-domains, aps-environment, com.apple.security.application-groups, and com.apple.developer.healthkit. An over-broad keychain share lets every app signed with the same group read shared items. App Groups are a shared-container finding, not a courtesy feature.
4. Binary and library checks#
A fat binary carries arm64 and x86_64. Look with lipo.
lipo -info unzipped/Payload/com.example.app/com.example.app nm -u unzipped/Payload/com.example.app/com.example.app | head strings unzipped/Payload/com.example.app/com.example.app | grep -i password | head
strings leaks API keys, hidden URLs, internal hostnames and verbose errors. Hardcoded secrets go straight into the report.
Static tooling:
class-dumpextracts class names and method signatures.Hopper DisassemblerorIDA Freeopens the binary for review.MobSFproduces a quick report: permissions, suspicious API use, network profile.
Check embedded frameworks for their own plist entries. Third-party SDKs often declare their own URL schemes and ATS exceptions that the main app never triaged.
5. Authorization and deep link tests#
Deep links are easy to miss. A custom scheme that opens the app is a surface for triggering in-app actions through a crafted link.
xcrun simctl openurl booted "myapp://transfer?amount=100"
On a real device, paste the link into Safari or Notes and tap it. On the universal link side, verify associated domains with the apple-app-site-association file served from the domain.
Things to test:
- Does the app still act when a required parameter is missing?
- Are tokens carried in the URL, and if so do they land in logs?
- Is login skipped after the scheme opens the app?
- Is untrusted input handed to a WebView?
In 2026 the expectation is that custom schemes are legacy surface. A finding that a sensitive action rides a custom scheme while a universal link equivalent is half-finished belongs in the report.
6. Network layer: ATS and traffic#
If ATS is misconfigured, the app may fall back to clear traffic. NSAllowsArbitraryLoads: true in Info.plist is already a finding.
To install Burp or Charles on the device:
Settings > General > VPN and Device Management > installed profile, then Settings > General > About > Certificate Trust Settings > full trust for the root.
# on the device, set the proxy via the Wi-Fi settings UI, then: frida-ps -Uai # confirm the device is reachable over USB
Open the app and watch traffic. Call out:
- Are requests HTTPS end to end?
- Is certificate validation enforced, or can it be bypassed?
- Does the token travel in a header on every request, or in a cookie?
- Which fields does the server repeat in every response?
- Is there a cleartext fallback path in code that the proxy never triggers in the happy case? Hunt for
NSAllowsLocalNetworkingand any customURLProtocol.
Detection: server access logs should show TLS 1.2 or 1.3 for every client hit. A spike of TLS 1.0 or 1.1 handshakes points to either an old client build or a pinning bypass attempt.
7. Jailbreak detection#
Production apps try to detect a jailbroken device. Without getting past those checks you cannot continue on the lab device. Two routes: manual inspection or Frida bypass.
frida-ps -Uai frida -U -f com.example.app -l jailbreak_bypass.js --no-pause
Stay inside the agreed scope. Running against your own or an explicitly approved build keeps the work legal and defensible.
Since a couple of iOS cycles the naive open() on /Applications/Cydia.app checks stopped working. The 2026 ground truth for getting a target running: Dopamine 3.0 (August 2026) covers iOS 16.5.1 through 17.3.1 on A14-A17/M1-M2 and iOS 26.0-26.0.1 on A12/A13 only; palera1n covers checkm8 devices (A8-A11) and is semi-tethered; A14+ devices on iOS 17.4 or later - which in 2026 means most modern hardware on iOS 26.x - have no public jailbreak at all, so lab work on those phones leans on a passcode-known encrypted backup, developer-mode builds, or an older device. TXM (the Trusted Execution Monitor) and PPL mean a kernel exploit also fights a second integrity layer, which is why the public tools narrowed to specific chips.
Most modern detection mixes:
- file existence on a long path list
canOpenURL("cydia://")variants- sandbox self-check via writing outside the container
dyldinjection scans forFridaGadget,CydiaSubstrate,Substitute- environment checks for
DYLD_INSERT_LIBRARIES
Test which one the target uses. A bypass that patches file existence checks misses a dyld scan, and vice versa. Report the evasion method, not just the bypass success.
8. SSL pinning bypass#
Many apps harden certificate validation, which shows up as an empty Burp view while the app still talks to the network. Try a Frida script.
frida -U -f com.example.app -l ssl_pinning_bypass.js --no-pause
Scripts behave differently across iOS versions. A failure has two likely causes: a custom socket stack, or a validation loop tied to certificate transparency logs. In the second case the script needs to change.
Objection helps here.
objection --gadget com.example.app explore # objection> ios sslpinning disable
Some apps pin at the SecTrust layer, some at the NSURLSession delegate, some inside a third-party networking SDK. Find where pinning lives before picking the hook. A bypass against SecTrust on an app that pins inside AFNetworking silently does nothing.
9. Local data storage#
Where does the app keep data, and who can read it.
- Tokens or passwords in
NSUserDefaults. - SQLite files under
DocumentsorLibrary/Application Support. - Keychain items: is
kSecAttrAccessibletight enough?kSecAttrAccessibleAfterFirstUnlockis common and loose. Watch forkSecAttrAccessibleWhenUnlockedThisDeviceOnlypaired with no backup exclusion. - PII or tokens in log output.
idevicesyslog | grep -i exampleapp
On a jailbroken device, dump the keychain.
security find-generic-password -l exampleapp -w
Missing NSFileProtection means a backup can carry the data without a wipe. Data Protection classes matter on iOS: NSFileProtectionComplete versus NSFileProtectionNone is the difference between data gone when the phone is locked and data present in an unlocked-device forensic image.
10. WebView and the JavaScript bridge#
A bidirectional attack surface appears when WKWebView exchanges messages with the host. In Safari > Develop > device > app, attach the Web Inspector.
- Is every
evaluateJavaScriptpayload validated? - Does
postMessagecheckorigin? - Are third-party links safe to open inside the WebView?
- Is
WKScriptMessageHandlerallowed on arbitrary frames via*origin?
A handler that trusts any origin can be reached by a page loaded from a hostile site inside the same WebView.
11. Biometric and local authentication#
Test how Face ID or Touch ID is used. Hook LAContext evaluatePolicy and see if the result can be flipped.
Interceptor.attach(ObjC.classes.LAContext["- evaluatePolicy:localizedReason:reply:"].implementation, { onEnter: function(args) { console.log("evaluatePolicy"); } });
If local auth is bypassable, the app needs a second factor on the server. Local-only biometrics is a finding when the same decision authorizes a payment.
12. App switching and background#
Check whether the app masks itself before going to background. Apps that forget to clear the screen in applicationDidEnterBackground leak in the App Switcher.
- Is
applicationProtectedDataDidBecomeUnavailablehandled? - Does a short background stint keep the session open?
- Are tokens still valid on a locked device?
13. Push notifications#
Verify how the device token is stored and used. A stolen token cannot send fake pushes for that user, but a leaked token loosens backend hygiene.
- Is the token stored as plaintext locally?
- Does the client validate the response when the token is sent to the backend?
- Does the push body carry PII?
14. Cookies and session handling#
Session lifetime, token deletion after logout, concurrent session limits. Session flaws sit high on the OWASP Mobile Top 10.
ls ~/Library/Developer/CoreSimulator/Devices/*/data/Library/Cookies
On the server side, cookie flags should be HttpOnly, Secure, and SameSite tuned.
15. Cryptography review#
Weak cryptography breaks on-device data safety. Verify the algorithms used in the keychain and storage layers.
- If AES is used, where does the key come from. A constant key is a finding.
- Base64 is encoding, not encryption. Frequent base64 in
stringsoutput deserves a look. - Falling back to TLS 1.0 or 1.1 is a finding. As of iOS 26
URLSessionnegotiates TLS 1.3 with X25519MLKEM768 by default, so a 1.0 or 1.1 fallback means the app explicitly opted back in.
openssl s_client -connect api.example.com:443 -tls1_1
16. Reverse engineering and Frida hooks#
Hook custom functions with Frida scripts.
Interceptor.attach(Module.getExportByName(null, "CCCrypt"), { onEnter: function(args) { console.log("CCCrypt called"); } });
Hooking signing, file reads and writes reveals the parameters that cross those boundaries. Hook SecStaticCodeCheckValidity to see whether the result can be forced to true. If the app opens its binary without validating the signature, a modified package can run in the same process context.
17. objection, needle, frida-ios-dump#
objection covers the daily needs. iOS modules include keychain dump, SSL pinning disable, file system listing and pasteboard viewer.
objection --gadget com.example.app explore # objection> ios keychain dump # objection> ios bundles show_frameworks
needle, frida-ios-dump, class-dump-z, and ipatool round out the kit. Pin toolchain versions per target, Frida and Objection drift fast.
18. App Groups and the shared container#
When an App Group is used, every app signed with the same group shares the container. Check com.apple.security.application-groups in entitlements.
ls ~/Library/Developer/CoreSimulator/Devices/*/data/Containers/Shared/AppGroup
Every plist and SQLite file in the group is readable by other apps in the group. On the simulator this is trivial to inspect; on a release build use a decrypted IPA to confirm the group list.
19. Signature and integrity checks#
Does the app validate its own signature. Hook SecStaticCodeCheckValidity and see whether the result can be forced to true.
Interceptor.attach(ObjC.classes.NSFileManager["- fileExistsAtPath:"].implementation, { onLeave: function(retval) { console.log("file check: " + retval); } });
20. Privacy Manifests and ATT#
Since Spring 2024 Apple requires a Privacy Manifest for apps. Read PrivacyInfo.xcprivacy from the bundle. It declares required-reason APIs (file timestamps, disk space, active keyboard, system boot time). If the app collects those APIs but the manifest declares nothing, the app gets rejected or the SDK misbehaves. Cross-check declared reasons against actual use in the binary. A mismatch is a compliance finding, and it often means the same API is reachable for fingerprinting.
Check NSUserTrackingUsageDescription is present and the app calls requestTrackingAuthorization before touching the IDFA. Missing prompt with IDFA in use is App Review material, not just a privacy note.
21. Entitlements deep pass#
codesign -d --entitlements :- unzipped/Payload/com.example.app/com.example.app codesign -dv unzipped/Payload/com.example.app/com.example.app 2>&1
Look for:
com.apple.developer.associated-domains: every domain listed is a universal link surface to probe.aps-environment:productionversusdevelopment, expected value for a release build.com.apple.security.application-groups: shared container scope, covered above.com.apple.developer.healthkit,com.apple.security.ikeychain,com.apple.developer.networking.multipath: each opens its own probe path.- Missing
com.apple.security.application-groupsdoes not clear the shared container; check the file system anyway.
22. Pair mobile with backend findings#
Combine client-side findings with API results. If a server trusts a client-set role, any parameter the client can change becomes a guess.
- Does the server trust a role hint sent by the client?
- Is rate limiting in place?
- Do error messages leak internal state?
- Does the backend pin decisions to client-asserted values for jailbreak or pinning state? A client-side bypass should never grant server trust.
Detection and mitigation matrix#
| Surface | Detection signal | Mitigation that should exist |
|---|---|---|
| ATS off | NSAllowsArbitraryLoads in plist, TLS 1.0 handshakes in logs | ATS on, no domain-level exceptions |
| Pinning bypass in lab | Burp shows traffic after ssl_pinning_bypass.js | Root/jailbreak detection on the client, server-side beaconing of the app state |
| Shared keychain | same access group on multiple binaries | tight access group scope, device-only items |
| Cleartext fallback | NSAllowsLocalNetworking, custom URLProtocol | none, block fallback by removing the key |
| Weak crypto in storage | CCCrypt called with static key via hooks | Secure Enclave backed keys, SecKey asymmetric for signing |
| Scheme handler abuse | parameters reach application:openURL: without auth | universal links only, auth gate before acting |
Workflow recap#
1. pull IPA -> 2. static triage (plist, entitlements, strings)
| |
v v
3. lab device 4. traffic + ATS posture
| |
v v
5. jailbreak bypass -> 6. pinning bypass
| |
v v
7. storage / keychain / app groups / WebView
|
v
8. biometrics / lifecycle / background
|
v
9. pair with backend findings -> report
Summary#
iOS testing runs on a specific order. Static analysis, traffic inspection and runtime hooks have to be planned together; none of them stands alone. This list gives a full sweep, but no single item proves the app is weak by itself. When time is tight, rank: data leakage, authorization gaps, deep link injection, pinning bypass. Writing the report on those four axes keeps the narrative clean.
What do you think?
React to show your appreciation