Back to Blog
iOS Security
Aug 06, 2026
4 min read

FreshCart - MobileHackingLab

My solution for FreshCart by mobile hacking lab iOS mobile application exploitation challenge

FreshCart - MobileHackingLab
# Freshcart iOS Application Security Lab: JavaScript-to-Native Bridge Exploitation Challenge. This challenge is centered around a fictitious grocery app called Freshcart. Freshcart contains a critical vulnerability that allows token stealing by exploiting the JavaScript to native bridge. Your objective is to exploit this vulnerability to steal the token used within the app. ![Cert](/uploads/1786412900281-screenshot-from-2026-08-11-04-48-07.webp) ## 1. Application Analysis By analysing the application files and code, Freshcart is a native iOS mobile application that embeds a full React SPA inside WKWebView. Classic hybrid app that is vulnerable to JS to native bridge vulnerability. ![analysis](/uploads/1786092021871-applicationanalysis.webp) ``` evaluateJavaScript:completionHandler: _objc_msgSend$evaluateJavaScript:completionHandler: ``` ``` total 12K drwxr-xr-x 7 basel basel 4.0K Aug 6 21:56 ../ drwxr-xr-x 3 basel basel 4.0K May 15 15:37 ./ drwxr-xr-x 3 basel basel 4.0K May 15 15:37 GCDWebServer.framework/ ``` --- ## 2. Info.plist After analysing the plist, I found `NSAllowsArbitraryLoads = true` which disables Apple Transport Security entirely. Combined with GCDWebServer serving content locally. ![plist](/uploads/1786092114652-plist.webp) --- ## 3. Endpoints We can see the endpoints hardcoded inside the application source code. ![Endpoints](/uploads/1786092149965-loginendpoint.webp) --- ## 4. main.adf11907.js The code snippet defines two primary functions responsible for authentication token management: ### `wo(e, t)` — Token Retrieval Function ```js wo = (e, t) => { if ( window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.retrieveToken ) { const n = (r) => { (r.data && r.data.token ? e(r.data.token) : t(), window.removeEventListener("message", n)); }; (window.addEventListener("message", n), window.webkit.messageHandlers.retrieveToken.postMessage(null)); } else { const n = localStorage.getItem("auth_token"); ((n && "undefined" !== n && null != n) || t(), e(n)); } } ``` **A.** Checks if `window.webkit.messageHandlers.retrieveToken` is registered by the native app. **B.** Registers a global event listener for message events: `window.addEventListener("message", n).` **C.** Triggers the bridge call: `window.webkit.messageHandlers.retrieveToken.postMessage(null)` **D.** The native Swift/ObjC layer receives null and responds asynchronously by posting a message back to the WebView window containing `{ token: "" }`. **E.** The event handler extracts `r.data.token`, calls the success callback `e(token)`, and cleans up the event listener. **F.** If the app is opened outside the native wrapper (or if the bridge isn't mounted), it attempts to read `auth_token` directly from `localStorage.getItem("auth_token")`. --- ### `Ao(e)` — Token Storage Function ```js Ao = (e) => { if ( window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.storeToken ) { const t = (e) => { (e.data && e.data.token ? (window.location.href = "/") : (window.location.href = "/logout"), window.removeEventListener("message", t)); }; (window.addEventListener("message", t), window.webkit.messageHandlers.storeToken.postMessage(e)); } else localStorage.setItem("auth_token", e); } ``` --- ## 5. Token Extraction To solve this kind of challenge you can get the token using many ways: > **Stored XSS via review form**: inject an HTML payload into the Review Content field which auto-executes via `` and hijacks the native bridge response to display the token in the page itself. > **Modifying** the js **source code** to show the token. > **Intercept** the **traffic** and get the token from the header after bypassing TLS (if any). > **Dump memory** while the application process is running and search for the token. > Use **hooking** scripts to **monitor JS** related methods. Since this challenge pushes towards not using automated scripts, the optimal solution is injecting a stored XSS payload to expose the token. --- ### A. JS Native Bridge XSS Solution ![TokenByXSS](/uploads/1786121637011-tokenbyxss.webp) Submit the following payload in the **Review Content** field of any product. The Review Title can be anything: ``` ``` When the review page renders, the invalid `` fires `onerror` immediately, which registers a `message` event listener and triggers `retrieveToken.postMessage(null)` on the native bridge. The native layer responds with `{ token: "" }`, which gets written into the `