# TryHackMe TryHeartMe—Writeup

Welcome back to another part of the Valentine's special CTF series of TryHackMe, TryHeartMe. No drama, let's start.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F2llhXZaJ0g0e3IBNAAyn%2FWpCNiiv.gif?alt=media&#x26;token=f0f6a63a-1b00-4c84-91aa-43b840c975c5" alt=""><figcaption></figcaption></figure>

Let's visit the website as always since this is a web category challenge:

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2Fc9Jjb3mmOFyNgAud4xfC%2FScreenshot%20(1304).png?alt=media&#x26;token=a40a18a2-31ff-4cb2-8337-cbd44689b113" alt=""><figcaption></figcaption></figure>

Oh, a Valentine's shop, let's register and try to buy something for nothing.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FCqMTNnNwDbGT8ZFkQdV3%2FScreenshot%20(1306).png?alt=media&#x26;token=746c743b-4574-4f46-8661-e7b029f112b0" alt=""><figcaption></figcaption></figure>

After completing the registration process, we were redirected back to the main dashboard, now authenticated with a valid account.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FszOvDBeWPi45q0enHnEp%2FScreenshot%20(1307).png?alt=media&#x26;token=5a33b94c-b632-46d0-8f95-56f70e6bc339" alt=""><figcaption></figcaption></figure>

As you can see, our credits is 0, so we can't buy anything.&#x20;

As I inspect the page, I noticed this **JWT** token that looks interesting:

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FyWV6CzhIQSovRtuvND4n%2FScreenshot%20(1308).png?alt=media&#x26;token=96ec3ace-f295-45e3-ab1b-e0517ba85b22" alt=""><figcaption></figcaption></figure>

Next thing I did is to decode it using my own decoder for **JWT** (Will release the source code of this in the future hehe).

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FdeMgiDjOkbXkL1tfvj4X%2FScreenshot%20(1335).png?alt=media&#x26;token=f718f7af-e609-40c5-86a9-38bd0f0aa183" alt=""><figcaption></figcaption></figure>

Upon inspecting the authentication mechanism, it was observed that the application stored sensitive fields, specifically the user **role** and **credit balance,** directly inside the JSON Web Token (JWT).

This design choice indicates that the application relies on the token’s payload to determine both authorization level and available credits. In other words, access control and balance-related decisions are derived entirely from data supplied by the client through the JWT.

Since JWTs are issued to the client and included with every request, this places a significant amount of trust on client-controlled data rather than enforcing these checks server-side.

JWTs are readable by default. If the application does not properly validate the token’s signature or allows weak configurations, an attacker may alter the payload to escalate their role or inflate their credit balance.

What we can do is to change the role to **admin** and credits to **9999999999**:

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2Fj8PWtCBY2wu1NmAhkwp3%2FScreenshot%20(1336).png?alt=media&#x26;token=f198f3f3-8868-4f1d-927b-9e98332072b7" alt=""><figcaption></figcaption></figure>

With the forged token prepared, we injected it into the browser’s cookie storage and refreshed the page to apply the changes.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F6X5qVTz0HGO9Bzo47zTB%2FScreenshot%20(1322).png?alt=media&#x26;token=ed201793-f91b-4dda-a0ae-1236e2a19370" alt=""><figcaption></figcaption></figure>

Observe the updated credit balance, and an additional item now visible. This item appears to be restricted to administrative accounts. Since the role was forged to `admin`, the account gained access to content that would otherwise be unavailable.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FZS3KA9U4eQ8OtDDA8Tvp%2FScreenshot%20(1323).png?alt=media&#x26;token=27c4e58f-d568-4d79-8cad-27882b9e8c46" alt=""><figcaption></figcaption></figure>

So it's the flag I guess, since we have unlimited credits, we can buy it!

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F32rnx1eVPYZEaY1tf1Ib%2FScreenshot%20(1324).png?alt=media&#x26;token=6d9f8be3-21f0-40c7-b9f5-86cbb3181c78" alt=""><figcaption></figcaption></figure>

After purchasing the Valenflag, we've got the flag!

This challenge ends not with complexity, but with misplaced trust. The application handed its most critical decisions, identity, authority, and value to a token never meant to carry that weight. By relying on JWTs as a source of truth rather than a proof of identity, the system quietly surrendered control to the client. No brute force. No exploit chain. Just a broken assumption. JWTs are powerful when treated correctly. When they aren’t, they don’t fail loudly, they fail silently, leaking privilege and trust one field at a time. And in the end, the flag wasn’t earned by breaking encryption, but by exposing a system that trusted too much… and verified too little.
