Operation Promotion
One engagement stands between you and your next title. - by l000gic
The following post by 0xb0b is licensed under CC BY 4.0
Scenario
You are up for promotion at Hadron Security. Your senior lead, Mara, has handed you a solo engagement against RecruitCorp, a small recruiting firm with a public-facing portal. Compromise the host, capture the flags, and demonstrate that you are ready for the Penetration Tester title.
Summary
Summary
In Operation Promotion, we enumerate a small recruiting firm's host and identify SSH, an Apache web portal, and an SMB service exposing a readable public share via anonymous guest authentication. After discovering a restricted /admin/ directory through robots.txt and Feroxbuster, we bypass the login form with the SQL injection payload admin' -- and gain access to an internal dashboard, which reveals a sysmaint-checks/ping.php endpoint vulnerable to command injection via the host parameter. We exploit this by injecting a busybox nc reverse shell through command substitution, landing a callback as www-data and uncovering the database user jford. After failing to crack the bcrypt hash, we generate a custom wordlist using Hashcat's dive.rule and brute-force SSH with Hydra to authenticate as jford, retrieving the user flag from the home directory. Finally, we abuse a permissive sudo rule allowing jford to run find as root and leverage GTFOBins to spawn a root shell via sudo /usr/bin/find . -exec /bin/sh \; -quit, fully compromising the host and retrieving the final flag from /root/flag.txt.
Recon
We use rustscan -b 500 -a operation-promotion.thm --top -- -sC -sV -Pn to enumerate all TCP ports on the target machine, piping the discovered results into Nmap which runs default NSE scripts -sC, service and version detection -sV, and treats the host as online without ICMP echo -Pn.
A batch size of 500 trades speed for stability, the default 1500 balances both, while much larger sizes increase throughput but risk missed responses and instability.

We discover three open ports. Among those are 22 (SSH), 80 (HTTP) and 139+445 (SMB). SSH is running OpenSSH 9.6p1 and the web server is hosted via Apache 2.4.58. The robots.txt disallows /admin/.

We start by enumerating the SMB service using NetExec and attempt to authenticate anonymously as a guest without a password. We are successful and see that we have read access to the public share.

We connect to the share using impackets smbclient.py and find a README.txt file in the share, but it doesn't contain anything of note.

We visit the website. At first glance, it looks like we're looking at a purely static page. RecruitCorp is launching a spring hiring campaign.

We enumerate additional directories using Feroxbuster and find the /admin/ directory again.

We visit this site and are presented with a login page.

Access as admin
We're trying to log in using a simple login bypass. By inserting a comment --, we're trying to bypass the password check.

We were able to log in successfully using the payload and now have the dashboard in front of us.

This dashboard provides a user lookup.

We try out different IDs and find an entry for the sysma-int service account, which reveals another directory containing a ping.php page.

Enum DB
As a little bonus, we can also use the SQL injection to enumerate all users in the database.
With the following script we use the HTTP response status (302 redirect vs. not) as a true/false oracle to confirm whether injected conditions match. Then we let it binary-search each character via payloads like ' OR UNICODE(SUBSTR((SELECT username FROM users LIMIT 1 OFFSET <i>),<pos>,1))<=<n> -- (same for the password) to extract every credential from the users table one character at a time.


Shell as www-data
For now, let's focus on the newly discovered directory mentioned in the note. When we call it, we are prompted to use it via the hosts parameter.

We make a simple test to ping localhost and see the output of a linux ping command. This suggest that a system command might being executed and we should try to inject commands.

Via command substituion we try to fetch our webserver...

... and we get a hit. We are able to succesfully inject commands.

Next, we try to get a reverse shell. We run a listener. For this we will be using Penelope.
Next, we replace the curl command with a busybox reverse shell. We get a hit, and are www-data.

Here we are able to identifiey the db_user jford and a corresponding db bcrypt hash. The /etc/passwd reveals, that the user of the system is also jford.

Shell as jford
Unfortunately we are not able to crack the db_pass_hash. Also the found passwords inside the DB are not of any use here.
We inspect the index again. Here we see some keywords. Maybe we need to extract some keywords using cewl and generate our own wordlist. Also Seasons combined with years”is a popular password. But this simple password won't work.

We generate a wordlist from the base spring2026. For brute-force attacks, we use Hashcat with the dive rule. This generates nearly 100,000 possible passwords.

We use Hydra to launch a dictionary attack on the SSH services as jford and select our newly generated wordlist. After a short while, we get a match.

We are jford...

... and find the users flag in the home directory of the user.

Shell as root
As jford we are allowed to run find as root via sudo.

With that we can spawn a shell in the context of root. See GTFOBins - living of the land...

We run the following command and become root. We find the final flag at /root/flag.txt.

Last updated
