# IP Spoofing to Account Takeover: You Patched It? Really?

## **Abstract**

In my [previous article](https://blog.mirzadzare.net/from-log-in-with-oauth-to-your-account-is-mine-desktop-app-edition), I described how I found a **security flaw** in a popular desktop app's OAuth flow that allowed me to **steal** any user's account with just **one click**. I reported it, saw it **patched**, and then **bypassed** the patch **again**. Since the process of bypassing and exploiting the flaw is interesting to me, I decided to write a second article about it. I also tried to get permission to disclose the report, but they didn't allow me to do so. Therefore, throughout this post, I'll refer to the domain as [redacted.com](http://redacted.com).

## Setup

Since the setup environment **hasn't changed,** I refer to the setup section of [this](https://blog.mirzadzare.net/from-log-in-with-oauth-to-your-account-is-mine-desktop-app-edition#heading-setup) write-up.

## Reconnaissance

After the vulnerability was **patched**, I tried to do the same thing I did before to see what would **prevent** me from taking over a victim's account.

### **Analyzing The Patch**

I followed the **previous attack scenario** explained [here](https://blog.mirzadzare.net/from-log-in-with-oauth-to-your-account-is-mine-desktop-app-edition#heading-attack-scenario), and you can see its diagram in the image below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1771452258913/ef9e470a-9b19-4b9e-8ee6-22ece75fb08a.png align="center")

As an attacker, I **initiated** the OAuth login process in my **desktop app**. I copied the generated OAuth URL (`https://oauth2.redacted.com/auth/google?remote_key=[ATTACKER_REMOTE_KEY]&jwt=1&client_id=[CLIENT_ID]&lang=en&return_skip=1`) from the **browser** it opened and then opened it on the **victim’s device** with a **different IP**. However, after completing the OAuth process on the victim’s device, I *wasn't* able to log in to the attacker’s desktop app as the victim, as I expected.

The patched version showed me the following page:

![](https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/69207a5a005b143f71163caa/8429489e-ab8c-4bd3-a422-39a2d8c0eef4.jpg align="center")

I tried again using a **different device** but the **same IP** through a **VPN**. I discovered that as an attacker, I could take over the victim's account only if our **IPs matched**. This showed that the prevention **relied** only on **IP**, which is usually **not enough**.

### IP Spoofing via HTTP Headers

By checking all the requests initiated by the **desktop application**, I noticed that all domains are behind the **CDN**, and when a developer puts a domain **behind a CDN**, finding the **real** **client IP** can be **challenging**. This is because there is no **universal**, standardized way for CDNs to convey the **original visitor's IP** to the backend, and this process can vary based on the CDN provider, programming language, server configuration, and other factors.

To obtain the real client IP behind a proxy (such as a CDN) and pass it to the server, several HTTP headers are commonly used:

*   `X-Forwarded-For` is a list of comma-separated IPs that gets appended to by each traversed proxy. The idea is that the **first IP** (added by the first proxy) is the **true client IP**. Each subsequent IP is another proxy along the path. The last proxy’s IP is ***not*** present (because proxies don’t add their own IPs, and because it connects directly to the server so its IP will be directly available anyway).
    
*   `Forwarded` is the most official but seemingly least-used header.
    
*   There are also special single-IP headers like `X-Real-IP` (Nginx), `CF-Connecting-IP` (Cloudflare), or `True-Client-IP` (Cloudflare and Akamai).
    

I prepared a victim's device, saved its **IP**, and initiated the OAuth flow with the attacker's device. This time, I used the `X-Forwarded-For header` in **every** request made by the Desktop application. Then I opened the generated OAuth URL using the victim’s device, and I successfully logged in as the victim on the attacker's Desktop application.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1771498172937/07e38cb2-a377-4c68-b503-ab5fe8e4875a.png align="center")

> As we saw, the patch is **vulnerable**, but one piece of the puzzle is missing for the exploit: How does the attacker find out the victim's IP to initiate the OAuth flow with it?

### Finding Victim’s IP

I previously identified a **stored XSS** vulnerability in the `redirect_uri` parameter of a specific endpoint, which has since been patched. In brief, when users accessed the **Manage Account** section within the **desktop application**, the backend generated a **token** and then triggered a **browser** window opening with a URL structured as `https://redacted.com/token?t=Code`. This URL facilitated **authentication transfer** from the **application** to the **browser** by leveraging data linked to the `Code` passed in the `t` parameter. The Code contained both account information and a `redirect_uri` value, which dictated the **destination URL** after the authentication process completed. The vulnerability happened because there were ***no*** **checks** on the `redirect_uri`, allowing **harmful** scripts to be added. This issue was **fixed** after my report.

The following request to the backend API shows the request body for generating the `Code` passed in the `t` parameter in the URL:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1771437255402/74578afa-a01b-49cd-aae7-cd04087e7208.png align="center")

And the `Code` that you'll receive in response:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1771439229452/550f56cd-c69a-4783-96a2-05a8026844b8.png align="center")

I attempted to exploit this feature to **achieve** an **open redirect** by **altering** the `redirect_uri` parameter to something like `mirzadzare.net`. I obtained the generated code from the response and opened the URL (`https://redacted.com/token?t=code`) in a browser. As a result, it **redirected** me to `mirzadzare.net` after the **authentication transfer**. With this low-impact open redirect, I could **redirect** the **victim** to **my website** and **log their IP!** The puzzle is finally complete.

## Attack Scenario

> Based on the analyzed flow, the process of logging the victim's IP and initiating the OAuth flow needed to be automated. I decided to write a PHP exploit to mimic the desktop application, capture the victim's access token, and send it to the attacker via a Telegram bot.

The attack scenario is illustrated in the diagram below:

![](https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/69207a5a005b143f71163caa/f0676bf5-39ab-45ed-a508-9257318add50.png align="center")

1.  The attacker **alters** the `redirect_uri` and sends it to the API endpoint, receiving an **authorization** **code** in response.
    
2.  The attacker **crafts** a **malicious link** containing this **code** and sends it to the victim.
    
3.  The victim opens the malicious link.
    
4.  The exploit sends a request to the desktop application's API to **initiate** the OAuth flow and **simultaneously** establishes a **WebSocket** connection with the backend server.
    
5.  The exploit opens a login window in the victim's browser (I could do this **without** popping up a window, but for **simplicity** and **clarity**, I did it for the proof of concept.).
    
6.  The victim enters their **credentials** in the OAuth provider login window.
    
7.  The exploit **continuously** monitors the **WebSocket connection** and detects when the victim **successfully** logs in and the **access token** is received.
    
8.  Finally, the exploit sends the **stolen access token** to the **attacker’s Telegram bot**, enabling the attacker to **take over** the victim's account.
    

![](https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/69207a5a005b143f71163caa/85a696de-b478-4c21-813b-0ec405995dbc.jpg align="center")

In the POC above, the current browser tab displays the exploit page, while the other shows the company's OAuth login result page. Once completed, the **access token** is sent from the **victim's browser** to the **attacker** via a **Telegram bot**.

## **Conclusion**

This writeup shows how **partial fixes** can create a false sense of security instead of removing the risk completely. Even though the original issue was fixed, the patch used **IP-based** **validation** as its **main** trust method, which is weak in modern web setups with proxies, CDNs, and client-controlled headers. By **combining small issues** like IP spoofing and an open redirect, a full account takeover was possible despite the initial fix.

This case teaches important lessons for developers and security teams. First, don't **rely** only on **changeable client properties** like IP addresses for authentication and authorization. Second, test fixes against real attacker models, not just the original example. Third, small issues like open redirects or header misconfigurations can become **serious** when **combined**.

> Security is about how small weaknesses interact, not just one flaw.
