A /.git directory was found but navigating to it also returns a 403 Response (Forbidden). Attempting to use gobuster to see if responses other than 403 could be seen:
Since only the root seems to be unreadable, maybe we could extract the git files using git-dumper.py
$mkdir80_git_blog-dev$./git-dumper/git_dumper.pyhttp://blog-dev.travel.htb./80_git_blog-dev [-] Testinghttp://blog-dev.travel.htb/.git/HEAD [200] [-] Testinghttp://blog-dev.travel.htb/.git/ [403] [-] Fetchingcommonfiles [...omitted...]$ls-a./80_git_blog-dev.gitREADME.mdrss_template.phptemplate.php$ls-a./80_git_blog-dev/.gitCOMMIT_EDITMSGconfigdescriptionHEADhooksindexinfologsobjectsrefs$cat./80_git_blog-dev/.git/logs/HEAD00000000000000000000000000000000000000000313850ae948d71767aff2cc8cc0f87a0feeef63jane<jane@travel.htb>1587458094-0700commit (initial): moved to git$cat./80_git_blog-dev/README.md# Rss Template ExtensionAllowsrss-feedstobeshownonacustomwordpresspage.## Setup*`gitclonehttps://github.com/WordPress/WordPress.git`*copyrss_template.php&template.phpto`wp-content/themes/twentytwenty`*createlogsdirectoryin`wp-content/themes/twentytwenty`*createpageinbackendandchooserss_template.phpastheme## Changelog-temporarilydisabledcachecompression-addedadditionalsecuritychecks-addedcaching-addedrsstemplate## ToDo-finishloggingimplementation
The contents of the .git folder seems to pertain to the deployment of and RSS feed for other services. A user, jane (jane@travel.htb), was also found to have been responsible for the creation of the repository. And based on the output of wpscan from http[://]blog.travel.htb, the RSS service may have been deployed to it as well.
2.2 TCP PORT 443 : HTTPS
All subdomains, when accessed via HTTPS, returns an under construction page with the following message:
We are currently sorting out how to get SSL
implemented with multiple domains properly. Also we
are experiencing severe performance problems on SSL
still.
In the meantime please use our non-SSL websites.
Thanks for your understanding,
admin
PART 3 : EXPLOITATION
3.1 The RSS Feed
3.1.1 RSS in blog.travel.htb:
Looking back at the output of wpscan:
[+] WordPress version 5.4 identified (Insecure,releasedon2020-03-31).|FoundBy:RssGenerator (Passive Detection)|-http://blog.travel.htb/feed/,<generator>https://wordpress.org/?v=5.4</generator>|-http://blog.travel.htb/comments/feed/,<generator>https://wordpress.org/?v=5.4</generator>
As well as the landing page of http[://]blog.travel.htb, there is a link to Awesome RSS in the navigation bar:
This brings you to http[://]blog.travel.htb/awesome-rss:
3.1.3 Review of rss_template.php:
This was extracted from the contents of http[://]blog-dev.travel.htb/.git/
There us a function get_feed($url) that is using SimplePie for the RSS and memcache for caching data. The argument that will be fed to the function is probably passed through GET parameters (custom_feed_url) in http[://]blog.travel.htb/awesome-rss and if none are supplied, will default to http://www.travel.htb/newsfeed/customfeed.xml:
Which has the same contents listed in http[://]blog.travel.htb/awesome-rss only in XML format.
Aside from the get_feed($url) function there seems to be a debug page as well -- debug.php which could be requested by adding a GET parameter, ?debug. It will still request the usual page but will include debug statements enclosed in HTML comments:
The request to the local HTTP Server went through and it seems like a PHP serialized object was logged into memcache as indicated by the xct_ prefix.
3.3 Interaction with memcache
3.3.1 How data is saved to memcache
Based on the class-simplepie.php which is included in rss_template.php extracted from the WordPress Github Page:
[...omitted...]if ($this->feed_url !==null){ $parsed_feed_url =$this->registry->call('Misc','parse_url',array($this->feed_url));// Decide whether to enable cachingif ($this->cache && $parsed_feed_url['scheme'] !=='') { $url =$this->feed_url . ($this->force_feed ?'#force_feed':''); $cache =$this->registry->call('Cache','get_handler',array($this->cache_location,call_user_func($this->cache_name_function, $url),'spc')); }// Fetch the data via SimplePie_File into $this->raw_dataif (($fetched =$this->fetch_data($cache)) ===true) {returntrue; }elseif ($fetched ===false) {returnfalse; }list($headers, $sniffed)= $fetched;}[...omitted...]public $cache_name_function ='md5';[...omitted...]publicfunctionset_cache_name_function($function ='md5'){if (is_callable($function)) {$this->cache_name_function = $function; }}[...omitted...]
When the feed_url parameter is not null, a call() function from the registry property will be made to a get_handler() function in Cache.php and in this case the value of $this->cache_location has been set to memcache://127.0.0.1:11211/?timeout=60&prefix=xct_ and $url will be hashed using MD5 based on call_user_func($this->cache_name_function, $url):
What will happen is that the substring, memcache, will be extracted from the passed location (memcache://127.0.0.1...) which will return SimplePie_Cache_Memcache based on the array of handlers defined. After which a newly initialized class of the same name will be returned.
The constructor function taken from the SimplePie_Cache_Memcache class definition Memcache.php takes the same parameters from the ones passed to get_handler() from Cache.php. The values from $this->options will be changed since some values were set in the $location variable (?timeout=60&prefix=xct_) so in this case, the prefix that will be used is xct_ instead of simplepie. Afterwhich, the value of $name will be appended to the prefix.
The data provided (contents of $url) will then be serialized and saved into the cache.
To summarize specific to this scenario:
The caching begins by taking three parameters that will be passed to the get_handler() function in Cache.php -- memcache://127.0.0.1:11211/?timeout=60&prefix=xct_, fe1fb813519a90aa175e3f3d721a07ca (MD5 value of http://10.10.14.6/customfeed.xml), and spc
The get_handler() function will then determine what method of caching is needed based on the first parameter given; in this case, memcache so the class definition of SimplePie_Cache_Memcache will be used. The name of the data that will be written in the cache will follow the format --xct_ plus the value of md5("fe1fb813519a90aa175e3f3d721a07ca:spc")
A serialized version of the data will then be saved in to the cached catalogued with the prefix plus the newly generated md5.
3.3.2 Review of debug.php
The output for debug.php last time when http://10.10.14.6/customfeed.xml was requested was:
Following the process explained earlier, the marker generated (xct_54bddbaec1(...)) should be the same as md5(md5("http://10.10.14.6/customfeed.xml").":spc"); which is actually the case -- 54bddbaec1543acec82c7141efde0625
3.4 Input Sanitation Check
3.4.1 Review of template.php
This file was also extracted from the contents of http[://]blog-dev.travel.htb/.git/ and it seems to be responsible for validating user input supplied in the url parameter when requesting /awesome-rss/:
<?php/** Todo: finish logging implementation via TemplateHelper*/functionsafe($url){// this should be secure $tmpUrl =urldecode($url);if(strpos($tmpUrl,"file://")!==falseorstrpos($tmpUrl,"@")!==false) { die("<h2>Hacking attempt prevented (LFI). Event has been logged.</h2>"); }if(strpos($tmpUrl,"-o")!==falseorstrpos($tmpUrl,"-F")!==false) { die("<h2>Hacking attempt prevented (Command Injection). Event has been logged.</h2>"); } $tmp =parse_url($url,PHP_URL_HOST);// preventing all localhost accessif($tmp =="localhost"or $tmp =="127.0.0.1") { die("<h2>Hacking attempt prevented (Internal SSRF). Event has been logged.</h2>"); }return $url;}functionurl_get_contents ($url) { $url =safe($url); $url =escapeshellarg($url); $pl ="curl ".$url; $output =shell_exec($pl);return $output;}
The safe($url) defined prevents Local File Inclusion (LFI) using file:// and Server-Side Request Forgery (SSRF) by filtering requests made via localhost. Meanwhile, even though there is a shell_exec() function in url_get_contents(), Command Injection is avoided by first passing through the safe() function to avoid polluting the curl command and then passing the url parameter through escapeshellarg() to avoid injection using /$(<command>) or appending new commands after a semi-colon (;<command>) to name a few examples.
Also inside template.php is a defined class, TemplateHelper. There are two initialized object properties defined in the class, file and data, based on the class constructor. The `wakeup()__ function is executed during deserialization. In this case, the defined function, __init()__ will be executed when __wakeup()` is triggered. init() will write to `DIR.'/logs'` with the filename defined in $file and contents defined in $data.
3.4.2 The location of __DIR__.'/logs'
Earlier when the contents of http[://]blog-dev.travel.htb/.git were extracted, the README.md file stated that rss_template.php and template.php were saved to wp-content/themes/twentytwenty:
## Setup
* `git clone https://github.com/WordPress/WordPress.git`
* copy rss_template.php & template.php to `wp-content/themes/twentytwenty`
* create logs directory in `wp-content/themes/twentytwenty`
* create page in backend and choose rss_template.php as theme
The memcache service is running via localhost and based on template.php, the sanitation of user input is limited to blacklisting "localhost" and "127.0.0.1" which could easily be bypassed:
3.5.1 Leverage the TemplateHelper class
Serialized objects are passed through memcache so we will use the TemplateHelper to write a file into the server.
The initialized variables, $file and $data, were changed from being defined as private to public since the serialized data will be interpreted from outside the definition of the TemplateHelper class.
3.5.3 Bypassing the security check in template.php
if($tmp =="localhost"or $tmp =="127.0.0.1"){ die("<h2>Hacking attempt prevented (Internal SSRF). Event has been logged.</h2>"); }
The only checks are only if the URL is requested via "localhost" and "127.0.0.1". This could easily bypassed by using 2130706433 (decimal value for 127.0.0.1), using 0.0.0.0, or using 0. The payload will be changed to:
A check for a valid name(xct_\) is performed so it might be necessary for the data to be deserialized. The earlier execution was written as SpyD3r (Gopherus default) payload will be changed to:
The last curl command was added to trigger the cached request to xct_54bddbaec1543acec82c7141efde0625 but this time, the serialized content was from the gopherus payload.
3.5.7 The Uploaded Webshell
Checking if the webshell was uploaded into the server:
There is an AuthorizedKeysCommand in the sshd_config file. /usr/bin/sss_ssh_authorizedkeys will be responsible to supply public keys that will be used for authentication. This helps with not having public keys locally stored into the server.