Back to Blog
iOS SecurityAug 06, 20264 min read
FreshCart - MobileHackingLab
My solution for FreshCart by mobile hacking lab iOS mobile application exploitation challenge

# 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.

## 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.

```
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.

---
## 3. Endpoints
We can see the endpoints hardcoded inside the application source code.

---
## 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

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 `